Compare commits
49 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
689012131f | ||
![]() |
6cdcf92782 | ||
![]() |
9c8bcbfdd3 | ||
![]() |
0b83a66b85 | ||
![]() |
9711cc868c | ||
![]() |
f8cb7599e6 | ||
![]() |
7636618e23 | ||
![]() |
5bc54c12f1 | ||
![]() |
e10e530dee | ||
![]() |
d69118b085 | ||
![]() |
dc90549b9d | ||
![]() |
b552dbc904 | ||
![]() |
a6a1678b47 | ||
![]() |
7a46ef5f19 | ||
![]() |
f9e0f7b390 | ||
![]() |
9211baf7ec | ||
![]() |
de6131f4f5 | ||
![]() |
2cb11e443c | ||
![]() |
a43ffcdef4 | ||
![]() |
6615e68430 | ||
![]() |
36daa7ccc1 | ||
![]() |
6e5481f345 | ||
![]() |
ba1c14ca0e | ||
![]() |
c69bb2ef71 | ||
![]() |
9cb4754132 | ||
![]() |
5ba8289c87 | ||
![]() |
258782c648 | ||
![]() |
c568bc1515 | ||
![]() |
da668f93cf | ||
![]() |
037dbd792f | ||
![]() |
7ec7afed87 | ||
![]() |
bea50e6db5 | ||
![]() |
6a00e75816 | ||
![]() |
957adaf6ee | ||
![]() |
827fb33eeb | ||
![]() |
19c96ee83f | ||
![]() |
5a7f52c773 | ||
![]() |
dddeff802f | ||
![]() |
91f6310892 | ||
![]() |
0389642543 | ||
![]() |
8528e0beff | ||
![]() |
e665a8f18b | ||
![]() |
6d5acbad2c | ||
![]() |
7217d0f753 | ||
![]() |
16a5d88dfb | ||
![]() |
646a46727f | ||
![]() |
f5e9197f98 | ||
![]() |
b47f26684b | ||
![]() |
3a03b5f1c6 |
154 changed files with 4777 additions and 985 deletions
12
.github/workflows/deploy-store.yml
vendored
12
.github/workflows/deploy-store.yml
vendored
|
@ -28,15 +28,14 @@ jobs:
|
||||||
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
||||||
run: |
|
run: |
|
||||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg
|
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg
|
||||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg
|
|
||||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
||||||
- name: Upload apk to google play
|
- name: Upload apk to google play
|
||||||
env:
|
env:
|
||||||
|
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||||
PLAY_SERVICE_ACCOUNT_EMAIL: ${{ secrets.PLAY_SERVICE_ACCOUNT_EMAIL }}
|
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
|
||||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
|
||||||
run: ./gradlew publishPlayRelease -PenableFirebase --stacktrace;
|
|
||||||
|
|
||||||
deploy-app-gallery:
|
deploy-app-gallery:
|
||||||
name: Deploy to AppGallery
|
name: Deploy to AppGallery
|
||||||
|
@ -60,7 +59,6 @@ jobs:
|
||||||
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
||||||
run: |
|
run: |
|
||||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-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
|
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
||||||
- name: Prepare credentials
|
- name: Prepare credentials
|
||||||
env:
|
env:
|
||||||
|
@ -68,7 +66,7 @@ jobs:
|
||||||
run: echo $AGC_CREDENTIALS > ./app/src/release/agconnect-credentials.json
|
run: echo $AGC_CREDENTIALS > ./app/src/release/agconnect-credentials.json
|
||||||
- name: Build and publish HMS version
|
- name: Build and publish HMS version
|
||||||
env:
|
env:
|
||||||
|
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
run: ./gradlew bundleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace
|
||||||
run: ./gradlew assembleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace
|
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -186,7 +186,7 @@
|
||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright 2019 Wulkanowy
|
Copyright 2021 Wulkanowy
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -21,8 +21,8 @@ android {
|
||||||
testApplicationId "io.github.tests.wulkanowy"
|
testApplicationId "io.github.tests.wulkanowy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 94
|
versionCode 97
|
||||||
versionName "1.2.1"
|
versionName "1.3.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|
||||||
|
@ -96,10 +96,20 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playConfigs {
|
||||||
|
play { enabled.set(true) }
|
||||||
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
viewBinding true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bundle {
|
||||||
|
language {
|
||||||
|
enableSplit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
testOptions.unitTests {
|
testOptions.unitTests {
|
||||||
includeAndroidResources = true
|
includeAndroidResources = true
|
||||||
}
|
}
|
||||||
|
@ -130,25 +140,24 @@ kapt {
|
||||||
}
|
}
|
||||||
|
|
||||||
play {
|
play {
|
||||||
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
|
|
||||||
serviceAccountCredentials = file('key.p12')
|
|
||||||
defaultToAppBundles = false
|
defaultToAppBundles = false
|
||||||
track = 'production'
|
track = 'production'
|
||||||
updatePriority = 3
|
updatePriority = 3
|
||||||
|
enabled.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
huaweiPublish {
|
huaweiPublish {
|
||||||
instances {
|
instances {
|
||||||
hmsRelease {
|
hmsRelease {
|
||||||
credentialsPath = "$rootDir/app/src/release/agconnect-credentials.json"
|
credentialsPath = "$rootDir/app/src/release/agconnect-credentials.json"
|
||||||
buildFormat = "apk"
|
buildFormat = "aab"
|
||||||
deployType = "draft"
|
deployType = "draft"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
work_manager = "2.5.0"
|
work_manager = "2.6.0"
|
||||||
android_hilt = "1.0.0"
|
android_hilt = "1.0.0"
|
||||||
room = "2.3.0"
|
room = "2.3.0"
|
||||||
chucker = "3.5.2"
|
chucker = "3.5.2"
|
||||||
|
@ -157,7 +166,7 @@ ext {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.github.wulkanowy:sdk:1.2.1"
|
implementation "io.github.wulkanowy:sdk:1.3.0"
|
||||||
|
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
|
@ -174,10 +183,10 @@ dependencies {
|
||||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||||
implementation "androidx.viewpager:viewpager:1.0.0"
|
implementation "androidx.viewpager:viewpager:1.0.0"
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.1.0"
|
implementation "androidx.constraintlayout:constraintlayout:2.1.1"
|
||||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
|
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
|
||||||
implementation "com.google.android.material:material:1.4.0"
|
implementation "com.google.android.material:material:1.4.0"
|
||||||
implementation "com.github.wulkanowy:material-chips-input:2.2.0"
|
implementation "com.github.wulkanowy:material-chips-input:2.3.1"
|
||||||
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
||||||
implementation 'com.github.lopspower:CircularImageView:4.2.0'
|
implementation 'com.github.lopspower:CircularImageView:4.2.0'
|
||||||
|
|
||||||
|
@ -211,11 +220,11 @@ dependencies {
|
||||||
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
||||||
implementation 'com.fredporciuncula:flow-preferences:1.5.0'
|
implementation 'com.fredporciuncula:flow-preferences:1.5.0'
|
||||||
|
|
||||||
playImplementation platform('com.google.firebase:firebase-bom:28.4.0')
|
playImplementation platform('com.google.firebase:firebase-bom:28.4.1')
|
||||||
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
||||||
playImplementation 'com.google.firebase:firebase-messaging:'
|
playImplementation 'com.google.firebase:firebase-messaging:'
|
||||||
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
||||||
playImplementation 'com.google.android.play:core:1.10.1'
|
playImplementation 'com.google.android.play:core:1.10.2'
|
||||||
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
||||||
|
|
||||||
hmsImplementation 'com.huawei.hms:hianalytics:6.2.0.301'
|
hmsImplementation 'com.huawei.hms:hianalytics:6.2.0.301'
|
||||||
|
|
BIN
app/key.p12.gpg
BIN
app/key.p12.gpg
Binary file not shown.
2316
app/schemas/io.github.wulkanowy.data.db.AppDatabase/40.json
Normal file
2316
app/schemas/io.github.wulkanowy.data.db.AppDatabase/40.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -45,6 +45,7 @@
|
||||||
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.splash.SplashActivity"
|
android:name=".ui.modules.splash.SplashActivity"
|
||||||
|
android:exported="true"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/WulkanowyTheme.SplashScreen"
|
android:theme="@style/WulkanowyTheme.SplashScreen"
|
||||||
tools:ignore="LockedOrientationActivity">
|
tools:ignore="LockedOrientationActivity">
|
||||||
|
@ -74,6 +75,7 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="true"
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -83,6 +85,7 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity"
|
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="true"
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -93,6 +96,22 @@
|
||||||
<service
|
<service
|
||||||
android:name=".services.widgets.TimetableWidgetService"
|
android:name=".services.widgets.TimetableWidgetService"
|
||||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||||
|
<service
|
||||||
|
android:name=".services.piggyback.VulcanNotificationListenerService"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.service.notification.NotificationListenerService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
<service
|
||||||
|
android:name=".services.messaging.AppMessagingService"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
|
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
|
||||||
|
@ -107,6 +126,7 @@
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
|
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/lucky_number_title">
|
android:label="@string/lucky_number_title">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
@ -119,11 +139,9 @@
|
||||||
<receiver android:name=".services.alarm.TimetableNotificationReceiver" />
|
<receiver android:name=".services.alarm.TimetableNotificationReceiver" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.work.impl.WorkManagerInitializer"
|
android:name="androidx.startup.InitializationProvider"
|
||||||
android:authorities="${applicationId}.workmanager-init"
|
android:authorities="${applicationId}.androidx-startup"
|
||||||
android:exported="false"
|
|
||||||
tools:node="remove" />
|
tools:node="remove" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
|
|
@ -8,8 +8,8 @@ import androidx.preference.PreferenceManager
|
||||||
import com.chuckerteam.chucker.api.ChuckerCollector
|
import com.chuckerteam.chucker.api.ChuckerCollector
|
||||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||||
import com.chuckerteam.chucker.api.RetentionManager
|
import com.chuckerteam.chucker.api.RetentionManager
|
||||||
import com.squareup.moshi.Moshi
|
|
||||||
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
@ -202,4 +202,8 @@ internal class RepositoryModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSchoolAnnouncementDao(database: AppDatabase) = database.schoolAnnouncementDao
|
fun provideSchoolAnnouncementDao(database: AppDatabase) = database.schoolAnnouncementDao
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideNotificationDao(database: AppDatabase) = database.notificationDao
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import io.github.wulkanowy.data.db.dao.AttendanceDao
|
||||||
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||||
import io.github.wulkanowy.data.db.dao.ConferenceDao
|
import io.github.wulkanowy.data.db.dao.ConferenceDao
|
||||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
|
||||||
import io.github.wulkanowy.data.db.dao.ExamDao
|
import io.github.wulkanowy.data.db.dao.ExamDao
|
||||||
import io.github.wulkanowy.data.db.dao.GradeDao
|
import io.github.wulkanowy.data.db.dao.GradeDao
|
||||||
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
|
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
|
||||||
|
@ -23,8 +22,10 @@ import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
||||||
import io.github.wulkanowy.data.db.dao.MessagesDao
|
import io.github.wulkanowy.data.db.dao.MessagesDao
|
||||||
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
||||||
import io.github.wulkanowy.data.db.dao.NoteDao
|
import io.github.wulkanowy.data.db.dao.NoteDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.NotificationDao
|
||||||
import io.github.wulkanowy.data.db.dao.RecipientDao
|
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||||
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||||
import io.github.wulkanowy.data.db.dao.SchoolDao
|
import io.github.wulkanowy.data.db.dao.SchoolDao
|
||||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||||
|
@ -38,7 +39,6 @@ import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||||
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
||||||
import io.github.wulkanowy.data.db.entities.Conference
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
|
||||||
import io.github.wulkanowy.data.db.entities.Exam
|
import io.github.wulkanowy.data.db.entities.Exam
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
|
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
|
||||||
|
@ -51,9 +51,11 @@ import io.github.wulkanowy.data.db.entities.Message
|
||||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
||||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||||
import io.github.wulkanowy.data.db.entities.Note
|
import io.github.wulkanowy.data.db.entities.Note
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
import io.github.wulkanowy.data.db.entities.Recipient
|
import io.github.wulkanowy.data.db.entities.Recipient
|
||||||
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
||||||
import io.github.wulkanowy.data.db.entities.School
|
import io.github.wulkanowy.data.db.entities.School
|
||||||
|
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||||
|
@ -95,6 +97,7 @@ import io.github.wulkanowy.data.db.migrations.Migration37
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration38
|
import io.github.wulkanowy.data.db.migrations.Migration38
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration39
|
import io.github.wulkanowy.data.db.migrations.Migration39
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration40
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||||
|
@ -134,6 +137,7 @@ import javax.inject.Singleton
|
||||||
StudentInfo::class,
|
StudentInfo::class,
|
||||||
TimetableHeader::class,
|
TimetableHeader::class,
|
||||||
SchoolAnnouncement::class,
|
SchoolAnnouncement::class,
|
||||||
|
Notification::class
|
||||||
],
|
],
|
||||||
version = AppDatabase.VERSION_SCHEMA,
|
version = AppDatabase.VERSION_SCHEMA,
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
|
@ -142,7 +146,7 @@ import javax.inject.Singleton
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VERSION_SCHEMA = 39
|
const val VERSION_SCHEMA = 40
|
||||||
|
|
||||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||||
Migration2(),
|
Migration2(),
|
||||||
|
@ -183,6 +187,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||||
Migration37(),
|
Migration37(),
|
||||||
Migration38(),
|
Migration38(),
|
||||||
Migration39(),
|
Migration39(),
|
||||||
|
Migration40()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
|
@ -252,4 +257,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||||
abstract val timetableHeaderDao: TimetableHeaderDao
|
abstract val timetableHeaderDao: TimetableHeaderDao
|
||||||
|
|
||||||
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
|
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
|
||||||
|
|
||||||
|
abstract val notificationDao: NotificationDao
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package io.github.wulkanowy.data.db.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Dao
|
||||||
|
interface NotificationDao : BaseDao<Notification> {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Notifications WHERE student_id = :studentId OR student_id = -1")
|
||||||
|
fun loadAll(studentId: Long): Flow<List<Notification>>
|
||||||
|
}
|
|
@ -14,33 +14,39 @@ import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Dao
|
@Dao
|
||||||
interface StudentDao {
|
abstract class StudentDao {
|
||||||
|
|
||||||
@Insert(onConflict = ABORT)
|
@Insert(onConflict = ABORT)
|
||||||
suspend fun insertAll(student: List<Student>): List<Long>
|
abstract suspend fun insertAll(student: List<Student>): List<Long>
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
suspend fun delete(student: Student)
|
abstract suspend fun delete(student: Student)
|
||||||
|
|
||||||
@Update(entity = Student::class)
|
@Update(entity = Student::class)
|
||||||
suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
||||||
|
|
||||||
@Query("SELECT * FROM Students WHERE is_current = 1")
|
@Query("SELECT * FROM Students WHERE is_current = 1")
|
||||||
suspend fun loadCurrent(): Student?
|
abstract suspend fun loadCurrent(): Student?
|
||||||
|
|
||||||
@Query("SELECT * FROM Students WHERE id = :id")
|
@Query("SELECT * FROM Students WHERE id = :id")
|
||||||
suspend fun loadById(id: Long): Student?
|
abstract suspend fun loadById(id: Long): Student?
|
||||||
|
|
||||||
@Query("SELECT * FROM Students")
|
@Query("SELECT * FROM Students")
|
||||||
suspend fun loadAll(): List<Student>
|
abstract suspend fun loadAll(): List<Student>
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query("SELECT * FROM Students")
|
@Query("SELECT * FROM Students")
|
||||||
suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters>
|
abstract suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters>
|
||||||
|
|
||||||
@Query("UPDATE Students SET is_current = 1 WHERE id = :id")
|
@Query("UPDATE Students SET is_current = 1 WHERE id = :id")
|
||||||
suspend fun updateCurrent(id: Long)
|
abstract suspend fun updateCurrent(id: Long)
|
||||||
|
|
||||||
@Query("UPDATE Students SET is_current = 0")
|
@Query("UPDATE Students SET is_current = 0")
|
||||||
suspend fun resetCurrent()
|
abstract suspend fun resetCurrent()
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open suspend fun switchCurrent(id: Long) {
|
||||||
|
resetCurrent()
|
||||||
|
updateCurrent(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package io.github.wulkanowy.data.db.entities
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
@Entity(tableName = "Notifications")
|
||||||
|
data class Notification(
|
||||||
|
|
||||||
|
@ColumnInfo(name = "student_id")
|
||||||
|
val studentId: Long,
|
||||||
|
|
||||||
|
val title: String,
|
||||||
|
|
||||||
|
val content: String,
|
||||||
|
|
||||||
|
val type: NotificationType,
|
||||||
|
|
||||||
|
val date: LocalDateTime,
|
||||||
|
|
||||||
|
val data: String? = null
|
||||||
|
) {
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
var id: Long = 0
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package io.github.wulkanowy.data.db.migrations
|
||||||
|
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
|
||||||
|
class Migration40 : Migration(39, 40) {
|
||||||
|
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS `Notifications` (
|
||||||
|
`student_id` INTEGER NOT NULL,
|
||||||
|
`title` TEXT NOT NULL,
|
||||||
|
`content` TEXT NOT NULL,
|
||||||
|
`type` TEXT NOT NULL,
|
||||||
|
`date` INTEGER NOT NULL,
|
||||||
|
`data` TEXT,
|
||||||
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ import androidx.annotation.StringRes
|
||||||
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
|
|
||||||
sealed interface Notification {
|
sealed interface NotificationData {
|
||||||
val type: NotificationType
|
val type: NotificationType
|
||||||
val startMenu: MainView.Section
|
val startMenu: MainView.Section
|
||||||
val icon: Int
|
val icon: Int
|
||||||
|
@ -14,7 +14,7 @@ sealed interface Notification {
|
||||||
val contentStringRes: Int
|
val contentStringRes: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MultipleNotifications(
|
data class MultipleNotificationsData(
|
||||||
override val type: NotificationType,
|
override val type: NotificationType,
|
||||||
override val startMenu: MainView.Section,
|
override val startMenu: MainView.Section,
|
||||||
@DrawableRes override val icon: Int,
|
@DrawableRes override val icon: Int,
|
||||||
|
@ -23,9 +23,9 @@ data class MultipleNotifications(
|
||||||
|
|
||||||
@PluralsRes val summaryStringRes: Int,
|
@PluralsRes val summaryStringRes: Int,
|
||||||
val lines: List<String>,
|
val lines: List<String>,
|
||||||
) : Notification
|
) : NotificationData
|
||||||
|
|
||||||
data class OneNotification(
|
data class OneNotificationData(
|
||||||
override val type: NotificationType,
|
override val type: NotificationType,
|
||||||
override val startMenu: MainView.Section,
|
override val startMenu: MainView.Section,
|
||||||
@DrawableRes override val icon: Int,
|
@DrawableRes override val icon: Int,
|
||||||
|
@ -33,4 +33,4 @@ data class OneNotification(
|
||||||
@StringRes override val contentStringRes: Int,
|
@StringRes override val contentStringRes: Int,
|
||||||
|
|
||||||
val contentValues: List<String>,
|
val contentValues: List<String>,
|
||||||
) : Notification
|
) : NotificationData
|
|
@ -32,10 +32,23 @@ class AttendanceRepository @Inject constructor(
|
||||||
|
|
||||||
private val cacheKey = "attendance"
|
private val cacheKey = "attendance"
|
||||||
|
|
||||||
fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
fun getAttendance(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
start: LocalDate,
|
||||||
|
end: LocalDate,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
shouldFetch = {
|
||||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) },
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
key = getRefreshKey(cacheKey, semester, start, end)
|
||||||
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
|
query = {
|
||||||
|
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
||||||
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
.getAttendance(start.monday, end.sunday, semester.semesterId)
|
.getAttendance(start.monday, end.sunday, semester.semesterId)
|
||||||
|
@ -50,12 +63,17 @@ class AttendanceRepository @Inject constructor(
|
||||||
filterResult = { it.filter { item -> item.date in start..end } }
|
filterResult = { it.filter { item -> item.date in start..end } }
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun excuseForAbsence(student: Student, semester: Semester, absenceList: List<Attendance>, reason: String? = null) {
|
suspend fun excuseForAbsence(
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).excuseForAbsence(absenceList.map { attendance ->
|
student: Student, semester: Semester,
|
||||||
|
absenceList: List<Attendance>, reason: String? = null
|
||||||
|
) {
|
||||||
|
val items = absenceList.map { attendance ->
|
||||||
Absent(
|
Absent(
|
||||||
date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)),
|
date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)),
|
||||||
timeId = attendance.timeId
|
timeId = attendance.timeId
|
||||||
)
|
)
|
||||||
}, reason)
|
}
|
||||||
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
.excuseForAbsence(items, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,12 @@ class AttendanceSummaryRepository @Inject constructor(
|
||||||
student: Student,
|
student: Student,
|
||||||
semester: Semester,
|
semester: Semester,
|
||||||
subjectId: Int,
|
subjectId: Int,
|
||||||
forceRefresh: Boolean
|
forceRefresh: Boolean,
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
it.isEmpty() || forceRefresh
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
|
|
|
@ -28,10 +28,28 @@ class CompletedLessonsRepository @Inject constructor(
|
||||||
|
|
||||||
private val cacheKey = "completed"
|
private val cacheKey = "completed"
|
||||||
|
|
||||||
fun getCompletedLessons(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
fun getCompletedLessons(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
start: LocalDate,
|
||||||
|
end: LocalDate,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
shouldFetch = {
|
||||||
query = { completedLessonsDb.loadAll(semester.studentId, semester.diaryId, start.monday, end.sunday) },
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
key = getRefreshKey(cacheKey, semester, start, end)
|
||||||
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
|
query = {
|
||||||
|
completedLessonsDb.loadAll(
|
||||||
|
studentId = semester.studentId,
|
||||||
|
diaryId = semester.diaryId,
|
||||||
|
from = start.monday,
|
||||||
|
end = end.sunday
|
||||||
|
)
|
||||||
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
.getCompletedLessons(start.monday, end.sunday)
|
.getCompletedLessons(start.monday, end.sunday)
|
||||||
|
|
|
@ -35,12 +35,12 @@ class ConferenceRepository @Inject constructor(
|
||||||
semester: Semester,
|
semester: Semester,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
notify: Boolean = false,
|
notify: Boolean = false,
|
||||||
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)
|
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
it.isEmpty() || forceRefresh
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
|
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
|
||||||
|
|
|
@ -36,14 +36,14 @@ class ExamRepository @Inject constructor(
|
||||||
start: LocalDate,
|
start: LocalDate,
|
||||||
end: LocalDate,
|
end: LocalDate,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
notify: Boolean = false
|
notify: Boolean = false,
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
key = getRefreshKey(cacheKey, semester, start, end)
|
key = getRefreshKey(cacheKey, semester, start, end)
|
||||||
)
|
)
|
||||||
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
examDb.loadAll(
|
examDb.loadAll(
|
||||||
|
|
|
@ -37,13 +37,12 @@ class GradeRepository @Inject constructor(
|
||||||
student: Student,
|
student: Student,
|
||||||
semester: Semester,
|
semester: Semester,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
notify: Boolean = false
|
notify: Boolean = false,
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { (details, summaries) ->
|
shouldFetch = { (details, summaries) ->
|
||||||
val isShouldBeRefreshed =
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired
|
||||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed
|
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
||||||
|
@ -71,8 +70,8 @@ class GradeRepository @Inject constructor(
|
||||||
newDetails: List<Grade>,
|
newDetails: List<Grade>,
|
||||||
notify: Boolean
|
notify: Boolean
|
||||||
) {
|
) {
|
||||||
val notifyBreakDate =
|
val notifyBreakDate = oldGrades.maxByOrNull {it.date }
|
||||||
oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate()
|
?.date ?: student.registrationDate.toLocalDate()
|
||||||
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
||||||
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
||||||
if (it.date >= notifyBreakDate) it.apply {
|
if (it.date >= notifyBreakDate) it.apply {
|
||||||
|
@ -89,8 +88,7 @@ class GradeRepository @Inject constructor(
|
||||||
) {
|
) {
|
||||||
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
||||||
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
||||||
val oldSummary =
|
val oldSummary = oldSummaries.find { old -> old.subject == summary.subject }
|
||||||
oldSummaries.find { oldSummary -> oldSummary.subject == summary.subject }
|
|
||||||
summary.isPredictedGradeNotified = when {
|
summary.isPredictedGradeNotified = when {
|
||||||
summary.predictedGrade.isEmpty() -> true
|
summary.predictedGrade.isEmpty() -> true
|
||||||
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
||||||
|
|
|
@ -39,9 +39,19 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
private val semesterCacheKey = "grade_stats_semester"
|
private val semesterCacheKey = "grade_stats_semester"
|
||||||
private val pointsCacheKey = "grade_stats_points"
|
private val pointsCacheKey = "grade_stats_points"
|
||||||
|
|
||||||
fun getGradesPartialStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
fun getGradesPartialStatistics(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
subjectName: String,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = partialMutex,
|
mutex = partialMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(partialCacheKey, semester)) },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
key = getRefreshKey(partialCacheKey, semester)
|
||||||
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
@ -76,9 +86,19 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getGradesSemesterStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
fun getGradesSemesterStatistics(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
subjectName: String,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = semesterMutex,
|
mutex = semesterMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(semesterCacheKey, semester)) },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
key = getRefreshKey(semesterCacheKey, semester)
|
||||||
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
@ -94,13 +114,15 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
val itemsWithAverage = items.map { item ->
|
val itemsWithAverage = items.map { item ->
|
||||||
item.copy().apply {
|
item.copy().apply {
|
||||||
val denominator = item.amounts.sum()
|
val denominator = item.amounts.sum()
|
||||||
average = if (denominator == 0) "" else (item.amounts.mapIndexed { gradeValue, amount ->
|
average = if (denominator == 0) "" else {
|
||||||
|
(item.amounts.mapIndexed { gradeValue, amount ->
|
||||||
(gradeValue + 1) * amount
|
(gradeValue + 1) * amount
|
||||||
}.sum().toDouble() / denominator).let {
|
}.sum().toDouble() / denominator).let {
|
||||||
"%.2f".format(Locale.FRANCE, it)
|
"%.2f".format(Locale.FRANCE, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
when (subjectName) {
|
when (subjectName) {
|
||||||
"Wszystkie" -> (itemsWithAverage.reversed() + GradeSemesterStatistics(
|
"Wszystkie" -> (itemsWithAverage.reversed() + GradeSemesterStatistics(
|
||||||
studentId = semester.studentId,
|
studentId = semester.studentId,
|
||||||
|
@ -109,7 +131,9 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
|
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
|
||||||
studentGrade = 0
|
studentGrade = 0
|
||||||
).apply {
|
).apply {
|
||||||
average = itemsWithAverage.mapNotNull { it.average.replace(",", ".").toDoubleOrNull() }.average().let {
|
average = itemsWithAverage.mapNotNull {
|
||||||
|
it.average.replace(",", ".").toDoubleOrNull()
|
||||||
|
}.average().let {
|
||||||
"%.2f".format(Locale.FRANCE, it)
|
"%.2f".format(Locale.FRANCE, it)
|
||||||
}
|
}
|
||||||
}).reversed()
|
}).reversed()
|
||||||
|
@ -118,9 +142,17 @@ class GradeStatisticsRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
fun getGradesPointsStatistics(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
subjectName: String,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = pointsMutex,
|
mutex = pointsMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(pointsCacheKey, semester)) },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester))
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
|
|
@ -30,16 +30,19 @@ class HomeworkRepository @Inject constructor(
|
||||||
private val cacheKey = "homework"
|
private val cacheKey = "homework"
|
||||||
|
|
||||||
fun getHomework(
|
fun getHomework(
|
||||||
student: Student, semester: Semester,
|
student: Student,
|
||||||
start: LocalDate, end: LocalDate,
|
semester: Semester,
|
||||||
forceRefresh: Boolean, notify: Boolean = false
|
start: LocalDate,
|
||||||
|
end: LocalDate,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
notify: Boolean = false,
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
key = getRefreshKey(cacheKey, semester, start, end)
|
key = getRefreshKey(cacheKey, semester, start, end)
|
||||||
)
|
)
|
||||||
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
homeworkDb.loadAll(
|
homeworkDb.loadAll(
|
||||||
|
|
|
@ -23,11 +23,17 @@ class LuckyNumberRepository @Inject constructor(
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getLuckyNumber(student: Student, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
fun getLuckyNumber(
|
||||||
|
student: Student,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
notify: Boolean = false,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it == null || forceRefresh },
|
shouldFetch = { it == null || forceRefresh },
|
||||||
query = { luckyNumberDb.load(student.studentId, now()) },
|
query = { luckyNumberDb.load(student.studentId, now()) },
|
||||||
fetch = { sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student) },
|
fetch = {
|
||||||
|
sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student)
|
||||||
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
if (new != old) {
|
if (new != old) {
|
||||||
old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
||||||
|
@ -41,9 +47,11 @@ class LuckyNumberRepository @Inject constructor(
|
||||||
fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) =
|
fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) =
|
||||||
luckyNumberDb.getAll(student.studentId, start, end)
|
luckyNumberDb.getAll(student.studentId, start, end)
|
||||||
|
|
||||||
suspend fun getNotNotifiedLuckyNumber(student: Student) = luckyNumberDb.load(student.studentId, now()).map {
|
suspend fun getNotNotifiedLuckyNumber(student: Student) =
|
||||||
|
luckyNumberDb.load(student.studentId, now()).map {
|
||||||
if (it?.isNotified == false) it else null
|
if (it?.isNotified == false) it else null
|
||||||
}.first()
|
}.first()
|
||||||
|
|
||||||
suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) = luckyNumberDb.updateAll(listOfNotNull(luckyNumber))
|
suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) =
|
||||||
|
luckyNumberDb.updateAll(listOfNotNull(luckyNumber))
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,14 +51,18 @@ class MessageRepository @Inject constructor(
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun getMessages(
|
fun getMessages(
|
||||||
student: Student, semester: Semester,
|
student: Student,
|
||||||
folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false
|
semester: Semester,
|
||||||
|
folder: MessageFolder,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
notify: Boolean = false,
|
||||||
): Flow<Resource<List<Message>>> = networkBoundResource(
|
): Flow<Resource<List<Message>>> = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
getRefreshKey(cacheKey, student, folder)
|
key = getRefreshKey(cacheKey, student, folder)
|
||||||
)
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
||||||
fetch = {
|
fetch = {
|
||||||
|
@ -77,7 +81,8 @@ class MessageRepository @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getMessagesWithReadByChange(
|
private fun getMessagesWithReadByChange(
|
||||||
old: List<Message>, new: List<Message>,
|
old: List<Message>,
|
||||||
|
new: List<Message>,
|
||||||
setNotified: Boolean
|
setNotified: Boolean
|
||||||
): List<Message> {
|
): List<Message> {
|
||||||
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
|
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
|
||||||
|
@ -96,7 +101,9 @@ class MessageRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMessage(
|
fun getMessage(
|
||||||
student: Student, message: Message, markAsRead: Boolean = false
|
student: Student,
|
||||||
|
message: Message,
|
||||||
|
markAsRead: Boolean = false,
|
||||||
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
|
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
checkNotNull(it, { "This message no longer exist!" })
|
checkNotNull(it, { "This message no longer exist!" })
|
||||||
|
@ -135,8 +142,10 @@ class MessageRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendMessage(
|
suspend fun sendMessage(
|
||||||
student: Student, subject: String, content: String,
|
student: Student,
|
||||||
recipients: List<Recipient>
|
subject: String,
|
||||||
|
content: String,
|
||||||
|
recipients: List<Recipient>,
|
||||||
): SentMessage = sdk.init(student).sendMessage(
|
): SentMessage = sdk.init(student).sendMessage(
|
||||||
subject = subject,
|
subject = subject,
|
||||||
content = content,
|
content = content,
|
||||||
|
|
|
@ -28,9 +28,16 @@ class MobileDeviceRepository @Inject constructor(
|
||||||
|
|
||||||
private val cacheKey = "devices"
|
private val cacheKey = "devices"
|
||||||
|
|
||||||
fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
fun getDevices(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student)) },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
|
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
|
|
@ -12,7 +12,6 @@ import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -28,9 +27,19 @@ class NoteRepository @Inject constructor(
|
||||||
|
|
||||||
private val cacheKey = "note"
|
private val cacheKey = "note"
|
||||||
|
|
||||||
fun getNotes(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
fun getNotes(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
notify: Boolean = false,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
getRefreshKey(cacheKey, semester)
|
||||||
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { noteDb.loadAll(student.studentId) },
|
query = { noteDb.loadAll(student.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.dao.NotificationDao
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class NotificationRepository @Inject constructor(
|
||||||
|
private val notificationDao: NotificationDao,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun getNotifications(studentId: Long) = notificationDao.loadAll(studentId)
|
||||||
|
|
||||||
|
suspend fun saveNotification(notification: Notification) =
|
||||||
|
notificationDao.insertAll(listOf(notification))
|
||||||
|
}
|
|
@ -14,7 +14,6 @@ import io.github.wulkanowy.sdk.toLocalDate
|
||||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
|
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
|
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
|
||||||
import io.github.wulkanowy.utils.toTimestamp
|
|
||||||
import io.github.wulkanowy.utils.toLocalDateTime
|
import io.github.wulkanowy.utils.toLocalDateTime
|
||||||
import io.github.wulkanowy.utils.toTimestamp
|
import io.github.wulkanowy.utils.toTimestamp
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
@ -108,6 +107,22 @@ class PreferencesRepository @Inject constructor(
|
||||||
R.bool.pref_default_notification_upcoming_lessons_enable
|
R.bool.pref_default_notification_upcoming_lessons_enable
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val isUpcomingLessonsNotificationsPersistentKey =
|
||||||
|
context.getString(R.string.pref_key_notifications_upcoming_lessons_persistent)
|
||||||
|
val isUpcomingLessonsNotificationsPersistent: Boolean
|
||||||
|
get() = getBoolean(
|
||||||
|
isUpcomingLessonsNotificationsPersistentKey,
|
||||||
|
R.bool.pref_default_notification_upcoming_lessons_persistent
|
||||||
|
)
|
||||||
|
|
||||||
|
val isNotificationPiggybackEnabledKey =
|
||||||
|
context.getString(R.string.pref_key_notifications_piggyback)
|
||||||
|
val isNotificationPiggybackEnabled: Boolean
|
||||||
|
get() = getBoolean(
|
||||||
|
R.string.pref_key_notifications_piggyback,
|
||||||
|
R.bool.pref_default_notification_piggyback
|
||||||
|
)
|
||||||
|
|
||||||
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
|
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
|
||||||
val isDebugNotificationEnable: Boolean
|
val isDebugNotificationEnable: Boolean
|
||||||
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
||||||
|
@ -176,10 +191,8 @@ class PreferencesRepository @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
var lasSyncDate: LocalDateTime
|
var lasSyncDate: LocalDateTime
|
||||||
get() = getLong(
|
get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date)
|
||||||
R.string.pref_key_last_sync_date,
|
.toLocalDateTime()
|
||||||
R.string.pref_default_last_sync_date
|
|
||||||
).toLocalDateTime()
|
|
||||||
set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply()
|
set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply()
|
||||||
|
|
||||||
var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
|
var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
|
||||||
|
@ -230,8 +243,10 @@ class PreferencesRepository @Inject constructor(
|
||||||
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
|
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
|
||||||
|
|
||||||
var inAppReviewDate: LocalDate?
|
var inAppReviewDate: LocalDate?
|
||||||
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }?.toLocalDate()
|
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }
|
||||||
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp()).apply()
|
?.toLocalDate()
|
||||||
|
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp())
|
||||||
|
.apply()
|
||||||
|
|
||||||
var isAppReviewDone: Boolean
|
var isAppReviewDone: Boolean
|
||||||
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)
|
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)
|
||||||
|
|
|
@ -7,6 +7,8 @@ import io.github.wulkanowy.data.db.entities.ReportingUnit
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -15,26 +17,34 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class RecipientRepository @Inject constructor(
|
class RecipientRepository @Inject constructor(
|
||||||
private val recipientDb: RecipientDao,
|
private val recipientDb: RecipientDao,
|
||||||
private val sdk: Sdk
|
private val sdk: Sdk,
|
||||||
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
private val cacheKey = "recipient"
|
||||||
|
|
||||||
suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) {
|
suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) {
|
||||||
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
|
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
|
||||||
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||||
|
|
||||||
recipientDb.deleteAll(old uniqueSubtract new)
|
recipientDb.deleteAll(old uniqueSubtract new)
|
||||||
recipientDb.insertAll(new uniqueSubtract old)
|
recipientDb.insertAll(new uniqueSubtract old)
|
||||||
|
|
||||||
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> {
|
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> {
|
||||||
return recipientDb.loadAll(unit.studentId, unit.unitId, role).ifEmpty {
|
val cached = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||||
refreshRecipients(student, unit, role)
|
|
||||||
|
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||||
|
return if (cached.isEmpty() || isExpired) {
|
||||||
|
refreshRecipients(student, unit, role)
|
||||||
recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||||
}
|
} else cached
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> {
|
suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> {
|
||||||
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId).mapToEntities(student.studentId)
|
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId)
|
||||||
|
.mapToEntities(student.studentId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ class RecoverRepository @Inject constructor(private val sdk: Sdk) {
|
||||||
return sdk.getPasswordResetCaptchaCode(host, symbol)
|
return sdk.getPasswordResetCaptchaCode(host, symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendRecoverRequest(url: String, symbol: String, email: String, reCaptchaResponse: String): String {
|
suspend fun sendRecoverRequest(
|
||||||
return sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
url: String, symbol: String, email: String, reCaptchaResponse: String
|
||||||
}
|
): String = sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
@ -12,7 +11,6 @@ import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -30,17 +28,15 @@ class SchoolAnnouncementRepository @Inject constructor(
|
||||||
|
|
||||||
fun getSchoolAnnouncements(
|
fun getSchoolAnnouncements(
|
||||||
student: Student,
|
student: Student,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean, notify: Boolean = false
|
||||||
notify: Boolean = false
|
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
it.isEmpty() || forceRefresh
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student))
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
schoolAnnouncementDb.loadAll(
|
schoolAnnouncementDb.loadAll(student.studentId)
|
||||||
student.studentId)
|
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
|
@ -57,9 +53,11 @@ class SchoolAnnouncementRepository @Inject constructor(
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
||||||
return schoolAnnouncementDb.loadAll(student.studentId)
|
return schoolAnnouncementDb.loadAll(student.studentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) = schoolAnnouncementDb.updateAll(schoolAnnouncement)
|
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =
|
||||||
|
schoolAnnouncementDb.updateAll(schoolAnnouncement)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntity
|
import io.github.wulkanowy.data.mappers.mapToEntity
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
@ -14,15 +16,26 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class SchoolRepository @Inject constructor(
|
class SchoolRepository @Inject constructor(
|
||||||
private val schoolDb: SchoolDao,
|
private val schoolDb: SchoolDao,
|
||||||
private val sdk: Sdk
|
private val sdk: Sdk,
|
||||||
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
private val cacheKey = "school_info"
|
||||||
networkBoundResource(
|
|
||||||
|
fun getSchoolInfo(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it == null || forceRefresh },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
key = getRefreshKey(cacheKey, student)
|
||||||
|
)
|
||||||
|
it == null || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
|
||||||
|
@ -37,6 +50,7 @@ class SchoolRepository @Inject constructor(
|
||||||
} else if (old == null) {
|
} else if (old == null) {
|
||||||
schoolDb.insertAll(listOf(new))
|
schoolDb.insertAll(listOf(new))
|
||||||
}
|
}
|
||||||
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,11 @@ class StudentInfoRepository @Inject constructor(
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getStudentInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
fun getStudentInfo(
|
||||||
networkBoundResource(
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it == null || forceRefresh },
|
shouldFetch = { it == null || forceRefresh },
|
||||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.room.withTransaction
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import io.github.wulkanowy.data.db.AppDatabase
|
||||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
@ -25,7 +27,8 @@ class StudentRepository @Inject constructor(
|
||||||
private val studentDb: StudentDao,
|
private val studentDb: StudentDao,
|
||||||
private val semesterDb: SemesterDao,
|
private val semesterDb: SemesterDao,
|
||||||
private val sdk: Sdk,
|
private val sdk: Sdk,
|
||||||
private val appInfo: AppInfo
|
private val appInfo: AppInfo,
|
||||||
|
private val appDatabase: AppDatabase
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
|
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
|
||||||
|
@ -92,7 +95,7 @@ class StudentRepository @Inject constructor(
|
||||||
return student
|
return student
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>): List<Long> {
|
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||||
val semesters = studentsWithSemesters.flatMap { it.semesters }
|
val semesters = studentsWithSemesters.flatMap { it.semesters }
|
||||||
val students = studentsWithSemesters.map { it.student }
|
val students = studentsWithSemesters.map { it.student }
|
||||||
.map {
|
.map {
|
||||||
|
@ -104,16 +107,21 @@ class StudentRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.mapIndexed { index, student ->
|
||||||
|
if (index == 0) {
|
||||||
|
student.copy(isCurrent = true).apply { avatarColor = student.avatarColor }
|
||||||
|
} else student
|
||||||
|
}
|
||||||
|
|
||||||
|
appDatabase.withTransaction {
|
||||||
|
studentDb.resetCurrent()
|
||||||
semesterDb.insertSemesters(semesters)
|
semesterDb.insertSemesters(semesters)
|
||||||
return studentDb.insertAll(students)
|
studentDb.insertAll(students)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) {
|
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) {
|
||||||
with(studentDb) {
|
studentDb.switchCurrent(studentWithSemesters.student.id)
|
||||||
resetCurrent()
|
|
||||||
updateCurrent(studentWithSemesters.student.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun logoutStudent(student: Student) = studentDb.delete(student)
|
suspend fun logoutStudent(student: Student) = studentDb.delete(student)
|
||||||
|
|
|
@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
|
@ -15,14 +17,24 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class SubjectRepository @Inject constructor(
|
class SubjectRepository @Inject constructor(
|
||||||
private val subjectDao: SubjectDao,
|
private val subjectDao: SubjectDao,
|
||||||
private val sdk: Sdk
|
private val sdk: Sdk,
|
||||||
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getSubjects(student: Student, semester: Semester, forceRefresh: Boolean = false) = networkBoundResource(
|
private val cacheKey = "subjects"
|
||||||
|
|
||||||
|
fun getSubjects(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
forceRefresh: Boolean = false,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
@ -31,6 +43,8 @@ class SubjectRepository @Inject constructor(
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
subjectDao.deleteAll(old uniqueSubtract new)
|
subjectDao.deleteAll(old uniqueSubtract new)
|
||||||
subjectDao.insertAll(new uniqueSubtract old)
|
subjectDao.insertAll(new uniqueSubtract old)
|
||||||
|
|
||||||
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
|
@ -15,14 +17,24 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class TeacherRepository @Inject constructor(
|
class TeacherRepository @Inject constructor(
|
||||||
private val teacherDb: TeacherDao,
|
private val teacherDb: TeacherDao,
|
||||||
private val sdk: Sdk
|
private val sdk: Sdk,
|
||||||
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getTeachers(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
private val cacheKey = "teachers"
|
||||||
|
|
||||||
|
fun getTeachers(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
@ -32,6 +44,8 @@ class TeacherRepository @Inject constructor(
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
teacherDb.deleteAll(old uniqueSubtract new)
|
teacherDb.deleteAll(old uniqueSubtract new)
|
||||||
teacherDb.insertAll(new uniqueSubtract old)
|
teacherDb.insertAll(new uniqueSubtract old)
|
||||||
|
|
||||||
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,18 +41,22 @@ class TimetableRepository @Inject constructor(
|
||||||
private val cacheKey = "timetable"
|
private val cacheKey = "timetable"
|
||||||
|
|
||||||
fun getTimetable(
|
fun getTimetable(
|
||||||
student: Student, semester: Semester, start: LocalDate, end: LocalDate,
|
student: Student,
|
||||||
forceRefresh: Boolean, refreshAdditional: Boolean = false
|
semester: Semester,
|
||||||
|
start: LocalDate,
|
||||||
|
end: LocalDate,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
refreshAdditional: Boolean = false,
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { (timetable, additional, headers) ->
|
shouldFetch = { (timetable, additional, headers) ->
|
||||||
val refreshKey = getRefreshKey(cacheKey, semester, start, end)
|
val refreshKey = getRefreshKey(cacheKey, semester, start, end)
|
||||||
val isShouldRefresh = refreshHelper.isShouldBeRefreshed(refreshKey)
|
val isExpired = refreshHelper.shouldBeRefreshed(refreshKey)
|
||||||
val isRefreshAdditional = additional.isEmpty() && refreshAdditional
|
val isRefreshAdditional = additional.isEmpty() && refreshAdditional
|
||||||
|
|
||||||
val isNoData = timetable.isEmpty() || isRefreshAdditional || headers.isEmpty()
|
val isNoData = timetable.isEmpty() || isRefreshAdditional || headers.isEmpty()
|
||||||
|
|
||||||
isNoData || forceRefresh || isShouldRefresh
|
isNoData || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
||||||
fetch = {
|
fetch = {
|
||||||
|
@ -79,8 +83,10 @@ class TimetableRepository @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getFullTimetableFromDatabase(
|
private fun getFullTimetableFromDatabase(
|
||||||
student: Student, semester: Semester,
|
student: Student,
|
||||||
start: LocalDate, end: LocalDate
|
semester: Semester,
|
||||||
|
start: LocalDate,
|
||||||
|
end: LocalDate,
|
||||||
): Flow<TimetableFull> {
|
): Flow<TimetableFull> {
|
||||||
val timetableFlow = timetableDb.loadAll(
|
val timetableFlow = timetableDb.loadAll(
|
||||||
diaryId = semester.diaryId,
|
diaryId = semester.diaryId,
|
||||||
|
@ -113,20 +119,11 @@ class TimetableRepository @Inject constructor(
|
||||||
|
|
||||||
private suspend fun refreshTimetable(
|
private suspend fun refreshTimetable(
|
||||||
student: Student,
|
student: Student,
|
||||||
lessonsOld: List<Timetable>, lessonsNew: List<Timetable>
|
lessonsOld: List<Timetable>,
|
||||||
|
lessonsNew: List<Timetable>,
|
||||||
) {
|
) {
|
||||||
val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew
|
val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew
|
||||||
val lessonsToAdd = (lessonsNew uniqueSubtract lessonsOld).map { new ->
|
val lessonsToAdd = lessonsNew uniqueSubtract lessonsOld
|
||||||
val matchingOld = lessonsOld.singleOrNull { new.start == it.start }
|
|
||||||
if (matchingOld != null) {
|
|
||||||
val useOldTeacher = new.teacher.isEmpty() && !new.changes && !matchingOld.changes
|
|
||||||
new.copy(
|
|
||||||
room = if (new.room.isEmpty()) matchingOld.room else new.room,
|
|
||||||
teacher = if (useOldTeacher) matchingOld.teacher
|
|
||||||
else new.teacher
|
|
||||||
)
|
|
||||||
} else new
|
|
||||||
}
|
|
||||||
|
|
||||||
timetableDb.deleteAll(lessonsToRemove)
|
timetableDb.deleteAll(lessonsToRemove)
|
||||||
timetableDb.insertAll(lessonsToAdd)
|
timetableDb.insertAll(lessonsToAdd)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import androidx.core.app.NotificationManagerCompat
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.Status
|
import io.github.wulkanowy.data.Status
|
||||||
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.services.HiltBroadcastReceiver
|
import io.github.wulkanowy.services.HiltBroadcastReceiver
|
||||||
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
|
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
|
||||||
|
@ -32,6 +33,9 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var studentRepository: StudentRepository
|
lateinit var studentRepository: StudentRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var preferencesRepository: PreferencesRepository
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val NOTIFICATION_TYPE_CURRENT = 1
|
const val NOTIFICATION_TYPE_CURRENT = 1
|
||||||
const val NOTIFICATION_TYPE_UPCOMING = 2
|
const val NOTIFICATION_TYPE_UPCOMING = 2
|
||||||
|
@ -68,6 +72,7 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||||
private fun prepareNotification(context: Context, intent: Intent) {
|
private fun prepareNotification(context: Context, intent: Intent) {
|
||||||
val type = intent.getIntExtra(LESSON_TYPE, 0)
|
val type = intent.getIntExtra(LESSON_TYPE, 0)
|
||||||
val notificationId = intent.getIntExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
val notificationId = intent.getIntExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||||
|
val isPersistent = preferencesRepository.isUpcomingLessonsNotificationsPersistent
|
||||||
|
|
||||||
if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) {
|
if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) {
|
||||||
return NotificationManagerCompat.from(context).cancel(notificationId)
|
return NotificationManagerCompat.from(context).cancel(notificationId)
|
||||||
|
@ -87,20 +92,38 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||||
|
|
||||||
Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId")
|
Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId")
|
||||||
|
|
||||||
showNotification(context, notificationId, studentName,
|
showNotification(context, notificationId, isPersistent, studentName,
|
||||||
if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start,
|
if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start,
|
||||||
context.getString(if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next, "($room) $subject".removePrefix("()")),
|
context.getString(
|
||||||
nextSubject?.let { context.getString(R.string.timetable_later, "($nextRoom) $nextSubject".removePrefix("()")) }
|
if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next,
|
||||||
|
"($room) $subject".removePrefix("()")
|
||||||
|
),
|
||||||
|
nextSubject?.let {
|
||||||
|
context.getString(
|
||||||
|
R.string.timetable_later,
|
||||||
|
"($nextRoom) $nextSubject".removePrefix("()")
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showNotification(context: Context, notificationId: Int, studentName: String?, countDown: Long, timeout: Long, title: String, next: String?) {
|
private fun showNotification(
|
||||||
NotificationManagerCompat.from(context).notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID)
|
context: Context,
|
||||||
|
notificationId: Int,
|
||||||
|
isPersistent: Boolean,
|
||||||
|
studentName: String?,
|
||||||
|
countDown: Long,
|
||||||
|
timeout: Long,
|
||||||
|
title: String,
|
||||||
|
next: String?
|
||||||
|
) {
|
||||||
|
NotificationManagerCompat.from(context)
|
||||||
|
.notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setContentText(next)
|
.setContentText(next)
|
||||||
.setAutoCancel(false)
|
.setAutoCancel(false)
|
||||||
.setOngoing(true)
|
|
||||||
.setWhen(countDown)
|
.setWhen(countDown)
|
||||||
|
.setOngoing(isPersistent)
|
||||||
.apply {
|
.apply {
|
||||||
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
||||||
}
|
}
|
||||||
|
@ -111,8 +134,14 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||||
it.setSummaryText(studentName)
|
it.setSummaryText(studentName)
|
||||||
it.addLine(next)
|
it.addLine(next)
|
||||||
})
|
})
|
||||||
.setContentIntent(PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id,
|
.setContentIntent(
|
||||||
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT))
|
PendingIntent.getActivity(
|
||||||
|
context,
|
||||||
|
MainView.Section.TIMETABLE.id,
|
||||||
|
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true),
|
||||||
|
FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import io.github.wulkanowy.utils.nickOrName
|
||||||
import io.github.wulkanowy.utils.toTimestamp
|
import io.github.wulkanowy.utils.toTimestamp
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.LocalDateTime.now
|
import java.time.LocalDateTime.now
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -57,10 +58,13 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||||
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
|
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
|
||||||
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
|
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
|
||||||
cancelScheduledTo(
|
cancelScheduledTo(
|
||||||
upcomingTime..lesson.start,
|
range = upcomingTime..lesson.start,
|
||||||
getRequestCode(upcomingTime, studentId)
|
requestCode = getRequestCode(upcomingTime, studentId)
|
||||||
|
)
|
||||||
|
cancelScheduledTo(
|
||||||
|
range = lesson.start..lesson.end,
|
||||||
|
requestCode = getRequestCode(lesson.start, 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")
|
||||||
}
|
}
|
||||||
|
@ -82,6 +86,11 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||||
return cancelScheduled(lessons, student)
|
return cancelScheduled(lessons, student)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lessons.firstOrNull()?.date?.isAfter(LocalDate.now().plusDays(2)) == true) {
|
||||||
|
Timber.d("Timetable notification scheduling skipped - lessons are too far")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
withContext(dispatchersProvider.backgroundThread) {
|
withContext(dispatchersProvider.backgroundThread) {
|
||||||
lessons.groupBy { it.date }
|
lessons.groupBy { it.date }
|
||||||
.map { it.value.sortedBy { lesson -> lesson.start } }
|
.map { it.value.sortedBy { lesson -> lesson.start } }
|
||||||
|
@ -96,26 +105,26 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||||
|
|
||||||
if (lesson.start > now()) {
|
if (lesson.start > now()) {
|
||||||
scheduleBroadcast(
|
scheduleBroadcast(
|
||||||
intent,
|
intent = intent,
|
||||||
student.studentId,
|
studentId = student.studentId,
|
||||||
NOTIFICATION_TYPE_UPCOMING,
|
notificationType = NOTIFICATION_TYPE_UPCOMING,
|
||||||
getUpcomingLessonTime(index, active, lesson)
|
time = getUpcomingLessonTime(index, active, lesson)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lesson.end > now()) {
|
if (lesson.end > now()) {
|
||||||
scheduleBroadcast(
|
scheduleBroadcast(
|
||||||
intent,
|
intent = intent,
|
||||||
student.studentId,
|
studentId = student.studentId,
|
||||||
NOTIFICATION_TYPE_CURRENT,
|
notificationType = NOTIFICATION_TYPE_CURRENT,
|
||||||
lesson.start
|
time = lesson.start
|
||||||
)
|
)
|
||||||
if (active.lastIndex == index) {
|
if (active.lastIndex == index) {
|
||||||
scheduleBroadcast(
|
scheduleBroadcast(
|
||||||
intent,
|
intent = intent,
|
||||||
student.studentId,
|
studentId = student.studentId,
|
||||||
NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
notificationType = NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
||||||
lesson.end
|
time = lesson.end
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,6 +152,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||||
notificationType: Int,
|
notificationType: Int,
|
||||||
time: LocalDateTime
|
time: LocalDateTime
|
||||||
) {
|
) {
|
||||||
|
try {
|
||||||
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
||||||
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||||
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||||
|
@ -155,5 +165,8 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||||
intent.getStringExtra(LESSON_TITLE)
|
intent.getStringExtra(LESSON_TITLE)
|
||||||
}, start: $time, student: $studentId"
|
}, start: $time, student: $studentId"
|
||||||
)
|
)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package io.github.wulkanowy.services.piggyback
|
||||||
|
|
||||||
|
import android.service.notification.NotificationListenerService
|
||||||
|
import android.service.notification.StatusBarNotification
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
|
import io.github.wulkanowy.services.sync.SyncManager
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class VulcanNotificationListenerService : NotificationListenerService() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var syncManager: SyncManager
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var preferenceRepository: PreferencesRepository
|
||||||
|
|
||||||
|
override fun onNotificationPosted(statusBarNotification: StatusBarNotification?) {
|
||||||
|
if (statusBarNotification?.packageName == "pl.edu.vulcan.hebe" && preferenceRepository.isNotificationPiggybackEnabled) {
|
||||||
|
syncManager.startOneTimeSyncWorker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -57,14 +57,20 @@ class SyncManager @Inject constructor(
|
||||||
|
|
||||||
fun startPeriodicSyncWorker(restart: Boolean = false) {
|
fun startPeriodicSyncWorker(restart: Boolean = false) {
|
||||||
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
|
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
|
||||||
workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
val serviceInterval = preferencesRepository.servicesInterval
|
||||||
PeriodicWorkRequestBuilder<SyncWorker>(preferencesRepository.servicesInterval, MINUTES)
|
|
||||||
|
workManager.enqueueUniquePeriodicWork(
|
||||||
|
SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
||||||
|
PeriodicWorkRequestBuilder<SyncWorker>(serviceInterval, MINUTES)
|
||||||
.setInitialDelay(10, MINUTES)
|
.setInitialDelay(10, MINUTES)
|
||||||
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
|
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
|
||||||
.setConstraints(Constraints.Builder()
|
.setConstraints(
|
||||||
|
Constraints.Builder()
|
||||||
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
||||||
.build())
|
.build()
|
||||||
.build())
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +83,11 @@ class SyncManager @Inject constructor(
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
workManager.enqueueUniqueWork("${SyncWorker::class.java.simpleName}_one_time", ExistingWorkPolicy.REPLACE, work)
|
workManager.enqueueUniqueWork(
|
||||||
|
"${SyncWorker::class.java.simpleName}_one_time",
|
||||||
|
ExistingWorkPolicy.REPLACE,
|
||||||
|
work
|
||||||
|
)
|
||||||
|
|
||||||
return workManager.getWorkInfoByIdLiveData(work.id).asFlow()
|
return workManager.getWorkInfoByIdLiveData(work.id).asFlow()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
|
import io.github.wulkanowy.data.pojos.NotificationData
|
||||||
|
import io.github.wulkanowy.data.pojos.OneNotificationData
|
||||||
|
import io.github.wulkanowy.data.repositories.NotificationRepository
|
||||||
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
|
import io.github.wulkanowy.utils.getCompatBitmap
|
||||||
|
import io.github.wulkanowy.utils.getCompatColor
|
||||||
|
import io.github.wulkanowy.utils.nickOrName
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
class AppNotificationManager @Inject constructor(
|
||||||
|
private val notificationManager: NotificationManagerCompat,
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
private val appInfo: AppInfo,
|
||||||
|
private val notificationRepository: NotificationRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun sendNotification(notificationData: NotificationData, student: Student) =
|
||||||
|
when (notificationData) {
|
||||||
|
is OneNotificationData -> sendOneNotification(notificationData, student)
|
||||||
|
is MultipleNotificationsData -> sendMultipleNotifications(notificationData, student)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun sendOneNotification(
|
||||||
|
notificationData: OneNotificationData,
|
||||||
|
student: Student
|
||||||
|
) {
|
||||||
|
val content = context.getString(
|
||||||
|
notificationData.contentStringRes,
|
||||||
|
*notificationData.contentValues.toTypedArray()
|
||||||
|
)
|
||||||
|
|
||||||
|
val title = context.getString(notificationData.titleStringRes)
|
||||||
|
|
||||||
|
val notification = getDefaultNotificationBuilder(notificationData)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(content)
|
||||||
|
.setStyle(
|
||||||
|
NotificationCompat.BigTextStyle()
|
||||||
|
.setSummaryText(student.nickOrName)
|
||||||
|
.bigText(content)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), notification)
|
||||||
|
|
||||||
|
saveNotification(title, content, notificationData, student)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun sendMultipleNotifications(
|
||||||
|
notificationData: MultipleNotificationsData,
|
||||||
|
student: Student
|
||||||
|
) {
|
||||||
|
val groupType = notificationData.type.group ?: return
|
||||||
|
val group = "${groupType}_${student.id}"
|
||||||
|
val groupId = student.id * 100 + notificationData.type.ordinal
|
||||||
|
|
||||||
|
notificationData.lines.forEach { item ->
|
||||||
|
val title = context.resources.getQuantityString(notificationData.titleStringRes, 1)
|
||||||
|
|
||||||
|
val notification = getDefaultNotificationBuilder(notificationData)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(item)
|
||||||
|
.setStyle(
|
||||||
|
NotificationCompat.BigTextStyle()
|
||||||
|
.setSummaryText(student.nickOrName)
|
||||||
|
.bigText(item)
|
||||||
|
)
|
||||||
|
.setGroup(group)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), notification)
|
||||||
|
|
||||||
|
saveNotification(title, item, notificationData, student)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
|
||||||
|
|
||||||
|
val summaryNotification = getDefaultNotificationBuilder(notificationData)
|
||||||
|
.setSmallIcon(notificationData.icon)
|
||||||
|
.setGroup(group)
|
||||||
|
.setStyle(NotificationCompat.InboxStyle().setSummaryText(student.nickOrName))
|
||||||
|
.setGroupSummary(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
notificationManager.notify(groupId.toInt(), summaryNotification)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
private fun getDefaultNotificationBuilder(notificationData: NotificationData): NotificationCompat.Builder {
|
||||||
|
val pendingIntentsFlags = if (appInfo.systemVersion >= Build.VERSION_CODES.M) {
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
} else {
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
}
|
||||||
|
|
||||||
|
return NotificationCompat.Builder(context, notificationData.type.channel)
|
||||||
|
.setLargeIcon(context.getCompatBitmap(notificationData.icon, R.color.colorPrimary))
|
||||||
|
.setSmallIcon(R.drawable.ic_stat_all)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||||
|
.setContentIntent(
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
context,
|
||||||
|
notificationData.startMenu.id,
|
||||||
|
MainActivity.getStartIntent(context, notificationData.startMenu, true),
|
||||||
|
pendingIntentsFlags
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun saveNotification(
|
||||||
|
title: String,
|
||||||
|
content: String,
|
||||||
|
notificationData: NotificationData,
|
||||||
|
student: Student
|
||||||
|
) {
|
||||||
|
val notificationEntity = Notification(
|
||||||
|
studentId = student.id,
|
||||||
|
title = title,
|
||||||
|
content = content,
|
||||||
|
type = notificationData.type,
|
||||||
|
date = LocalDateTime.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
notificationRepository.saveNotification(notificationEntity)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,102 +0,0 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
|
||||||
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.annotation.PluralsRes
|
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
|
||||||
import io.github.wulkanowy.data.pojos.Notification
|
|
||||||
import io.github.wulkanowy.data.pojos.OneNotification
|
|
||||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
|
||||||
import io.github.wulkanowy.utils.getCompatBitmap
|
|
||||||
import io.github.wulkanowy.utils.getCompatColor
|
|
||||||
import io.github.wulkanowy.utils.nickOrName
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
abstract class BaseNotification(
|
|
||||||
private val context: Context,
|
|
||||||
private val notificationManager: NotificationManagerCompat,
|
|
||||||
) {
|
|
||||||
|
|
||||||
protected fun sendNotification(notification: Notification, student: Student) =
|
|
||||||
when (notification) {
|
|
||||||
is OneNotification -> sendOneNotification(notification, student)
|
|
||||||
is MultipleNotifications -> sendMultipleNotifications(notification, student)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendOneNotification(notification: OneNotification, student: Student?) {
|
|
||||||
notificationManager.notify(
|
|
||||||
Random.nextInt(Int.MAX_VALUE),
|
|
||||||
getNotificationBuilder(notification).apply {
|
|
||||||
val content = context.getString(
|
|
||||||
notification.contentStringRes,
|
|
||||||
*notification.contentValues.toTypedArray()
|
|
||||||
)
|
|
||||||
setContentTitle(context.getString(notification.titleStringRes))
|
|
||||||
setContentText(content)
|
|
||||||
setStyle(
|
|
||||||
NotificationCompat.BigTextStyle()
|
|
||||||
.setSummaryText(student?.nickOrName)
|
|
||||||
.bigText(content)
|
|
||||||
)
|
|
||||||
}.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendMultipleNotifications(notification: MultipleNotifications, student: Student) {
|
|
||||||
val group = notification.type.group + student.id
|
|
||||||
val groupId = student.id * 100 + notification.type.ordinal
|
|
||||||
|
|
||||||
notification.lines.forEach { item ->
|
|
||||||
notificationManager.notify(
|
|
||||||
Random.nextInt(Int.MAX_VALUE),
|
|
||||||
getNotificationBuilder(notification).apply {
|
|
||||||
setContentTitle(getQuantityString(notification.titleStringRes, 1))
|
|
||||||
setContentText(item)
|
|
||||||
setStyle(
|
|
||||||
NotificationCompat.BigTextStyle()
|
|
||||||
.setSummaryText(student.nickOrName)
|
|
||||||
.bigText(item)
|
|
||||||
)
|
|
||||||
setGroup(group)
|
|
||||||
}.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
|
|
||||||
|
|
||||||
notificationManager.notify(
|
|
||||||
groupId.toInt(),
|
|
||||||
getNotificationBuilder(notification).apply {
|
|
||||||
setSmallIcon(notification.icon)
|
|
||||||
setGroup(group)
|
|
||||||
setStyle(NotificationCompat.InboxStyle().setSummaryText(student.nickOrName))
|
|
||||||
setGroupSummary(true)
|
|
||||||
}.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getNotificationBuilder(notification: Notification) = NotificationCompat
|
|
||||||
.Builder(context, notification.type.channel)
|
|
||||||
.setLargeIcon(context.getCompatBitmap(notification.icon, R.color.colorPrimary))
|
|
||||||
.setSmallIcon(R.drawable.ic_stat_all)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
||||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
|
||||||
.setContentIntent(
|
|
||||||
PendingIntent.getActivity(
|
|
||||||
context, notification.startMenu.id,
|
|
||||||
MainActivity.getStartIntent(context, notification.startMenu, true),
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getQuantityString(@PluralsRes id: Int, value: Int): String {
|
|
||||||
return context.resources.getQuantityString(id, value, value)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +1,25 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Conference
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewConferenceNotification @Inject constructor(
|
class NewConferenceNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notify(items: List<Conference>, student: Student) {
|
suspend fun notify(items: List<Conference>, student: Student) {
|
||||||
val today = LocalDateTime.now()
|
val today = LocalDateTime.now()
|
||||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||||
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
|
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
|
||||||
}.ifEmpty { return }
|
}.ifEmpty { return }
|
||||||
|
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_CONFERENCE,
|
type = NotificationType.NEW_CONFERENCE,
|
||||||
icon = R.drawable.ic_more_conferences,
|
icon = R.drawable.ic_more_conferences,
|
||||||
titleStringRes = R.plurals.conference_notify_new_item_title,
|
titleStringRes = R.plurals.conference_notify_new_item_title,
|
||||||
|
@ -33,6 +29,6 @@ class NewConferenceNotification @Inject constructor(
|
||||||
lines = lines
|
lines = lines
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,25 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Exam
|
import io.github.wulkanowy.data.db.entities.Exam
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewExamNotification @Inject constructor(
|
class NewExamNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notify(items: List<Exam>, student: Student) {
|
suspend fun notify(items: List<Exam>, student: Student) {
|
||||||
val today = LocalDate.now()
|
val today = LocalDate.now()
|
||||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
|
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
|
||||||
}.ifEmpty { return }
|
}.ifEmpty { return }
|
||||||
|
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_EXAM,
|
type = NotificationType.NEW_EXAM,
|
||||||
icon = R.drawable.ic_main_exam,
|
icon = R.drawable.ic_main_exam,
|
||||||
titleStringRes = R.plurals.exam_notify_new_item_title,
|
titleStringRes = R.plurals.exam_notify_new_item_title,
|
||||||
|
@ -33,6 +29,6 @@ class NewExamNotification @Inject constructor(
|
||||||
lines = lines
|
lines = lines
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,19 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewGradeNotification @Inject constructor(
|
class NewGradeNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notifyDetails(items: List<Grade>, student: Student) {
|
suspend fun notifyDetails(items: List<Grade>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_GRADE_DETAILS,
|
type = NotificationType.NEW_GRADE_DETAILS,
|
||||||
icon = R.drawable.ic_stat_grade,
|
icon = R.drawable.ic_stat_grade,
|
||||||
titleStringRes = R.plurals.grade_new_items,
|
titleStringRes = R.plurals.grade_new_items,
|
||||||
|
@ -29,11 +25,11 @@ class NewGradeNotification @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notifyPredicted(items: List<GradeSummary>, student: Student) {
|
suspend fun notifyPredicted(items: List<GradeSummary>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_GRADE_PREDICTED,
|
type = NotificationType.NEW_GRADE_PREDICTED,
|
||||||
icon = R.drawable.ic_stat_grade,
|
icon = R.drawable.ic_stat_grade,
|
||||||
titleStringRes = R.plurals.grade_new_items_predicted,
|
titleStringRes = R.plurals.grade_new_items_predicted,
|
||||||
|
@ -45,11 +41,11 @@ class NewGradeNotification @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notifyFinal(items: List<GradeSummary>, student: Student) {
|
suspend fun notifyFinal(items: List<GradeSummary>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_GRADE_FINAL,
|
type = NotificationType.NEW_GRADE_FINAL,
|
||||||
icon = R.drawable.ic_stat_grade,
|
icon = R.drawable.ic_stat_grade,
|
||||||
titleStringRes = R.plurals.grade_new_items_final,
|
titleStringRes = R.plurals.grade_new_items_final,
|
||||||
|
@ -61,6 +57,6 @@ class NewGradeNotification @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,25 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Homework
|
import io.github.wulkanowy.data.db.entities.Homework
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewHomeworkNotification @Inject constructor(
|
class NewHomeworkNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notify(items: List<Homework>, student: Student) {
|
suspend fun notify(items: List<Homework>, student: Student) {
|
||||||
val today = LocalDate.now()
|
val today = LocalDate.now()
|
||||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
|
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
|
||||||
}.ifEmpty { return }
|
}.ifEmpty { return }
|
||||||
|
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_HOMEWORK,
|
type = NotificationType.NEW_HOMEWORK,
|
||||||
icon = R.drawable.ic_more_homework,
|
icon = R.drawable.ic_more_homework,
|
||||||
titleStringRes = R.plurals.homework_notify_new_item_title,
|
titleStringRes = R.plurals.homework_notify_new_item_title,
|
||||||
|
@ -33,6 +29,6 @@ class NewHomeworkNotification @Inject constructor(
|
||||||
lines = lines
|
lines = lines
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.OneNotification
|
import io.github.wulkanowy.data.pojos.OneNotificationData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewLuckyNumberNotification @Inject constructor(
|
class NewLuckyNumberNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notify(item: LuckyNumber, student: Student) {
|
suspend fun notify(item: LuckyNumber, student: Student) {
|
||||||
val notification = OneNotification(
|
val notification = OneNotificationData(
|
||||||
type = NotificationType.NEW_LUCKY_NUMBER,
|
type = NotificationType.NEW_LUCKY_NUMBER,
|
||||||
icon = R.drawable.ic_stat_luckynumber,
|
icon = R.drawable.ic_stat_luckynumber,
|
||||||
titleStringRes = R.string.lucky_number_notify_new_item_title,
|
titleStringRes = R.string.lucky_number_notify_new_item_title,
|
||||||
|
@ -25,6 +21,6 @@ class NewLuckyNumberNotification @Inject constructor(
|
||||||
contentValues = listOf(item.luckyNumber.toString())
|
contentValues = listOf(item.luckyNumber.toString())
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewMessageNotification @Inject constructor(
|
class NewMessageNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notify(items: List<Message>, student: Student) {
|
suspend fun notify(items: List<Message>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_MESSAGE,
|
type = NotificationType.NEW_MESSAGE,
|
||||||
icon = R.drawable.ic_stat_message,
|
icon = R.drawable.ic_stat_message,
|
||||||
titleStringRes = R.plurals.message_new_items,
|
titleStringRes = R.plurals.message_new_items,
|
||||||
|
@ -28,6 +24,6 @@ class NewMessageNotification @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,19 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Note
|
import io.github.wulkanowy.data.db.entities.Note
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
|
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewNoteNotification @Inject constructor(
|
class NewNoteNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notify(items: List<Note>, student: Student) {
|
suspend fun notify(items: List<Note>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_NOTE,
|
type = NotificationType.NEW_NOTE,
|
||||||
icon = R.drawable.ic_stat_note,
|
icon = R.drawable.ic_stat_note,
|
||||||
titleStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
titleStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
||||||
|
@ -41,6 +37,6 @@ class NewNoteNotification @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewSchoolAnnouncementNotification @Inject constructor(
|
class NewSchoolAnnouncementNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
suspend fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_ANNOUNCEMENT,
|
type = NotificationType.NEW_ANNOUNCEMENT,
|
||||||
icon = R.drawable.ic_all_about,
|
icon = R.drawable.ic_all_about,
|
||||||
titleStringRes = R.plurals.school_announcement_notify_new_item_title,
|
titleStringRes = R.plurals.school_announcement_notify_new_item_title,
|
||||||
|
@ -28,6 +24,6 @@ class NewSchoolAnnouncementNotification @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,9 @@ import io.github.wulkanowy.services.sync.channels.NewHomeworkChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
|
import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
|
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
|
||||||
|
import io.github.wulkanowy.services.sync.channels.PushChannel
|
||||||
|
|
||||||
enum class NotificationType(val group: String, val channel: String) {
|
enum class NotificationType(val group: String?, val channel: String) {
|
||||||
NEW_CONFERENCE("new_conferences_group", NewConferencesChannel.CHANNEL_ID),
|
NEW_CONFERENCE("new_conferences_group", NewConferencesChannel.CHANNEL_ID),
|
||||||
NEW_EXAM("new_exam_group", NewExamChannel.CHANNEL_ID),
|
NEW_EXAM("new_exam_group", NewExamChannel.CHANNEL_ID),
|
||||||
NEW_GRADE_DETAILS("new_grade_details_group", NewGradesChannel.CHANNEL_ID),
|
NEW_GRADE_DETAILS("new_grade_details_group", NewGradesChannel.CHANNEL_ID),
|
||||||
|
@ -20,4 +21,5 @@ enum class NotificationType(val group: String, val channel: String) {
|
||||||
NEW_MESSAGE("new_message_group", NewMessagesChannel.CHANNEL_ID),
|
NEW_MESSAGE("new_message_group", NewMessagesChannel.CHANNEL_ID),
|
||||||
NEW_NOTE("new_notes_group", NewNotesChannel.CHANNEL_ID),
|
NEW_NOTE("new_notes_group", NewNotesChannel.CHANNEL_ID),
|
||||||
NEW_ANNOUNCEMENT("new_school_announcements_group", NewSchoolAnnouncementsChannel.CHANNEL_ID),
|
NEW_ANNOUNCEMENT("new_school_announcements_group", NewSchoolAnnouncementsChannel.CHANNEL_ID),
|
||||||
|
PUSH(null, PushChannel.CHANNEL_ID)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,12 @@ import io.github.wulkanowy.utils.waitForResult
|
||||||
import java.time.LocalDate.now
|
import java.time.LocalDate.now
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AttendanceWork @Inject constructor(private val attendanceRepository: AttendanceRepository) : Work {
|
class AttendanceWork @Inject constructor(
|
||||||
|
private val attendanceRepository: AttendanceRepository
|
||||||
|
) : Work {
|
||||||
|
|
||||||
override suspend fun doWork(student: Student, semester: Semester) {
|
override suspend fun doWork(student: Student, semester: Semester) {
|
||||||
attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true).waitForResult()
|
attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true)
|
||||||
|
.waitForResult()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,10 +121,14 @@ class AccountDetailsFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popView() {
|
override fun popViewToMain() {
|
||||||
(requireActivity() as MainActivity).popView(2)
|
(requireActivity() as MainActivity).popView(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun popViewToAccounts() {
|
||||||
|
(requireActivity() as MainActivity).popView(1)
|
||||||
|
}
|
||||||
|
|
||||||
override fun recreateMainView() {
|
override fun recreateMainView() {
|
||||||
requireActivity().recreate()
|
requireActivity().recreate()
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@ class AccountDetailsPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.afterLoading {
|
}.afterLoading {
|
||||||
view?.popView()
|
view?.popViewToMain()
|
||||||
}.launch("switch")
|
}.launch("switch")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,11 +152,14 @@ class AccountDetailsPresenter @Inject constructor(
|
||||||
syncManager.stopSyncWorker()
|
syncManager.stopSyncWorker()
|
||||||
openClearLoginView()
|
openClearLoginView()
|
||||||
}
|
}
|
||||||
studentWithSemesters!!.student.isCurrent -> {
|
studentWithSemesters?.student?.isCurrent == true -> {
|
||||||
Timber.i("Logout result: Logout student and switch to another")
|
Timber.i("Logout result: Logout student and switch to another")
|
||||||
recreateMainView()
|
recreateMainView()
|
||||||
}
|
}
|
||||||
else -> Timber.i("Logout result: Logout student")
|
else -> {
|
||||||
|
Timber.i("Logout result: Logout student")
|
||||||
|
recreateMainView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Status.ERROR -> {
|
Status.ERROR -> {
|
||||||
|
@ -165,7 +168,11 @@ class AccountDetailsPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.afterLoading {
|
}.afterLoading {
|
||||||
view?.popView()
|
if (studentWithSemesters?.student?.isCurrent == true) {
|
||||||
|
view?.popViewToMain()
|
||||||
|
} else {
|
||||||
|
view?.popViewToAccounts()
|
||||||
|
}
|
||||||
}.launch("logout")
|
}.launch("logout")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,9 @@ interface AccountDetailsView : BaseView {
|
||||||
|
|
||||||
fun showLogoutConfirmDialog()
|
fun showLogoutConfirmDialog()
|
||||||
|
|
||||||
fun popView()
|
fun popViewToMain()
|
||||||
|
|
||||||
|
fun popViewToAccounts()
|
||||||
|
|
||||||
fun recreateMainView()
|
fun recreateMainView()
|
||||||
|
|
||||||
|
|
|
@ -245,7 +245,9 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||||
presenter.onDateSet(date.year, date.monthValue, date.dayOfMonth)
|
presenter.onDateSet(date.year, date.monthValue, date.dayOfMonth)
|
||||||
}
|
}
|
||||||
|
|
||||||
datePicker.show(this@AttendanceFragment.parentFragmentManager, null)
|
if (!parentFragmentManager.isStateSaved) {
|
||||||
|
datePicker.show(parentFragmentManager, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showExcuseDialog() {
|
override fun showExcuseDialog() {
|
||||||
|
|
|
@ -152,6 +152,8 @@ class AttendancePresenter @Inject constructor(
|
||||||
fun onExcuseDialogSubmit(reason: String) {
|
fun onExcuseDialogSubmit(reason: String) {
|
||||||
view?.finishActionMode()
|
view?.finishActionMode()
|
||||||
|
|
||||||
|
if (attendanceToExcuseList.isEmpty()) return
|
||||||
|
|
||||||
if (isVulcanExcusedFunctionEnabled) {
|
if (isVulcanExcusedFunctionEnabled) {
|
||||||
excuseAbsence(
|
excuseAbsence(
|
||||||
reason = reason.takeIf { it.isNotBlank() },
|
reason = reason.takeIf { it.isNotBlank() },
|
||||||
|
@ -234,6 +236,7 @@ class AttendancePresenter @Inject constructor(
|
||||||
enableSwipe(true)
|
enableSwipe(true)
|
||||||
showRefresh(true)
|
showRefresh(true)
|
||||||
showProgress(false)
|
showProgress(false)
|
||||||
|
showErrorView(false)
|
||||||
showEmpty(filteredAttendance.isEmpty())
|
showEmpty(filteredAttendance.isEmpty())
|
||||||
showContent(filteredAttendance.isNotEmpty())
|
showContent(filteredAttendance.isNotEmpty())
|
||||||
updateData(filteredAttendance.sortedBy { item -> item.number })
|
updateData(filteredAttendance.sortedBy { item -> item.number })
|
||||||
|
|
|
@ -82,7 +82,13 @@ class AttendanceSummaryPresenter @Inject constructor(
|
||||||
flowWithResourceIn {
|
flowWithResourceIn {
|
||||||
val student = studentRepository.getCurrentStudent()
|
val student = studentRepository.getCurrentStudent()
|
||||||
val semester = semesterRepository.getCurrentSemester(student)
|
val semester = semesterRepository.getCurrentSemester(student)
|
||||||
attendanceSummaryRepository.getAttendanceSummary(student, semester, subjectId, forceRefresh)
|
|
||||||
|
attendanceSummaryRepository.getAttendanceSummary(
|
||||||
|
student = student,
|
||||||
|
semester = semester,
|
||||||
|
subjectId = subjectId,
|
||||||
|
forceRefresh = forceRefresh
|
||||||
|
)
|
||||||
}.onEach {
|
}.onEach {
|
||||||
when (it.status) {
|
when (it.status) {
|
||||||
Status.LOADING -> {
|
Status.LOADING -> {
|
||||||
|
@ -92,6 +98,7 @@ class AttendanceSummaryPresenter @Inject constructor(
|
||||||
showRefresh(true)
|
showRefresh(true)
|
||||||
showProgress(false)
|
showProgress(false)
|
||||||
showContent(true)
|
showContent(true)
|
||||||
|
showErrorView(false)
|
||||||
updateDataSet(sortItems(it.data))
|
updateDataSet(sortItems(it.data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,6 +106,7 @@ class AttendanceSummaryPresenter @Inject constructor(
|
||||||
Status.SUCCESS -> {
|
Status.SUCCESS -> {
|
||||||
Timber.i("Loading attendance summary result: Success")
|
Timber.i("Loading attendance summary result: Success")
|
||||||
view?.apply {
|
view?.apply {
|
||||||
|
showErrorView(false)
|
||||||
showEmpty(it.data!!.isEmpty())
|
showEmpty(it.data!!.isEmpty())
|
||||||
showContent(it.data.isNotEmpty())
|
showContent(it.data.isNotEmpty())
|
||||||
updateDataSet(sortItems(it.data))
|
updateDataSet(sortItems(it.data))
|
||||||
|
|
|
@ -14,6 +14,8 @@ class ConferenceAdapter @Inject constructor() :
|
||||||
|
|
||||||
var items = emptyList<Conference>()
|
var items = emptyList<Conference>()
|
||||||
|
|
||||||
|
var onItemClickListener: (Conference) -> Unit = {}
|
||||||
|
|
||||||
override fun getItemCount() = items.size
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
||||||
|
@ -28,7 +30,10 @@ class ConferenceAdapter @Inject constructor() :
|
||||||
conferenceItemTitle.text = item.title
|
conferenceItemTitle.text = item.title
|
||||||
conferenceItemSubject.text = item.subject
|
conferenceItemSubject.text = item.subject
|
||||||
conferenceItemContent.text = item.agenda
|
conferenceItemContent.text = item.agenda
|
||||||
conferenceItemContent.visibility = if (item.agenda.isBlank()) View.GONE else View.VISIBLE
|
conferenceItemContent.visibility =
|
||||||
|
if (item.agenda.isBlank()) View.GONE else View.VISIBLE
|
||||||
|
|
||||||
|
root.setOnClickListener { onItemClickListener(item) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
package io.github.wulkanowy.ui.modules.conference
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
|
import io.github.wulkanowy.databinding.DialogConferenceBinding
|
||||||
|
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||||
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
|
|
||||||
|
class ConferenceDialog : DialogFragment() {
|
||||||
|
|
||||||
|
private var binding: DialogConferenceBinding by lifecycleAwareVariable()
|
||||||
|
|
||||||
|
private lateinit var conference: Conference
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val ARGUMENT_KEY = "item"
|
||||||
|
|
||||||
|
fun newInstance(conference: Conference) = ConferenceDialog().apply {
|
||||||
|
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, conference) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setStyle(STYLE_NO_TITLE, 0)
|
||||||
|
arguments?.let {
|
||||||
|
conference = it.getSerializable(ARGUMENT_KEY) as Conference
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
) = DialogConferenceBinding.inflate(inflater).also { binding = it }.root
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
|
conferenceDialogClose.setOnClickListener { dismiss() }
|
||||||
|
|
||||||
|
conferenceDialogSubjectValue.text = conference.subject
|
||||||
|
conferenceDialogDateValue.text = conference.date.toFormattedString("dd.MM.yyyy HH:mm")
|
||||||
|
conferenceDialogHeaderValue.text = conference.title
|
||||||
|
conferenceDialogAgendaValue.text = conference.agenda
|
||||||
|
conferenceDialogPresentValue.text = conference.presentOnConference
|
||||||
|
conferenceDialogPresentValue.isVisible = conference.presentOnConference.isNotBlank()
|
||||||
|
conferenceDialogPresentTitle.isVisible = conference.presentOnConference.isNotBlank()
|
||||||
|
conferenceDialogAgendaValue.isVisible = conference.agenda.isNotBlank()
|
||||||
|
conferenceDialogAgendaTitle.isVisible = conference.agenda.isNotBlank()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Conference
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
import io.github.wulkanowy.databinding.FragmentConferenceBinding
|
import io.github.wulkanowy.databinding.FragmentConferenceBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
|
@ -41,6 +42,8 @@ class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.frag
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
|
conferencesAdapter.onItemClickListener = presenter::onItemSelected
|
||||||
|
|
||||||
with(binding.conferenceRecycler) {
|
with(binding.conferenceRecycler) {
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = conferencesAdapter
|
adapter = conferencesAdapter
|
||||||
|
@ -50,7 +53,11 @@ class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.frag
|
||||||
with(binding) {
|
with(binding) {
|
||||||
conferenceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
conferenceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||||
conferenceSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
conferenceSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||||
conferenceSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
conferenceSwipe.setProgressBackgroundColorSchemeColor(
|
||||||
|
requireContext().getThemeAttrColor(
|
||||||
|
R.attr.colorSwipeRefresh
|
||||||
|
)
|
||||||
|
)
|
||||||
conferenceErrorRetry.setOnClickListener { presenter.onRetry() }
|
conferenceErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||||
conferenceErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
conferenceErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||||
}
|
}
|
||||||
|
@ -98,6 +105,10 @@ class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.frag
|
||||||
binding.conferenceRecycler.visibility = if (show) View.VISIBLE else View.GONE
|
binding.conferenceRecycler.visibility = if (show) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openConferenceDialog(conference: Conference) {
|
||||||
|
(activity as? MainActivity)?.showDialogFragment(ConferenceDialog.newInstance(conference))
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
presenter.onDetachView()
|
presenter.onDetachView()
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.github.wulkanowy.ui.modules.conference
|
package io.github.wulkanowy.ui.modules.conference
|
||||||
|
|
||||||
import io.github.wulkanowy.data.Status
|
import io.github.wulkanowy.data.Status
|
||||||
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
import io.github.wulkanowy.data.repositories.ConferenceRepository
|
import io.github.wulkanowy.data.repositories.ConferenceRepository
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
|
@ -43,6 +44,10 @@ class ConferencePresenter @Inject constructor(
|
||||||
loadData(true)
|
loadData(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onItemSelected(conference: Conference) {
|
||||||
|
view?.openConferenceDialog(conference)
|
||||||
|
}
|
||||||
|
|
||||||
fun onDetailsClick() {
|
fun onDetailsClick() {
|
||||||
view?.showErrorDetailsDialog(lastError)
|
view?.showErrorDetailsDialog(lastError)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,4 +26,6 @@ interface ConferenceView : BaseView {
|
||||||
fun enableSwipe(enable: Boolean)
|
fun enableSwipe(enable: Boolean)
|
||||||
|
|
||||||
fun showContent(show: Boolean)
|
fun showContent(show: Boolean)
|
||||||
|
|
||||||
|
fun openConferenceDialog(conference: Conference)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
import io.github.wulkanowy.utils.left
|
import io.github.wulkanowy.utils.left
|
||||||
import io.github.wulkanowy.utils.nickOrName
|
import io.github.wulkanowy.utils.nickOrName
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
|
import timber.log.Timber
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
@ -52,7 +53,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
|
|
||||||
var onAttendanceTileClickListener: () -> Unit = {}
|
var onAttendanceTileClickListener: () -> Unit = {}
|
||||||
|
|
||||||
var onLessonsTileClickListener: () -> Unit = {}
|
var onLessonsTileClickListener: (LocalDate) -> Unit = {}
|
||||||
|
|
||||||
var onHomeworkTileClickListener: () -> Unit = {}
|
var onHomeworkTileClickListener: () -> Unit = {}
|
||||||
|
|
||||||
|
@ -170,6 +171,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
val isLoading = item.isLoading
|
val isLoading = item.isLoading
|
||||||
val binding = horizontalGroupViewHolder.binding
|
val binding = horizontalGroupViewHolder.binding
|
||||||
val context = binding.root.context
|
val context = binding.root.context
|
||||||
|
val isLoadingVisible =
|
||||||
|
(isLoading && !item.isDataLoaded) || (isLoading && !item.isFullDataLoaded)
|
||||||
val attendanceColor = when {
|
val attendanceColor = when {
|
||||||
attendancePercentage == null || attendancePercentage == .0 -> {
|
attendancePercentage == null || attendancePercentage == .0 -> {
|
||||||
context.getThemeAttrColor(R.attr.colorOnSurface)
|
context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||||
|
@ -199,13 +202,12 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
context.getString(R.string.dashboard_horizontal_group_no_data)
|
context.getString(R.string.dashboard_horizontal_group_no_data)
|
||||||
} else luckyNumber?.toString()
|
} else luckyNumber?.toString()
|
||||||
|
|
||||||
dashboardHorizontalGroupItemInfoContainer.isVisible = error != null || isLoading
|
dashboardHorizontalGroupItemInfoContainer.isVisible = error != null || isLoadingVisible
|
||||||
dashboardHorizontalGroupItemInfoProgress.isVisible =
|
dashboardHorizontalGroupItemInfoProgress.isVisible = isLoadingVisible
|
||||||
(isLoading && !item.isDataLoaded) || (isLoading && !item.isFullDataLoaded)
|
|
||||||
dashboardHorizontalGroupItemInfoErrorText.isVisible = error != null
|
dashboardHorizontalGroupItemInfoErrorText.isVisible = error != null
|
||||||
|
|
||||||
with(dashboardHorizontalGroupItemLuckyContainer) {
|
with(dashboardHorizontalGroupItemLuckyContainer) {
|
||||||
isVisible = luckyNumber != null && luckyNumber != -1
|
isVisible = luckyNumber != null && luckyNumber != -1 && !isLoadingVisible
|
||||||
setOnClickListener { onLuckyNumberTileClickListener() }
|
setOnClickListener { onLuckyNumberTileClickListener() }
|
||||||
|
|
||||||
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
@ -220,7 +222,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
}
|
}
|
||||||
|
|
||||||
with(dashboardHorizontalGroupItemAttendanceContainer) {
|
with(dashboardHorizontalGroupItemAttendanceContainer) {
|
||||||
isVisible = attendancePercentage != null && attendancePercentage != -1.0
|
isVisible =
|
||||||
|
attendancePercentage != null && attendancePercentage != -1.0 && !isLoadingVisible
|
||||||
updateLayoutParams<ConstraintLayout.LayoutParams> {
|
updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||||
matchConstraintPercentWidth = when {
|
matchConstraintPercentWidth = when {
|
||||||
luckyNumber == null && unreadMessagesCount == null -> 1.0f
|
luckyNumber == null && unreadMessagesCount == null -> 1.0f
|
||||||
|
@ -232,7 +235,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
}
|
}
|
||||||
|
|
||||||
with(dashboardHorizontalGroupItemMessageContainer) {
|
with(dashboardHorizontalGroupItemMessageContainer) {
|
||||||
isVisible = unreadMessagesCount != null && unreadMessagesCount != -1
|
isVisible =
|
||||||
|
unreadMessagesCount != null && unreadMessagesCount != -1 && !isLoadingVisible
|
||||||
setOnClickListener { onMessageTileClickListener() }
|
setOnClickListener { onMessageTileClickListener() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,10 +275,12 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
val item = items[position] as DashboardItem.Lessons
|
val item = items[position] as DashboardItem.Lessons
|
||||||
val timetableFull = item.lessons
|
val timetableFull = item.lessons
|
||||||
val binding = lessonsViewHolder.binding
|
val binding = lessonsViewHolder.binding
|
||||||
|
var dateToNavigate = LocalDate.now()
|
||||||
|
|
||||||
fun updateLessonState() {
|
fun updateLessonState() {
|
||||||
val currentDateTime = LocalDateTime.now()
|
val currentDateTime = LocalDateTime.now()
|
||||||
val currentDate = LocalDate.now()
|
val currentDate = LocalDate.now()
|
||||||
|
val tomorrowDate = currentDate.plusDays(1)
|
||||||
|
|
||||||
val currentTimetable = timetableFull?.lessons
|
val currentTimetable = timetableFull?.lessons
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
|
@ -292,22 +298,27 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
|
|
||||||
when {
|
when {
|
||||||
currentTimetable.isNotEmpty() -> {
|
currentTimetable.isNotEmpty() -> {
|
||||||
|
dateToNavigate = currentDate
|
||||||
updateLessonView(item, currentTimetable, binding)
|
updateLessonView(item, currentTimetable, binding)
|
||||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
||||||
}
|
}
|
||||||
tomorrowTimetable.isNotEmpty() -> {
|
tomorrowTimetable.isNotEmpty() -> {
|
||||||
|
dateToNavigate = tomorrowDate
|
||||||
updateLessonView(item, tomorrowTimetable, binding)
|
updateLessonView(item, tomorrowTimetable, binding)
|
||||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
||||||
}
|
}
|
||||||
currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
|
currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
|
||||||
|
dateToNavigate = currentDate
|
||||||
updateLessonView(item, emptyList(), binding, currentDayHeader)
|
updateLessonView(item, emptyList(), binding, currentDayHeader)
|
||||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
||||||
}
|
}
|
||||||
tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> {
|
tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> {
|
||||||
|
dateToNavigate = tomorrowDate
|
||||||
updateLessonView(item, emptyList(), binding, tomorrowDayHeader)
|
updateLessonView(item, emptyList(), binding, tomorrowDayHeader)
|
||||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
dateToNavigate = tomorrowDate
|
||||||
updateLessonView(item, emptyList(), binding)
|
updateLessonView(item, emptyList(), binding)
|
||||||
binding.dashboardLessonsItemTitleTomorrow.isVisible =
|
binding.dashboardLessonsItemTitleTomorrow.isVisible =
|
||||||
!(item.isLoading && item.error == null)
|
!(item.isLoading && item.error == null)
|
||||||
|
@ -322,7 +333,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
Handler(Looper.getMainLooper()).post { updateLessonState() }
|
Handler(Looper.getMainLooper()).post { updateLessonState() }
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.root.setOnClickListener { onLessonsTileClickListener() }
|
binding.root.setOnClickListener { onLessonsTileClickListener(dateToNavigate) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateLessonView(
|
private fun updateLessonView(
|
||||||
|
@ -426,7 +437,10 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val minutesToEndLesson = firstLesson.left!!.toMinutes() + 1
|
val minutesToEndLesson = firstLesson.left?.toMinutes()?.plus(1) ?: run {
|
||||||
|
Timber.e(IllegalArgumentException("Lesson left is null. START ${firstLesson.start} ; END ${firstLesson.end} ; CURRENT ${LocalDateTime.now()}"))
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
firstTimeText = context.resources.getQuantityString(
|
firstTimeText = context.resources.getQuantityString(
|
||||||
R.plurals.dashboard_timetable_first_lesson_time_more_minutes,
|
R.plurals.dashboard_timetable_first_lesson_time_more_minutes,
|
||||||
|
|
|
@ -24,6 +24,7 @@ import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
|
||||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment
|
||||||
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
||||||
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
||||||
import io.github.wulkanowy.utils.capitalise
|
import io.github.wulkanowy.utils.capitalise
|
||||||
|
@ -70,10 +71,7 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
val mainActivity = requireActivity() as MainActivity
|
val mainActivity = requireActivity() as MainActivity
|
||||||
val itemTouchHelper = ItemTouchHelper(
|
val itemTouchHelper = ItemTouchHelper(
|
||||||
DashboardItemMoveCallback(
|
DashboardItemMoveCallback(dashboardAdapter, presenter::onDragAndDropEnd)
|
||||||
dashboardAdapter,
|
|
||||||
presenter::onDragAndDropEnd
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
dashboardAdapter.apply {
|
dashboardAdapter.apply {
|
||||||
|
@ -87,7 +85,9 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||||
onAttendanceTileClickListener = {
|
onAttendanceTileClickListener = {
|
||||||
mainActivity.pushView(AttendanceSummaryFragment.newInstance())
|
mainActivity.pushView(AttendanceSummaryFragment.newInstance())
|
||||||
}
|
}
|
||||||
onLessonsTileClickListener = { mainActivity.pushView(TimetableFragment.newInstance()) }
|
onLessonsTileClickListener = {
|
||||||
|
mainActivity.pushView(TimetableFragment.newInstance(it))
|
||||||
|
}
|
||||||
onGradeTileClickListener = { mainActivity.pushView(GradeFragment.newInstance()) }
|
onGradeTileClickListener = { mainActivity.pushView(GradeFragment.newInstance()) }
|
||||||
onHomeworkTileClickListener = { mainActivity.pushView(HomeworkFragment.newInstance()) }
|
onHomeworkTileClickListener = { mainActivity.pushView(HomeworkFragment.newInstance()) }
|
||||||
onAnnouncementsTileClickListener = {
|
onAnnouncementsTileClickListener = {
|
||||||
|
@ -121,6 +121,7 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
return when (item.itemId) {
|
return when (item.itemId) {
|
||||||
R.id.dashboard_menu_tiles -> presenter.onDashboardTileSettingsSelected()
|
R.id.dashboard_menu_tiles -> presenter.onDashboardTileSettingsSelected()
|
||||||
|
R.id.dashboard_menu_notifaction_list -> presenter.onNotificationsCenterSelected()
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,6 +184,10 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||||
if (::presenter.isInitialized) presenter.onViewReselected()
|
if (::presenter.isInitialized) presenter.onViewReselected()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openNotificationsCenterView() {
|
||||||
|
(requireActivity() as MainActivity).pushView(NotificationsCenterFragment.newInstance())
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
dashboardAdapter.clearTimers()
|
dashboardAdapter.clearTimers()
|
||||||
presenter.onDetachView()
|
presenter.onDetachView()
|
||||||
|
|
|
@ -209,6 +209,11 @@ class DashboardPresenter @Inject constructor(
|
||||||
view?.showErrorDetailsDialog(lastError)
|
view?.showErrorDetailsDialog(lastError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onNotificationsCenterSelected(): Boolean {
|
||||||
|
view?.openNotificationsCenterView()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
fun onDashboardTileSettingsSelected(): Boolean {
|
fun onDashboardTileSettingsSelected(): Boolean {
|
||||||
view?.showDashboardTileSettings(preferencesRepository.selectedDashboardTiles.toList())
|
view?.showDashboardTileSettings(preferencesRepository.selectedDashboardTiles.toList())
|
||||||
return true
|
return true
|
||||||
|
@ -492,7 +497,13 @@ class DashboardPresenter @Inject constructor(
|
||||||
end = LocalDate.now().plusDays(7),
|
end = LocalDate.now().plusDays(7),
|
||||||
forceRefresh = forceRefresh
|
forceRefresh = forceRefresh
|
||||||
)
|
)
|
||||||
}.onEach {
|
}
|
||||||
|
.map { examResource ->
|
||||||
|
val sortedExams = examResource.data?.sortedBy { it.date }
|
||||||
|
|
||||||
|
examResource.copy(data = sortedExams)
|
||||||
|
}
|
||||||
|
.onEach {
|
||||||
when (it.status) {
|
when (it.status) {
|
||||||
Status.LOADING -> {
|
Status.LOADING -> {
|
||||||
Timber.i("Loading dashboard exams data started")
|
Timber.i("Loading dashboard exams data started")
|
||||||
|
|
|
@ -23,4 +23,6 @@ interface DashboardView : BaseView {
|
||||||
fun resetView()
|
fun resetView()
|
||||||
|
|
||||||
fun popViewToRoot()
|
fun popViewToRoot()
|
||||||
|
|
||||||
|
fun openNotificationsCenterView()
|
||||||
}
|
}
|
|
@ -87,7 +87,7 @@ class NotificationDebugPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun withStudent(block: (Student) -> Unit) {
|
private fun withStudent(block: suspend (Student) -> Unit) {
|
||||||
launch {
|
launch {
|
||||||
block(studentRepository.getCurrentStudent(false))
|
block(studentRepository.getCurrentStudent(false))
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,7 +131,9 @@ class GradeAverageProvider @Inject constructor(
|
||||||
val updatedFirstSemesterGrades =
|
val updatedFirstSemesterGrades =
|
||||||
firstSemesterSubject?.grades?.updateModifiers(student).orEmpty()
|
firstSemesterSubject?.grades?.updateModifiers(student).orEmpty()
|
||||||
|
|
||||||
(updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage(isOptionalArithmeticAverage)
|
(updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage(
|
||||||
|
isOptionalArithmeticAverage
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
secondSemesterSubject.average
|
secondSemesterSubject.average
|
||||||
}
|
}
|
||||||
|
@ -147,7 +149,8 @@ class GradeAverageProvider @Inject constructor(
|
||||||
|
|
||||||
return if (!isAnyVulcanAverage || isGradeAverageForceCalc) {
|
return if (!isAnyVulcanAverage || isGradeAverageForceCalc) {
|
||||||
val secondSemesterAverage =
|
val secondSemesterAverage =
|
||||||
secondSemesterSubject.grades.updateModifiers(student).calcAverage(isOptionalArithmeticAverage)
|
secondSemesterSubject.grades.updateModifiers(student)
|
||||||
|
.calcAverage(isOptionalArithmeticAverage)
|
||||||
val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student)
|
val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student)
|
||||||
?.calcAverage(isOptionalArithmeticAverage) ?: secondSemesterAverage
|
?.calcAverage(isOptionalArithmeticAverage) ?: secondSemesterAverage
|
||||||
|
|
||||||
|
@ -213,7 +216,8 @@ class GradeAverageProvider @Inject constructor(
|
||||||
proposedPoints = "",
|
proposedPoints = "",
|
||||||
finalPoints = "",
|
finalPoints = "",
|
||||||
pointsSum = "",
|
pointsSum = "",
|
||||||
average = if (calcAverage) details.updateModifiers(student).calcAverage(isOptionalArithmeticAverage) else .0
|
average = if (calcAverage) details.updateModifiers(student)
|
||||||
|
.calcAverage(isOptionalArithmeticAverage) else .0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.databinding.ItemGradeSummaryBinding
|
import io.github.wulkanowy.databinding.ItemGradeSummaryBinding
|
||||||
import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding
|
import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding
|
||||||
import io.github.wulkanowy.utils.calcAverage
|
import io.github.wulkanowy.utils.calcFinalAverage
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -25,6 +25,10 @@ class GradeSummaryAdapter @Inject constructor(
|
||||||
|
|
||||||
var items = emptyList<GradeSummary>()
|
var items = emptyList<GradeSummary>()
|
||||||
|
|
||||||
|
var onCalculatedHelpClickListener: () -> Unit = {}
|
||||||
|
|
||||||
|
var onFinalHelpClickListener: () -> Unit = {}
|
||||||
|
|
||||||
override fun getItemCount() = items.size + if (items.isNotEmpty()) 1 else 0
|
override fun getItemCount() = items.size + if (items.isNotEmpty()) 1 else 0
|
||||||
|
|
||||||
override fun getItemViewType(position: Int) = when (position) {
|
override fun getItemViewType(position: Int) = when (position) {
|
||||||
|
@ -60,7 +64,7 @@ class GradeSummaryAdapter @Inject constructor(
|
||||||
val finalItemsCount = items.count { it.finalGrade.matches("[0-6][+-]?".toRegex()) }
|
val finalItemsCount = items.count { it.finalGrade.matches("[0-6][+-]?".toRegex()) }
|
||||||
val calculatedItemsCount = items.count { value -> value.average != 0.0 }
|
val calculatedItemsCount = items.count { value -> value.average != 0.0 }
|
||||||
val allItemsCount = items.count { !it.subject.equals("zachowanie", true) }
|
val allItemsCount = items.count { !it.subject.equals("zachowanie", true) }
|
||||||
val finalAverage = items.calcAverage(
|
val finalAverage = items.calcFinalAverage(
|
||||||
preferencesRepository.gradePlusModifier,
|
preferencesRepository.gradePlusModifier,
|
||||||
preferencesRepository.gradeMinusModifier
|
preferencesRepository.gradeMinusModifier
|
||||||
)
|
)
|
||||||
|
@ -83,6 +87,9 @@ class GradeSummaryAdapter @Inject constructor(
|
||||||
calculatedItemsCount,
|
calculatedItemsCount,
|
||||||
allItemsCount
|
allItemsCount
|
||||||
)
|
)
|
||||||
|
|
||||||
|
gradeSummaryCalculatedAverageHelp.setOnClickListener { onCalculatedHelpClickListener() }
|
||||||
|
gradeSummaryFinalAverageHelp.setOnClickListener { onFinalHelpClickListener() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.INVISIBLE
|
import android.view.View.INVISIBLE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
|
@ -48,6 +49,11 @@ class GradeSummaryFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
|
with(gradeSummaryAdapter) {
|
||||||
|
onCalculatedHelpClickListener = presenter::onCalculatedAverageHelpClick
|
||||||
|
onFinalHelpClickListener = presenter::onFinalAverageHelpClick
|
||||||
|
}
|
||||||
|
|
||||||
with(binding.gradeSummaryRecycler) {
|
with(binding.gradeSummaryRecycler) {
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = gradeSummaryAdapter
|
adapter = gradeSummaryAdapter
|
||||||
|
@ -55,7 +61,11 @@ class GradeSummaryFragment :
|
||||||
with(binding) {
|
with(binding) {
|
||||||
gradeSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
gradeSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||||
gradeSummarySwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
gradeSummarySwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||||
gradeSummarySwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
gradeSummarySwipe.setProgressBackgroundColorSchemeColor(
|
||||||
|
requireContext().getThemeAttrColor(
|
||||||
|
R.attr.colorSwipeRefresh
|
||||||
|
)
|
||||||
|
)
|
||||||
gradeSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
|
gradeSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||||
gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||||
}
|
}
|
||||||
|
@ -107,6 +117,22 @@ class GradeSummaryFragment :
|
||||||
binding.gradeSummarySwipe.isRefreshing = show
|
binding.gradeSummarySwipe.isRefreshing = show
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun showCalculatedAverageHelpDialog() {
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.grade_summary_calculated_average_help_dialog_title)
|
||||||
|
.setMessage(R.string.grade_summary_calculated_average_help_dialog_message)
|
||||||
|
.setPositiveButton(R.string.all_close) { _, _ -> }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showFinalAverageHelpDialog() {
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.grade_summary_final_average_help_dialog_title)
|
||||||
|
.setMessage(R.string.grade_summary_final_average_help_dialog_message)
|
||||||
|
.setPositiveButton(R.string.all_close) { _, _ -> }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) {
|
override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) {
|
||||||
presenter.onParentViewLoadData(semesterId, forceRefresh)
|
presenter.onParentViewLoadData(semesterId, forceRefresh)
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,14 @@ class GradeSummaryPresenter @Inject constructor(
|
||||||
cancelJobs("load")
|
cancelJobs("load")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onCalculatedAverageHelpClick() {
|
||||||
|
view?.showCalculatedAverageHelpDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onFinalAverageHelpClick() {
|
||||||
|
view?.showFinalAverageHelpDialog()
|
||||||
|
}
|
||||||
|
|
||||||
private fun createGradeSummaryItems(items: List<GradeSubject>): List<GradeSummary> {
|
private fun createGradeSummaryItems(items: List<GradeSubject>): List<GradeSummary> {
|
||||||
return items
|
return items
|
||||||
.filter { !checkEmpty(it) }
|
.filter { !checkEmpty(it) }
|
||||||
|
|
|
@ -33,6 +33,10 @@ interface GradeSummaryView : BaseView {
|
||||||
|
|
||||||
fun showEmpty(show: Boolean)
|
fun showEmpty(show: Boolean)
|
||||||
|
|
||||||
|
fun showCalculatedAverageHelpDialog()
|
||||||
|
|
||||||
|
fun showFinalAverageHelpDialog()
|
||||||
|
|
||||||
fun notifyParentDataLoaded(semesterId: Int)
|
fun notifyParentDataLoaded(semesterId: Int)
|
||||||
|
|
||||||
fun notifyParentRefresh()
|
fun notifyParentRefresh()
|
||||||
|
|
|
@ -103,9 +103,8 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyInitSymbolFragment(loginData: Triple<String, String, String>) {
|
override fun notifyInitSymbolFragment(loginData: Triple<String, String, String>) {
|
||||||
(loginAdapter.getFragmentInstance(1) as? LoginSymbolFragment)?.onParentInitSymbolFragment(
|
(loginAdapter.getFragmentInstance(1) as? LoginSymbolFragment)
|
||||||
loginData
|
?.onParentInitSymbolFragment(loginData)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyInitStudentSelectFragment(studentsWithSemesters: List<StudentWithSemesters>) {
|
override fun notifyInitStudentSelectFragment(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
|
class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
|
||||||
|
|
||||||
var onBadCredentials: () -> Unit = {}
|
var onBadCredentials: (String?) -> Unit = {}
|
||||||
|
|
||||||
var onInvalidToken: (String) -> Unit = {}
|
var onInvalidToken: (String) -> Unit = {}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler
|
||||||
|
|
||||||
override fun proceed(error: Throwable) {
|
override fun proceed(error: Throwable) {
|
||||||
when (error) {
|
when (error) {
|
||||||
is BadCredentialsException -> onBadCredentials()
|
is BadCredentialsException -> onBadCredentials(error.message)
|
||||||
is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student))
|
is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student))
|
||||||
is TokenDeadException -> onInvalidToken(resources.getString(R.string.login_expired_token))
|
is TokenDeadException -> onInvalidToken(resources.getString(R.string.login_expired_token))
|
||||||
is InvalidTokenException -> onInvalidToken(resources.getString(R.string.login_invalid_token))
|
is InvalidTokenException -> onInvalidToken(resources.getString(R.string.login_invalid_token))
|
||||||
|
|
|
@ -51,10 +51,12 @@ class LoginAdvancedFragment :
|
||||||
private lateinit var hostSymbols: Array<String>
|
private lateinit var hostSymbols: Array<String>
|
||||||
|
|
||||||
override val formHostValue: String
|
override val formHostValue: String
|
||||||
get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty()
|
get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
override val formHostSymbol: String
|
override val formHostSymbol: String
|
||||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty()
|
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
override val formPinValue: String
|
override val formPinValue: String
|
||||||
get() = binding.loginFormPin.text.toString().trim()
|
get() = binding.loginFormPin.text.toString().trim()
|
||||||
|
@ -92,39 +94,62 @@ class LoginAdvancedFragment :
|
||||||
loginFormSignIn.setOnClickListener { presenter.onSignInClick() }
|
loginFormSignIn.setOnClickListener { presenter.onSignInClick() }
|
||||||
|
|
||||||
loginTypeSwitch.setOnCheckedChangeListener { _, checkedId ->
|
loginTypeSwitch.setOnCheckedChangeListener { _, checkedId ->
|
||||||
presenter.onLoginModeSelected(when (checkedId) {
|
presenter.onLoginModeSelected(
|
||||||
|
when (checkedId) {
|
||||||
R.id.loginTypeApi -> Sdk.Mode.API
|
R.id.loginTypeApi -> Sdk.Mode.API
|
||||||
R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER
|
R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER
|
||||||
else -> Sdk.Mode.HYBRID
|
else -> Sdk.Mode.HYBRID
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
loginFormPin.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() }
|
loginFormPin.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() }
|
||||||
loginFormPass.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() }
|
loginFormPass.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() }
|
||||||
|
|
||||||
loginFormSymbol.setAdapter(ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, resources.getStringArray(R.array.symbols_values)))
|
loginFormSymbol.setAdapter(
|
||||||
|
ArrayAdapter(
|
||||||
|
requireContext(),
|
||||||
|
android.R.layout.simple_list_item_1,
|
||||||
|
resources.getStringArray(R.array.symbols_values)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
with(binding.loginFormHost) {
|
with(binding.loginFormHost) {
|
||||||
setText(hostKeys.getOrNull(0).orEmpty())
|
setText(hostKeys.getOrNull(0).orEmpty())
|
||||||
setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys))
|
setAdapter(
|
||||||
|
LoginSymbolAdapter(
|
||||||
|
context,
|
||||||
|
R.layout.support_simple_spinner_dropdown_item,
|
||||||
|
hostKeys
|
||||||
|
)
|
||||||
|
)
|
||||||
setOnClickListener { if (binding.loginFormContainer.visibility == GONE) dismissDropDown() }
|
setOnClickListener { if (binding.loginFormContainer.visibility == GONE) dismissDropDown() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showMobileApiWarningMessage() {
|
override fun showMobileApiWarningMessage() {
|
||||||
binding.loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_mobile_api)
|
binding.loginFormAdvancedWarningInfo.text =
|
||||||
|
getString(R.string.login_advanced_warning_mobile_api)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showScraperWarningMessage() {
|
override fun showScraperWarningMessage() {
|
||||||
binding.loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_scraper)
|
binding.loginFormAdvancedWarningInfo.text =
|
||||||
|
getString(R.string.login_advanced_warning_scraper)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showHybridWarningMessage() {
|
override fun showHybridWarningMessage() {
|
||||||
binding.loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_hybrid)
|
binding.loginFormAdvancedWarningInfo.text =
|
||||||
|
getString(R.string.login_advanced_warning_hybrid)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setDefaultCredentials(username: String, pass: String, symbol: String, token: String, pin: String) {
|
override fun setDefaultCredentials(
|
||||||
|
username: String,
|
||||||
|
pass: String,
|
||||||
|
symbol: String,
|
||||||
|
token: String,
|
||||||
|
pin: String
|
||||||
|
) {
|
||||||
with(binding) {
|
with(binding) {
|
||||||
loginFormUsername.setText(username)
|
loginFormUsername.setText(username)
|
||||||
loginFormPass.setText(pass)
|
loginFormPass.setText(pass)
|
||||||
|
@ -177,10 +202,10 @@ class LoginAdvancedFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setErrorPassIncorrect() {
|
override fun setErrorPassIncorrect(message: String?) {
|
||||||
with(binding.loginFormPassLayout) {
|
with(binding.loginFormPassLayout) {
|
||||||
requestFocus()
|
requestFocus()
|
||||||
error = getString(R.string.login_incorrect_password)
|
error = message ?: getString(R.string.login_incorrect_password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,11 +321,13 @@ class LoginAdvancedFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyParentAccountLogged(studentsWithSemesters: List<StudentWithSemesters>) {
|
override fun notifyParentAccountLogged(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||||
(activity as? LoginActivity)?.onFormFragmentAccountLogged(studentsWithSemesters, Triple(
|
(activity as? LoginActivity)?.onFormFragmentAccountLogged(
|
||||||
|
studentsWithSemesters, Triple(
|
||||||
binding.loginFormUsername.text.toString(),
|
binding.loginFormUsername.text.toString(),
|
||||||
binding.loginFormPass.text.toString(),
|
binding.loginFormPass.text.toString(),
|
||||||
resources.getStringArray(R.array.hosts_values)[1]
|
resources.getStringArray(R.array.hosts_values)[1]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
|
|
@ -34,9 +34,9 @@ class LoginAdvancedPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onBadCredentials() {
|
private fun onBadCredentials(message: String?) {
|
||||||
view?.run {
|
view?.run {
|
||||||
setErrorPassIncorrect()
|
setErrorPassIncorrect(message)
|
||||||
showSoftKeyboard()
|
showSoftKeyboard()
|
||||||
Timber.i("Entered wrong username or password")
|
Timber.i("Entered wrong username or password")
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ interface LoginAdvancedView : BaseView {
|
||||||
|
|
||||||
fun setErrorPassInvalid(focus: Boolean)
|
fun setErrorPassInvalid(focus: Boolean)
|
||||||
|
|
||||||
fun setErrorPassIncorrect()
|
fun setErrorPassIncorrect(message: String?)
|
||||||
|
|
||||||
fun clearUsernameError()
|
fun clearUsernameError()
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
|
@ -41,10 +42,12 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||||
get() = binding.loginFormPass.text.toString()
|
get() = binding.loginFormPass.text.toString()
|
||||||
|
|
||||||
override val formHostValue: String
|
override val formHostValue: String
|
||||||
get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty()
|
get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
override val formHostSymbol: String
|
override val formHostSymbol: String
|
||||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty()
|
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
override val nicknameLabel: String
|
override val nicknameLabel: String
|
||||||
get() = getString(R.string.login_nickname_hint)
|
get() = getString(R.string.login_nickname_hint)
|
||||||
|
@ -88,7 +91,13 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||||
|
|
||||||
with(binding.loginFormHost) {
|
with(binding.loginFormHost) {
|
||||||
setText(hostKeys.getOrNull(0).orEmpty())
|
setText(hostKeys.getOrNull(0).orEmpty())
|
||||||
setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys))
|
setAdapter(
|
||||||
|
LoginSymbolAdapter(
|
||||||
|
context,
|
||||||
|
R.layout.support_simple_spinner_dropdown_item,
|
||||||
|
hostKeys
|
||||||
|
)
|
||||||
|
)
|
||||||
setOnClickListener { if (binding.loginFormContainer.visibility == GONE) dismissDropDown() }
|
setOnClickListener { if (binding.loginFormContainer.visibility == GONE) dismissDropDown() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,24 +151,31 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setErrorPassIncorrect() {
|
override fun setErrorPassIncorrect(message: String?) {
|
||||||
with(binding.loginFormPassLayout) {
|
val error = message ?: getString(R.string.login_incorrect_password_default)
|
||||||
error = getString(R.string.login_incorrect_password)
|
|
||||||
|
with(binding) {
|
||||||
|
loginFormUsernameLayout.error = " "
|
||||||
|
loginFormPassLayout.error = " "
|
||||||
|
loginFormErrorBox.text = getString(R.string.login_incorrect_password, error)
|
||||||
|
loginFormErrorBox.isVisible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setErrorEmailInvalid(domain: String) {
|
override fun setErrorEmailInvalid(domain: String) {
|
||||||
with(binding.loginFormUsernameLayout) {
|
with(binding.loginFormUsernameLayout) {
|
||||||
error = getString(R.string.login_invalid_custom_email,domain)
|
error = getString(R.string.login_invalid_custom_email, domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearUsernameError() {
|
override fun clearUsernameError() {
|
||||||
binding.loginFormUsernameLayout.error = null
|
binding.loginFormUsernameLayout.error = null
|
||||||
|
binding.loginFormErrorBox.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearPassError() {
|
override fun clearPassError() {
|
||||||
binding.loginFormPassLayout.error = null
|
binding.loginFormPassLayout.error = null
|
||||||
|
binding.loginFormErrorBox.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showSoftKeyboard() {
|
override fun showSoftKeyboard() {
|
||||||
|
@ -183,12 +199,18 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||||
binding.loginFormVersion.text = "v${appInfo.versionName}"
|
binding.loginFormVersion.text = "v${appInfo.versionName}"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyParentAccountLogged(studentsWithSemesters: List<StudentWithSemesters>, loginData: Triple<String, String, String>) {
|
override fun notifyParentAccountLogged(
|
||||||
|
studentsWithSemesters: List<StudentWithSemesters>,
|
||||||
|
loginData: Triple<String, String, String>
|
||||||
|
) {
|
||||||
(activity as? LoginActivity)?.onFormFragmentAccountLogged(studentsWithSemesters, loginData)
|
(activity as? LoginActivity)?.onFormFragmentAccountLogged(studentsWithSemesters, loginData)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openPrivacyPolicyPage() {
|
override fun openPrivacyPolicyPage() {
|
||||||
context?.openInternetBrowser("https://wulkanowy.github.io/polityka-prywatnosci.html", ::showMessage)
|
context?.openInternetBrowser(
|
||||||
|
"https://wulkanowy.github.io/polityka-prywatnosci.html",
|
||||||
|
::showMessage
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showContact(show: Boolean) {
|
override fun showContact(show: Boolean) {
|
||||||
|
@ -210,7 +232,10 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openFaqPage() {
|
override fun openFaqPage() {
|
||||||
context?.openInternetBrowser("https://wulkanowy.github.io/czesto-zadawane-pytania/dlaczego-nie-moge-sie-zalogowac", ::showMessage)
|
context?.openInternetBrowser(
|
||||||
|
"https://wulkanowy.github.io/czesto-zadawane-pytania/dlaczego-nie-moge-sie-zalogowac",
|
||||||
|
::showMessage
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
@ -223,7 +248,8 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||||
chooserTitle = requireContext().getString(R.string.login_email_intent_title),
|
chooserTitle = requireContext().getString(R.string.login_email_intent_title),
|
||||||
email = "wulkanowyinc@gmail.com",
|
email = "wulkanowyinc@gmail.com",
|
||||||
subject = requireContext().getString(R.string.login_email_subject),
|
subject = requireContext().getString(R.string.login_email_subject),
|
||||||
body = requireContext().getString(R.string.login_email_text,
|
body = requireContext().getString(
|
||||||
|
R.string.login_email_text,
|
||||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||||
appInfo.systemVersion.toString(),
|
appInfo.systemVersion.toString(),
|
||||||
appInfo.versionName,
|
appInfo.versionName,
|
||||||
|
|
|
@ -30,7 +30,7 @@ class LoginFormPresenter @Inject constructor(
|
||||||
showVersion()
|
showVersion()
|
||||||
|
|
||||||
loginErrorHandler.onBadCredentials = {
|
loginErrorHandler.onBadCredentials = {
|
||||||
setErrorPassIncorrect()
|
setErrorPassIncorrect(it)
|
||||||
showSoftKeyboard()
|
showSoftKeyboard()
|
||||||
Timber.i("Entered wrong username or password")
|
Timber.i("Entered wrong username or password")
|
||||||
}
|
}
|
||||||
|
@ -90,10 +90,10 @@ class LoginFormPresenter @Inject constructor(
|
||||||
|
|
||||||
flowWithResource {
|
flowWithResource {
|
||||||
studentRepository.getStudentsScrapper(
|
studentRepository.getStudentsScrapper(
|
||||||
email,
|
email = email,
|
||||||
password,
|
password = password,
|
||||||
host,
|
scrapperBaseUrl = host,
|
||||||
symbol
|
symbol = symbol
|
||||||
)
|
)
|
||||||
}.onEach {
|
}.onEach {
|
||||||
when (it.status) {
|
when (it.status) {
|
||||||
|
|
|
@ -37,7 +37,7 @@ interface LoginFormView : BaseView {
|
||||||
|
|
||||||
fun setErrorPassInvalid(focus: Boolean)
|
fun setErrorPassInvalid(focus: Boolean)
|
||||||
|
|
||||||
fun setErrorPassIncorrect()
|
fun setErrorPassIncorrect(message: String?)
|
||||||
|
|
||||||
fun setErrorEmailInvalid(domain: String)
|
fun setErrorEmailInvalid(domain: String)
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,9 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||||
when (it.status) {
|
when (it.status) {
|
||||||
Status.LOADING -> Timber.d("Login student select students load started")
|
Status.LOADING -> Timber.d("Login student select students load started")
|
||||||
Status.SUCCESS -> view?.updateData(studentsWithSemesters.map { studentWithSemesters ->
|
Status.SUCCESS -> view?.updateData(studentsWithSemesters.map { studentWithSemesters ->
|
||||||
studentWithSemesters to it.data!!.any { item -> compareStudents(studentWithSemesters.student, item.student) }
|
studentWithSemesters to it.data!!.any { item ->
|
||||||
|
compareStudents(studentWithSemesters.student, item.student)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
Status.ERROR -> {
|
Status.ERROR -> {
|
||||||
errorHandler.dispatch(it.error!!)
|
errorHandler.dispatch(it.error!!)
|
||||||
|
@ -95,11 +97,8 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerStudents(studentsWithSemesters: List<StudentWithSemesters>) {
|
private fun registerStudents(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||||
flowWithResource {
|
flowWithResource { studentRepository.saveStudents(studentsWithSemesters) }
|
||||||
val savedStudents = studentRepository.saveStudents(studentsWithSemesters)
|
.onEach {
|
||||||
val firstRegistered = studentsWithSemesters.first().apply { student.id = savedStudents.first() }
|
|
||||||
studentRepository.switchStudent(firstRegistered)
|
|
||||||
}.onEach {
|
|
||||||
when (it.status) {
|
when (it.status) {
|
||||||
Status.LOADING -> view?.run {
|
Status.LOADING -> view?.run {
|
||||||
Timber.i("Registration started")
|
Timber.i("Registration started")
|
||||||
|
@ -134,7 +133,10 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||||
view?.openEmail(lastError?.message.ifNullOrBlank { "empty" })
|
view?.openEmail(lastError?.message.ifNullOrBlank { "empty" })
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun logRegisterEvent(studentsWithSemesters: List<StudentWithSemesters>, error: Throwable? = null) {
|
private fun logRegisterEvent(
|
||||||
|
studentsWithSemesters: List<StudentWithSemesters>,
|
||||||
|
error: Throwable? = null
|
||||||
|
) {
|
||||||
studentsWithSemesters.forEach { student ->
|
studentsWithSemesters.forEach { student ->
|
||||||
analytics.logEvent(
|
analytics.logEvent(
|
||||||
"registration_student_select",
|
"registration_student_select",
|
||||||
|
|
|
@ -131,7 +131,9 @@ class LuckyNumberHistoryFragment :
|
||||||
presenter.onDateSet(date.year, date.monthValue, date.dayOfMonth)
|
presenter.onDateSet(date.year, date.monthValue, date.dayOfMonth)
|
||||||
}
|
}
|
||||||
|
|
||||||
datePicker.show(this@LuckyNumberHistoryFragment.parentFragmentManager, null)
|
if (!parentFragmentManager.isStateSaved) {
|
||||||
|
datePicker.show(parentFragmentManager, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showContent(show: Boolean) {
|
override fun showContent(show: Boolean) {
|
||||||
|
|
|
@ -79,12 +79,7 @@ class MessagePreviewAdapter @Inject constructor() :
|
||||||
|
|
||||||
val readText = when {
|
val readText = when {
|
||||||
recipientCount > 1 -> {
|
recipientCount > 1 -> {
|
||||||
context.resources.getQuantityString(
|
context.getString(R.string.message_read_by, message.readBy, recipientCount)
|
||||||
R.plurals.message_read_by,
|
|
||||||
message.readBy,
|
|
||||||
message.readBy,
|
|
||||||
recipientCount
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
message.readBy == 1 -> {
|
message.readBy == 1 -> {
|
||||||
context.getString(R.string.message_read, context.getString(R.string.all_yes))
|
context.getString(R.string.message_read, context.getString(R.string.all_yes))
|
||||||
|
|
|
@ -117,6 +117,7 @@ class MessageTabPresenter @Inject constructor(
|
||||||
if (!it.data.isNullOrEmpty()) {
|
if (!it.data.isNullOrEmpty()) {
|
||||||
view?.run {
|
view?.run {
|
||||||
enableSwipe(true)
|
enableSwipe(true)
|
||||||
|
showErrorView(false)
|
||||||
showRefresh(true)
|
showRefresh(true)
|
||||||
showProgress(false)
|
showProgress(false)
|
||||||
showContent(true)
|
showContent(true)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
|
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
|
||||||
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
||||||
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
|
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
|
||||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||||
|
@ -66,6 +67,9 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
|
||||||
override val examRes: Pair<String, Drawable?>?
|
override val examRes: Pair<String, Drawable?>?
|
||||||
get() = context?.run { getString(R.string.exam_title) to getCompatDrawable(R.drawable.ic_main_exam) }
|
get() = context?.run { getString(R.string.exam_title) to getCompatDrawable(R.drawable.ic_main_exam) }
|
||||||
|
|
||||||
|
override val luckyNumberRes: Pair<String, Drawable?>?
|
||||||
|
get() = context?.run { getString(R.string.lucky_number_title) to getCompatDrawable(R.drawable.ic_more_lucky_number) }
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding = FragmentMoreBinding.bind(view)
|
binding = FragmentMoreBinding.bind(view)
|
||||||
|
@ -128,6 +132,10 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
|
||||||
(activity as? MainActivity)?.pushView(ExamFragment.newInstance())
|
(activity as? MainActivity)?.pushView(ExamFragment.newInstance())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openLuckyNumberView() {
|
||||||
|
(activity as? MainActivity)?.pushView(LuckyNumberFragment.newInstance())
|
||||||
|
}
|
||||||
|
|
||||||
override fun popView(depth: Int) {
|
override fun popView(depth: Int) {
|
||||||
(activity as? MainActivity)?.popView(depth)
|
(activity as? MainActivity)?.popView(depth)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ class MorePresenter @Inject constructor(
|
||||||
schoolAndTeachersRes?.first -> openSchoolAndTeachersView()
|
schoolAndTeachersRes?.first -> openSchoolAndTeachersView()
|
||||||
mobileDevicesRes?.first -> openMobileDevicesView()
|
mobileDevicesRes?.first -> openMobileDevicesView()
|
||||||
settingsRes?.first -> openSettingsView()
|
settingsRes?.first -> openSettingsView()
|
||||||
|
luckyNumberRes?.first -> openLuckyNumberView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +49,7 @@ class MorePresenter @Inject constructor(
|
||||||
examRes,
|
examRes,
|
||||||
homeworkRes,
|
homeworkRes,
|
||||||
noteRes,
|
noteRes,
|
||||||
|
luckyNumberRes,
|
||||||
conferencesRes,
|
conferencesRes,
|
||||||
schoolAnnouncementRes,
|
schoolAnnouncementRes,
|
||||||
schoolAndTeachersRes,
|
schoolAndTeachersRes,
|
||||||
|
|
|
@ -23,6 +23,8 @@ interface MoreView : BaseView {
|
||||||
|
|
||||||
val examRes: Pair<String, Drawable?>?
|
val examRes: Pair<String, Drawable?>?
|
||||||
|
|
||||||
|
val luckyNumberRes: Pair<String, Drawable?>?
|
||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun updateData(data: List<Pair<String, Drawable?>>)
|
fun updateData(data: List<Pair<String, Drawable?>>)
|
||||||
|
@ -46,4 +48,6 @@ interface MoreView : BaseView {
|
||||||
fun openMobileDevicesView()
|
fun openMobileDevicesView()
|
||||||
|
|
||||||
fun openExamView()
|
fun openExamView()
|
||||||
|
|
||||||
|
fun openLuckyNumberView()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package io.github.wulkanowy.ui.modules.notificationscenter
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import io.github.wulkanowy.databinding.ItemNotificationsCenterBinding
|
||||||
|
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||||
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class NotificationsCenterAdapter @Inject constructor() :
|
||||||
|
ListAdapter<Notification, NotificationsCenterAdapter.ViewHolder>(DiffUtilCallback()) {
|
||||||
|
|
||||||
|
var onItemClickListener: (NotificationType) -> Unit = {}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||||
|
ItemNotificationsCenterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val item = getItem(position)
|
||||||
|
|
||||||
|
with(holder.binding) {
|
||||||
|
notificationsCenterItemTitle.text = item.title
|
||||||
|
notificationsCenterItemContent.text = item.content
|
||||||
|
notificationsCenterItemDate.text = item.date.toFormattedString("HH:mm, d MMM")
|
||||||
|
notificationsCenterItemIcon.setImageResource(item.type.toDrawableResId())
|
||||||
|
|
||||||
|
root.setOnClickListener { onItemClickListener(item.type) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NotificationType.toDrawableResId() = when (this) {
|
||||||
|
NotificationType.NEW_CONFERENCE -> R.drawable.ic_more_conferences
|
||||||
|
NotificationType.NEW_EXAM -> R.drawable.ic_main_exam
|
||||||
|
NotificationType.NEW_GRADE_DETAILS -> R.drawable.ic_stat_grade
|
||||||
|
NotificationType.NEW_GRADE_PREDICTED -> R.drawable.ic_stat_grade
|
||||||
|
NotificationType.NEW_GRADE_FINAL -> R.drawable.ic_stat_grade
|
||||||
|
NotificationType.NEW_HOMEWORK -> R.drawable.ic_more_homework
|
||||||
|
NotificationType.NEW_LUCKY_NUMBER -> R.drawable.ic_stat_luckynumber
|
||||||
|
NotificationType.NEW_MESSAGE -> R.drawable.ic_stat_message
|
||||||
|
NotificationType.NEW_NOTE -> R.drawable.ic_stat_note
|
||||||
|
NotificationType.NEW_ANNOUNCEMENT -> R.drawable.ic_all_about
|
||||||
|
NotificationType.PUSH -> R.drawable.ic_stat_all
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewHolder(val binding: ItemNotificationsCenterBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
private class DiffUtilCallback : DiffUtil.ItemCallback<Notification>() {
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: Notification, newItem: Notification) =
|
||||||
|
oldItem == newItem
|
||||||
|
|
||||||
|
override fun areItemsTheSame(oldItem: Notification, newItem: Notification) =
|
||||||
|
oldItem.id == newItem.id
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package io.github.wulkanowy.ui.modules.notificationscenter
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import io.github.wulkanowy.databinding.FragmentNotificationsCenterBinding
|
||||||
|
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||||
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
|
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.note.NoteFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class NotificationsCenterFragment :
|
||||||
|
BaseFragment<FragmentNotificationsCenterBinding>(R.layout.fragment_notifications_center),
|
||||||
|
NotificationsCenterView, MainView.TitledView {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var presenter: NotificationsCenterPresenter
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var notificationsCenterAdapter: NotificationsCenterAdapter
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance() = NotificationsCenterFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val titleStringId: Int
|
||||||
|
get() = R.string.notifications_center_title
|
||||||
|
|
||||||
|
override val isViewEmpty: Boolean
|
||||||
|
get() = notificationsCenterAdapter.itemCount == 0
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding = FragmentNotificationsCenterBinding.bind(view)
|
||||||
|
presenter.onAttachView(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initView() {
|
||||||
|
notificationsCenterAdapter.onItemClickListener = { notificationType ->
|
||||||
|
notificationType.toDestinationFragment()
|
||||||
|
?.let { (requireActivity() as MainActivity).pushView(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
with(binding.notificationsCenterRecycler) {
|
||||||
|
layoutManager = LinearLayoutManager(context)
|
||||||
|
adapter = notificationsCenterAdapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateData(data: List<Notification>) {
|
||||||
|
notificationsCenterAdapter.submitList(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showEmpty(show: Boolean) {
|
||||||
|
binding.notificationsCenterEmpty.isVisible = show
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showProgress(show: Boolean) {
|
||||||
|
binding.notificationsCenterProgress.isVisible = show
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showContent(show: Boolean) {
|
||||||
|
binding.notificationsCenterRecycler.isVisible = show
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showErrorView(show: Boolean) {
|
||||||
|
binding.notificationCenterError.isVisible = show
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setErrorDetails(message: String) {
|
||||||
|
binding.notificationCenterErrorMessage.text = message
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
presenter.onDetachView()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NotificationType.toDestinationFragment(): Fragment? = when (this) {
|
||||||
|
NotificationType.NEW_CONFERENCE -> ConferenceFragment.newInstance()
|
||||||
|
NotificationType.NEW_EXAM -> ExamFragment.newInstance()
|
||||||
|
NotificationType.NEW_GRADE_DETAILS -> GradeFragment.newInstance()
|
||||||
|
NotificationType.NEW_GRADE_PREDICTED -> GradeFragment.newInstance()
|
||||||
|
NotificationType.NEW_GRADE_FINAL -> GradeFragment.newInstance()
|
||||||
|
NotificationType.NEW_HOMEWORK -> HomeworkFragment.newInstance()
|
||||||
|
NotificationType.NEW_LUCKY_NUMBER -> LuckyNumberFragment.newInstance()
|
||||||
|
NotificationType.NEW_MESSAGE -> MessageFragment.newInstance()
|
||||||
|
NotificationType.NEW_NOTE -> NoteFragment.newInstance()
|
||||||
|
NotificationType.NEW_ANNOUNCEMENT -> SchoolAnnouncementFragment.newInstance()
|
||||||
|
NotificationType.PUSH -> null
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package io.github.wulkanowy.ui.modules.notificationscenter
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.repositories.NotificationRepository
|
||||||
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.emitAll
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class NotificationsCenterPresenter @Inject constructor(
|
||||||
|
private val notificationRepository: NotificationRepository,
|
||||||
|
errorHandler: ErrorHandler,
|
||||||
|
studentRepository: StudentRepository
|
||||||
|
) : BasePresenter<NotificationsCenterView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
|
private lateinit var lastError: Throwable
|
||||||
|
|
||||||
|
override fun onAttachView(view: NotificationsCenterView) {
|
||||||
|
super.onAttachView(view)
|
||||||
|
view.initView()
|
||||||
|
Timber.i("Notifications centre view was initialized")
|
||||||
|
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onRetry() {
|
||||||
|
view?.run {
|
||||||
|
showErrorView(false)
|
||||||
|
showProgress(true)
|
||||||
|
}
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDetailsClick() {
|
||||||
|
view?.showErrorDetailsDialog(lastError)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadData() {
|
||||||
|
Timber.i("Loading notifications data started")
|
||||||
|
|
||||||
|
flow {
|
||||||
|
val studentId = studentRepository.getCurrentStudent(false).id
|
||||||
|
emitAll(notificationRepository.getNotifications(studentId))
|
||||||
|
}
|
||||||
|
.map { notificationList -> notificationList.sortedByDescending { it.date } }
|
||||||
|
.catch { Timber.i("Loading notifications result: An exception occurred") }
|
||||||
|
.onEach {
|
||||||
|
Timber.i("Loading notifications result: Success")
|
||||||
|
|
||||||
|
if (it.isEmpty()) {
|
||||||
|
view?.run {
|
||||||
|
showContent(false)
|
||||||
|
showProgress(false)
|
||||||
|
showEmpty(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
view?.run {
|
||||||
|
showContent(true)
|
||||||
|
showProgress(false)
|
||||||
|
showEmpty(false)
|
||||||
|
updateData(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launch()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showErrorViewOnError(message: String, error: Throwable) {
|
||||||
|
view?.run {
|
||||||
|
if (isViewEmpty) {
|
||||||
|
lastError = error
|
||||||
|
setErrorDetails(message)
|
||||||
|
showErrorView(true)
|
||||||
|
showEmpty(false)
|
||||||
|
} else showError(message, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package io.github.wulkanowy.ui.modules.notificationscenter
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import io.github.wulkanowy.ui.base.BaseView
|
||||||
|
|
||||||
|
interface NotificationsCenterView : BaseView {
|
||||||
|
|
||||||
|
val isViewEmpty: Boolean
|
||||||
|
|
||||||
|
fun initView()
|
||||||
|
|
||||||
|
fun updateData(data: List<Notification>)
|
||||||
|
|
||||||
|
fun showProgress(show: Boolean)
|
||||||
|
|
||||||
|
fun showEmpty(show: Boolean)
|
||||||
|
|
||||||
|
fun showContent(show: Boolean)
|
||||||
|
|
||||||
|
fun showErrorView(show: Boolean)
|
||||||
|
|
||||||
|
fun setErrorDetails(message: String)
|
||||||
|
}
|
|
@ -38,7 +38,7 @@ class SchoolAnnouncementDialog : DialogFragment() {
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
) = DialogSchoolAnnouncementBinding.inflate(inflater).apply { binding = this }.root
|
) = DialogSchoolAnnouncementBinding.inflate(inflater).also { binding = it }.root
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
|
@ -64,6 +64,7 @@ class SchoolAnnouncementPresenter @Inject constructor(
|
||||||
view?.run {
|
view?.run {
|
||||||
enableSwipe(true)
|
enableSwipe(true)
|
||||||
showRefresh(true)
|
showRefresh(true)
|
||||||
|
showErrorView(false)
|
||||||
showProgress(false)
|
showProgress(false)
|
||||||
showContent(true)
|
showContent(true)
|
||||||
updateData(it.data)
|
updateData(it.data)
|
||||||
|
|
|
@ -10,9 +10,12 @@ import android.provider.Settings
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.thelittlefireman.appkillermanager.AppKillerManager
|
import com.thelittlefireman.appkillermanager.AppKillerManager
|
||||||
import com.thelittlefireman.appkillermanager.exceptions.NoActionFoundException
|
import com.thelittlefireman.appkillermanager.exceptions.NoActionFoundException
|
||||||
|
@ -43,6 +46,21 @@ class NotificationsFragment : PreferenceFragmentCompat(),
|
||||||
|
|
||||||
override val titleStringId get() = R.string.pref_settings_notifications_title
|
override val titleStringId get() = R.string.pref_settings_notifications_title
|
||||||
|
|
||||||
|
override val isNotificationPermissionGranted: Boolean
|
||||||
|
get() {
|
||||||
|
val packageNameList =
|
||||||
|
NotificationManagerCompat.getEnabledListenerPackages(requireContext())
|
||||||
|
val appPackageName = requireContext().packageName
|
||||||
|
|
||||||
|
return appPackageName in packageNameList
|
||||||
|
}
|
||||||
|
|
||||||
|
private val notificationSettingsContract =
|
||||||
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
|
||||||
|
presenter.onNotificationPermissionResult()
|
||||||
|
}
|
||||||
|
|
||||||
override fun initView(showDebugNotificationSwitch: Boolean) {
|
override fun initView(showDebugNotificationSwitch: Boolean) {
|
||||||
findPreference<Preference>(getString(R.string.pref_key_notification_debug))?.isVisible =
|
findPreference<Preference>(getString(R.string.pref_key_notification_debug))?.isVisible =
|
||||||
showDebugNotificationSwitch
|
showDebugNotificationSwitch
|
||||||
|
@ -57,13 +75,12 @@ class NotificationsFragment : PreferenceFragmentCompat(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findPreference<Preference>(getString(R.string.pref_key_notifications_system_settings))?.run {
|
findPreference<Preference>(getString(R.string.pref_key_notifications_system_settings))
|
||||||
setOnPreferenceClickListener {
|
?.setOnPreferenceClickListener {
|
||||||
presenter.onOpenSystemSettingsClicked()
|
presenter.onOpenSystemSettingsClicked()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateRecyclerView(
|
override fun onCreateRecyclerView(
|
||||||
inflater: LayoutInflater?,
|
inflater: LayoutInflater?,
|
||||||
|
@ -157,6 +174,25 @@ class NotificationsFragment : PreferenceFragmentCompat(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openNotificationPermissionDialog() {
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(getString(R.string.pref_notification_piggyback_popup_title))
|
||||||
|
.setMessage(getString(R.string.pref_notification_piggyback_popup_description))
|
||||||
|
.setPositiveButton(getString(R.string.pref_notification_piggyback_popup_positive)) { _, _ ->
|
||||||
|
notificationSettingsContract.launch(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"))
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
|
setNotificationPiggybackPreferenceChecked(false)
|
||||||
|
}
|
||||||
|
.setOnDismissListener { setNotificationPiggybackPreferenceChecked(false) }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean) {
|
||||||
|
findPreference<SwitchPreferenceCompat>(getString(R.string.pref_key_notifications_piggyback))?.isChecked =
|
||||||
|
isChecked
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||||
|
|
|
@ -31,6 +31,9 @@ class NotificationsPresenter @Inject constructor(
|
||||||
)
|
)
|
||||||
initView(appInfo.isDebug)
|
initView(appInfo.isDebug)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkNotificationPiggybackState()
|
||||||
|
|
||||||
Timber.i("Settings notifications view was initialized")
|
Timber.i("Settings notifications view was initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +42,7 @@ class NotificationsPresenter @Inject constructor(
|
||||||
|
|
||||||
preferencesRepository.apply {
|
preferencesRepository.apply {
|
||||||
when (key) {
|
when (key) {
|
||||||
isUpcomingLessonsNotificationsEnableKey -> {
|
isUpcomingLessonsNotificationsEnableKey, isUpcomingLessonsNotificationsPersistentKey -> {
|
||||||
if (!isUpcomingLessonsNotificationsEnable) {
|
if (!isUpcomingLessonsNotificationsEnable) {
|
||||||
timetableNotificationHelper.cancelNotification()
|
timetableNotificationHelper.cancelNotification()
|
||||||
}
|
}
|
||||||
|
@ -47,6 +50,11 @@ class NotificationsPresenter @Inject constructor(
|
||||||
isDebugNotificationEnableKey -> {
|
isDebugNotificationEnableKey -> {
|
||||||
chuckerCollector.showNotification = isDebugNotificationEnable
|
chuckerCollector.showNotification = isDebugNotificationEnable
|
||||||
}
|
}
|
||||||
|
isNotificationPiggybackEnabledKey -> {
|
||||||
|
if (isNotificationPiggybackEnabled && view?.isNotificationPermissionGranted == false) {
|
||||||
|
view?.openNotificationPermissionDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
analytics.logEvent("setting_changed", "name" to key)
|
analytics.logEvent("setting_changed", "name" to key)
|
||||||
|
@ -59,4 +67,18 @@ class NotificationsPresenter @Inject constructor(
|
||||||
fun onOpenSystemSettingsClicked() {
|
fun onOpenSystemSettingsClicked() {
|
||||||
view?.openSystemSettings()
|
view?.openSystemSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onNotificationPermissionResult() {
|
||||||
|
view?.run {
|
||||||
|
setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkNotificationPiggybackState() {
|
||||||
|
if (preferencesRepository.isNotificationPiggybackEnabled) {
|
||||||
|
view?.run {
|
||||||
|
setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import io.github.wulkanowy.ui.base.BaseView
|
||||||
|
|
||||||
interface NotificationsView : BaseView {
|
interface NotificationsView : BaseView {
|
||||||
|
|
||||||
|
val isNotificationPermissionGranted: Boolean
|
||||||
|
|
||||||
fun initView(showDebugNotificationSwitch: Boolean)
|
fun initView(showDebugNotificationSwitch: Boolean)
|
||||||
|
|
||||||
fun showFixSyncDialog()
|
fun showFixSyncDialog()
|
||||||
|
@ -11,4 +13,8 @@ interface NotificationsView : BaseView {
|
||||||
fun openSystemSettings()
|
fun openSystemSettings()
|
||||||
|
|
||||||
fun enableNotification(notificationKey: String, enable: Boolean)
|
fun enableNotification(notificationKey: String, enable: Boolean)
|
||||||
|
|
||||||
|
fun openNotificationPermissionDialog()
|
||||||
|
|
||||||
|
fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package io.github.wulkanowy.ui.modules.timetable
|
package io.github.wulkanowy.ui.modules.timetable
|
||||||
|
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
|
@ -151,8 +152,8 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
|
|
||||||
if (lesson.isStudentPlan && showTimers) {
|
if (lesson.isStudentPlan && showTimers) {
|
||||||
timers[position] = timer(period = 1000) {
|
timers[position] = timer(period = 1000) {
|
||||||
if (ViewCompat.isAttachedToWindow(root)) {
|
Handler(Looper.getMainLooper()).post {
|
||||||
root.post { updateTimeLeft(binding, lesson, position) }
|
updateTimeLeft(binding, lesson, position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -176,8 +177,8 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
|
|
||||||
private fun updateTimeLeft(binding: ItemTimetableBinding, lesson: Timetable, position: Int) {
|
private fun updateTimeLeft(binding: ItemTimetableBinding, lesson: Timetable, position: Int) {
|
||||||
val isShowTimeUntil = lesson.isShowTimeUntil(getPreviousLesson(position))
|
val isShowTimeUntil = lesson.isShowTimeUntil(getPreviousLesson(position))
|
||||||
val until = lesson.until
|
val until = lesson.until.plusMinutes(1)
|
||||||
val left = lesson.left
|
val left = lesson.left?.plusMinutes(1)
|
||||||
val isJustFinished = lesson.isJustFinished
|
val isJustFinished = lesson.isJustFinished
|
||||||
|
|
||||||
with(binding) {
|
with(binding) {
|
||||||
|
@ -190,17 +191,10 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
visibility = VISIBLE
|
visibility = VISIBLE
|
||||||
text = context.getString(
|
text = context.getString(
|
||||||
R.string.timetable_time_until,
|
R.string.timetable_time_until,
|
||||||
if (until.seconds <= 60) {
|
|
||||||
context.getString(
|
|
||||||
R.string.timetable_seconds,
|
|
||||||
until.seconds.toString(10)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
context.getString(
|
context.getString(
|
||||||
R.string.timetable_minutes,
|
R.string.timetable_minutes,
|
||||||
until.toMinutes().toString(10)
|
until.toMinutes().toString(10)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,17 +206,10 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
visibility = VISIBLE
|
visibility = VISIBLE
|
||||||
text = context.getString(
|
text = context.getString(
|
||||||
R.string.timetable_time_left,
|
R.string.timetable_time_left,
|
||||||
if (left.seconds < 60) {
|
|
||||||
context.getString(
|
|
||||||
R.string.timetable_seconds,
|
|
||||||
left.seconds.toString(10)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
context.getString(
|
context.getString(
|
||||||
R.string.timetable_minutes,
|
R.string.timetable_minutes,
|
||||||
left.toMinutes().toString(10)
|
left.toMinutes().toString()
|
||||||
)
|
)
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -360,7 +347,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||||
private fun updateTeacherColor(teacherTextView: TextView, lesson: Timetable) {
|
private fun updateTeacherColor(teacherTextView: TextView, lesson: Timetable) {
|
||||||
teacherTextView.setTextColor(
|
teacherTextView.setTextColor(
|
||||||
teacherTextView.context.getThemeAttrColor(
|
teacherTextView.context.getThemeAttrColor(
|
||||||
if (lesson.teacherOld.isNotBlank() && lesson.teacherOld != lesson.teacher) R.attr.colorTimetableChange
|
if (lesson.teacherOld.isNotBlank()) R.attr.colorTimetableChange
|
||||||
else android.R.attr.textColorSecondary
|
else android.R.attr.textColorSecondary
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,7 +13,6 @@ import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Timetable
|
import io.github.wulkanowy.data.db.entities.Timetable
|
||||||
import io.github.wulkanowy.databinding.DialogTimetableBinding
|
import io.github.wulkanowy.databinding.DialogTimetableBinding
|
||||||
import io.github.wulkanowy.utils.capitalise
|
import io.github.wulkanowy.utils.capitalise
|
||||||
import io.github.wulkanowy.utils.decapitalise
|
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
|
@ -52,7 +51,7 @@ class TimetableDialog : DialogFragment() {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
with(lesson) {
|
with(lesson) {
|
||||||
setInfo(info, teacher, canceled, changes)
|
setInfo(info, canceled, changes)
|
||||||
setSubject(subject, subjectOld)
|
setSubject(subject, subjectOld)
|
||||||
setTeacher(teacher, teacherOld)
|
setTeacher(teacher, teacherOld)
|
||||||
setGroup(group)
|
setGroup(group)
|
||||||
|
@ -80,7 +79,7 @@ class TimetableDialog : DialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("DefaultLocale")
|
@SuppressLint("DefaultLocale")
|
||||||
private fun setInfo(info: String, teacher: String, canceled: Boolean, changes: Boolean) {
|
private fun setInfo(info: String, canceled: Boolean, changes: Boolean) {
|
||||||
with(binding) {
|
with(binding) {
|
||||||
when {
|
when {
|
||||||
info.isNotBlank() -> {
|
info.isNotBlank() -> {
|
||||||
|
@ -90,20 +89,26 @@ class TimetableDialog : DialogFragment() {
|
||||||
R.attr.colorPrimary
|
R.attr.colorPrimary
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
timetableDialogChangesValue.setTextColor(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
timetableDialogChangesValue.setTextColor(
|
||||||
|
requireContext().getThemeAttrColor(
|
||||||
|
R.attr.colorPrimary
|
||||||
|
)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
timetableDialogChangesTitle.setTextColor(
|
timetableDialogChangesTitle.setTextColor(
|
||||||
requireContext().getThemeAttrColor(
|
requireContext().getThemeAttrColor(
|
||||||
R.attr.colorTimetableChange
|
R.attr.colorTimetableChange
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
timetableDialogChangesValue.setTextColor(requireContext().getThemeAttrColor(R.attr.colorTimetableChange))
|
timetableDialogChangesValue.setTextColor(
|
||||||
|
requireContext().getThemeAttrColor(
|
||||||
|
R.attr.colorTimetableChange
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
timetableDialogChangesValue.text = when {
|
timetableDialogChangesValue.text = when {
|
||||||
canceled && !changes -> "Lekcja odwołana: $info"
|
canceled && !changes -> "Lekcja odwołana: $info"
|
||||||
changes && teacher.isNotBlank() -> "Zastępstwo: $teacher"
|
|
||||||
changes && teacher.isBlank() -> "Zastępstwo, ${info.decapitalise()}"
|
|
||||||
else -> info.capitalise()
|
else -> info.capitalise()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,6 +136,15 @@ class TimetableDialog : DialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
teacherOld.isNotBlank() && teacherOld == teacher -> {
|
||||||
|
timetableDialogTeacherValue.run {
|
||||||
|
visibility = GONE
|
||||||
|
}
|
||||||
|
timetableDialogTeacherNewValue.run {
|
||||||
|
visibility = VISIBLE
|
||||||
|
text = teacher
|
||||||
|
}
|
||||||
|
}
|
||||||
teacher.isNotBlank() -> timetableDialogTeacherValue.text = teacher
|
teacher.isNotBlank() -> timetableDialogTeacherValue.text = teacher
|
||||||
else -> {
|
else -> {
|
||||||
timetableDialogTeacherTitle.visibility = GONE
|
timetableDialogTeacherTitle.visibility = GONE
|
||||||
|
|
|
@ -44,7 +44,13 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
|
||||||
companion object {
|
companion object {
|
||||||
private const val SAVED_DATE_KEY = "CURRENT_DATE"
|
private const val SAVED_DATE_KEY = "CURRENT_DATE"
|
||||||
|
|
||||||
fun newInstance() = TimetableFragment()
|
private const val ARGUMENT_DATE_KEY = "ARGUMENT_DATE"
|
||||||
|
|
||||||
|
fun newInstance(date: LocalDate? = null) = TimetableFragment().apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
date?.let { putLong(ARGUMENT_DATE_KEY, it.toEpochDay()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val titleStringId get() = R.string.timetable_title
|
override val titleStringId get() = R.string.timetable_title
|
||||||
|
@ -62,7 +68,11 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding = FragmentTimetableBinding.bind(view)
|
binding = FragmentTimetableBinding.bind(view)
|
||||||
messageContainer = binding.timetableRecycler
|
messageContainer = binding.timetableRecycler
|
||||||
presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY))
|
|
||||||
|
val initDate = savedInstanceState?.getLong(SAVED_DATE_KEY)
|
||||||
|
?: arguments?.getLong(ARGUMENT_DATE_KEY)?.takeUnless { it == 0L }
|
||||||
|
|
||||||
|
presenter.onAttachView(this, initDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
|
@ -202,7 +212,9 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
|
||||||
presenter.onDateSet(date.year, date.monthValue, date.dayOfMonth)
|
presenter.onDateSet(date.year, date.monthValue, date.dayOfMonth)
|
||||||
}
|
}
|
||||||
|
|
||||||
datePicker.show(this@TimetableFragment.parentFragmentManager, null)
|
if (!parentFragmentManager.isStateSaved) {
|
||||||
|
datePicker.show(parentFragmentManager, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openAdditionalLessonsView() {
|
override fun openAdditionalLessonsView() {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue