forked from github/wulkanowy-mirror
Compare commits
66 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 | |||
3d0dcead50 | |||
b64b41c11c | |||
77c5330f91 | |||
2b55ec02ff | |||
49ebae6e63 | |||
44a9db48a6 | |||
0008a72be1 | |||
a43acaaa07 | |||
e6c9abb4e5 | |||
3b9451184c | |||
45d1727dbe | |||
8d7b611c44 | |||
c3adb9b6d6 | |||
d87283eb31 | |||
d139c22782 | |||
e557021ad9 | |||
37af5de25c |
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 }}
|
||||
run: |
|
||||
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
|
||||
- name: Upload apk to google play
|
||||
env:
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||
PLAY_SERVICE_ACCOUNT_EMAIL: ${{ secrets.PLAY_SERVICE_ACCOUNT_EMAIL }}
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
run: ./gradlew publishPlayRelease -PenableFirebase --stacktrace;
|
||||
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
|
||||
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
|
||||
|
||||
deploy-app-gallery:
|
||||
name: Deploy to AppGallery
|
||||
@ -60,7 +59,6 @@ jobs:
|
||||
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
||||
run: |
|
||||
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
|
||||
- name: Prepare credentials
|
||||
env:
|
||||
@ -68,7 +66,7 @@ jobs:
|
||||
run: echo $AGC_CREDENTIALS > ./app/src/release/agconnect-credentials.json
|
||||
- name: Build and publish HMS version
|
||||
env:
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
run: ./gradlew assembleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace
|
||||
run: ./gradlew bundleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace
|
||||
|
2
LICENSE
2
LICENSE
@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2019 Wulkanowy
|
||||
Copyright 2021 Wulkanowy
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -21,8 +21,8 @@ android {
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode 93
|
||||
versionName "1.2.0"
|
||||
versionCode 97
|
||||
versionName "1.3.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
@ -96,10 +96,20 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
playConfigs {
|
||||
play { enabled.set(true) }
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
bundle {
|
||||
language {
|
||||
enableSplit = false
|
||||
}
|
||||
}
|
||||
|
||||
testOptions.unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
@ -130,25 +140,24 @@ kapt {
|
||||
}
|
||||
|
||||
play {
|
||||
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
|
||||
serviceAccountCredentials = file('key.p12')
|
||||
defaultToAppBundles = false
|
||||
track = 'beta'
|
||||
track = 'production'
|
||||
updatePriority = 3
|
||||
enabled.set(false)
|
||||
}
|
||||
|
||||
huaweiPublish {
|
||||
instances {
|
||||
hmsRelease {
|
||||
credentialsPath = "$rootDir/app/src/release/agconnect-credentials.json"
|
||||
buildFormat = "apk"
|
||||
buildFormat = "aab"
|
||||
deployType = "draft"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
work_manager = "2.5.0"
|
||||
work_manager = "2.6.0"
|
||||
android_hilt = "1.0.0"
|
||||
room = "2.3.0"
|
||||
chucker = "3.5.2"
|
||||
@ -157,11 +166,11 @@ ext {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "io.github.wulkanowy:sdk:1.2.0"
|
||||
implementation "io.github.wulkanowy:sdk:1.3.0"
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
|
||||
|
||||
implementation "androidx.core:core-ktx:1.6.0"
|
||||
implementation "androidx.activity:activity-ktx:1.3.1"
|
||||
@ -174,10 +183,10 @@ dependencies {
|
||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||
implementation "androidx.viewpager:viewpager:1.0.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 "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.lopspower:CircularImageView:4.2.0'
|
||||
|
||||
@ -211,14 +220,14 @@ dependencies {
|
||||
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
||||
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-messaging:'
|
||||
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
||||
playImplementation 'com.google.android.play:core:1.10.0'
|
||||
playImplementation 'com.google.android.play:core:1.10.2'
|
||||
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
||||
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:6.1.1.300'
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:6.2.0.301'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.0.300'
|
||||
|
||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
||||
@ -228,7 +237,7 @@ dependencies {
|
||||
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
testImplementation "io.mockk:mockk:$mockk"
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1'
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2'
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
testImplementation 'org.robolectric:robolectric:4.6.1'
|
||||
|
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">
|
||||
<activity
|
||||
android:name=".ui.modules.splash.SplashActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/WulkanowyTheme.SplashScreen"
|
||||
tools:ignore="LockedOrientationActivity">
|
||||
@ -74,6 +75,7 @@
|
||||
<activity
|
||||
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||
<intent-filter>
|
||||
@ -83,6 +85,7 @@
|
||||
<activity
|
||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||
<intent-filter>
|
||||
@ -93,6 +96,22 @@
|
||||
<service
|
||||
android:name=".services.widgets.TimetableWidgetService"
|
||||
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
|
||||
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
|
||||
@ -107,6 +126,7 @@
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
|
||||
android:exported="true"
|
||||
android:label="@string/lucky_number_title">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
@ -119,11 +139,9 @@
|
||||
<receiver android:name=".services.alarm.TimetableNotificationReceiver" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.work.impl.WorkManagerInitializer"
|
||||
android:authorities="${applicationId}.workmanager-init"
|
||||
android:exported="false"
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
tools:node="remove" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
|
@ -8,8 +8,8 @@ import androidx.preference.PreferenceManager
|
||||
import com.chuckerteam.chucker.api.ChuckerCollector
|
||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||
import com.chuckerteam.chucker.api.RetentionManager
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||
import com.squareup.moshi.Moshi
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@ -202,4 +202,8 @@ internal class RepositoryModule {
|
||||
@Singleton
|
||||
@Provides
|
||||
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.CompletedLessonsDao
|
||||
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.GradeDao
|
||||
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.MobileDeviceDao
|
||||
import io.github.wulkanowy.data.db.dao.NoteDao
|
||||
import io.github.wulkanowy.data.db.dao.NotificationDao
|
||||
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||
import io.github.wulkanowy.data.db.dao.SchoolDao
|
||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||
@ -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.CompletedLesson
|
||||
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.Grade
|
||||
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.MobileDevice
|
||||
import io.github.wulkanowy.data.db.entities.Note
|
||||
import io.github.wulkanowy.data.db.entities.Notification
|
||||
import io.github.wulkanowy.data.db.entities.Recipient
|
||||
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
||||
import io.github.wulkanowy.data.db.entities.School
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||
@ -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.Migration39
|
||||
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.Migration6
|
||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||
@ -134,6 +137,7 @@ import javax.inject.Singleton
|
||||
StudentInfo::class,
|
||||
TimetableHeader::class,
|
||||
SchoolAnnouncement::class,
|
||||
Notification::class
|
||||
],
|
||||
version = AppDatabase.VERSION_SCHEMA,
|
||||
exportSchema = true
|
||||
@ -142,7 +146,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 39
|
||||
const val VERSION_SCHEMA = 40
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||
Migration2(),
|
||||
@ -183,6 +187,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration37(),
|
||||
Migration38(),
|
||||
Migration39(),
|
||||
Migration40()
|
||||
)
|
||||
|
||||
fun newInstance(
|
||||
@ -252,4 +257,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract val timetableHeaderDao: TimetableHeaderDao
|
||||
|
||||
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
|
||||
@Dao
|
||||
interface StudentDao {
|
||||
abstract class StudentDao {
|
||||
|
||||
@Insert(onConflict = ABORT)
|
||||
suspend fun insertAll(student: List<Student>): List<Long>
|
||||
abstract suspend fun insertAll(student: List<Student>): List<Long>
|
||||
|
||||
@Delete
|
||||
suspend fun delete(student: Student)
|
||||
abstract suspend fun delete(student: Student)
|
||||
|
||||
@Update(entity = Student::class)
|
||||
suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
||||
abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
||||
|
||||
@Query("SELECT * FROM Students WHERE is_current = 1")
|
||||
suspend fun loadCurrent(): Student?
|
||||
abstract suspend fun loadCurrent(): Student?
|
||||
|
||||
@Query("SELECT * FROM Students WHERE id = :id")
|
||||
suspend fun loadById(id: Long): Student?
|
||||
abstract suspend fun loadById(id: Long): Student?
|
||||
|
||||
@Query("SELECT * FROM Students")
|
||||
suspend fun loadAll(): List<Student>
|
||||
abstract suspend fun loadAll(): List<Student>
|
||||
|
||||
@Transaction
|
||||
@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")
|
||||
suspend fun updateCurrent(id: Long)
|
||||
abstract suspend fun updateCurrent(id: Long)
|
||||
|
||||
@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.ui.modules.main.MainView
|
||||
|
||||
sealed interface Notification {
|
||||
sealed interface NotificationData {
|
||||
val type: NotificationType
|
||||
val startMenu: MainView.Section
|
||||
val icon: Int
|
||||
@ -14,7 +14,7 @@ sealed interface Notification {
|
||||
val contentStringRes: Int
|
||||
}
|
||||
|
||||
data class MultipleNotifications(
|
||||
data class MultipleNotificationsData(
|
||||
override val type: NotificationType,
|
||||
override val startMenu: MainView.Section,
|
||||
@DrawableRes override val icon: Int,
|
||||
@ -23,9 +23,9 @@ data class MultipleNotifications(
|
||||
|
||||
@PluralsRes val summaryStringRes: Int,
|
||||
val lines: List<String>,
|
||||
) : Notification
|
||||
) : NotificationData
|
||||
|
||||
data class OneNotification(
|
||||
data class OneNotificationData(
|
||||
override val type: NotificationType,
|
||||
override val startMenu: MainView.Section,
|
||||
@DrawableRes override val icon: Int,
|
||||
@ -33,4 +33,4 @@ data class OneNotification(
|
||||
@StringRes override val contentStringRes: Int,
|
||||
|
||||
val contentValues: List<String>,
|
||||
) : Notification
|
||||
) : NotificationData
|
@ -32,10 +32,23 @@ class AttendanceRepository @Inject constructor(
|
||||
|
||||
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,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) },
|
||||
shouldFetch = {
|
||||
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 = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getAttendance(start.monday, end.sunday, semester.semesterId)
|
||||
@ -50,12 +63,17 @@ class AttendanceRepository @Inject constructor(
|
||||
filterResult = { it.filter { item -> item.date in start..end } }
|
||||
)
|
||||
|
||||
suspend fun excuseForAbsence(student: Student, semester: Semester, absenceList: List<Attendance>, reason: String? = null) {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).excuseForAbsence(absenceList.map { attendance ->
|
||||
suspend fun excuseForAbsence(
|
||||
student: Student, semester: Semester,
|
||||
absenceList: List<Attendance>, reason: String? = null
|
||||
) {
|
||||
val items = absenceList.map { attendance ->
|
||||
Absent(
|
||||
date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)),
|
||||
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,
|
||||
semester: Semester,
|
||||
subjectId: Int,
|
||||
forceRefresh: Boolean
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
it.isEmpty() || forceRefresh
|
||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
||||
fetch = {
|
||||
|
@ -28,10 +28,28 @@ class CompletedLessonsRepository @Inject constructor(
|
||||
|
||||
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,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { completedLessonsDb.loadAll(semester.studentId, semester.diaryId, start.monday, end.sunday) },
|
||||
shouldFetch = {
|
||||
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 = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getCompletedLessons(start.monday, end.sunday)
|
||||
|
@ -35,12 +35,12 @@ class ConferenceRepository @Inject constructor(
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)
|
||||
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
it.isEmpty() || forceRefresh
|
||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
|
||||
|
@ -36,14 +36,14 @@ class ExamRepository @Inject constructor(
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false
|
||||
notify: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, semester, start, end)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
examDb.loadAll(
|
||||
|
@ -33,11 +33,16 @@ class GradeRepository @Inject constructor(
|
||||
|
||||
private val cacheKey = "grade"
|
||||
|
||||
fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
fun getGrades(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { (details, summaries) ->
|
||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
||||
@ -59,8 +64,14 @@ class GradeRepository @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
private suspend fun refreshGradeDetails(student: Student, oldGrades: List<Grade>, newDetails: List<Grade>, notify: Boolean) {
|
||||
val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate()
|
||||
private suspend fun refreshGradeDetails(
|
||||
student: Student,
|
||||
oldGrades: List<Grade>,
|
||||
newDetails: List<Grade>,
|
||||
notify: Boolean
|
||||
) {
|
||||
val notifyBreakDate = oldGrades.maxByOrNull {it.date }
|
||||
?.date ?: student.registrationDate.toLocalDate()
|
||||
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
||||
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
||||
if (it.date >= notifyBreakDate) it.apply {
|
||||
@ -70,10 +81,14 @@ class GradeRepository @Inject constructor(
|
||||
})
|
||||
}
|
||||
|
||||
private suspend fun refreshGradeSummaries(oldSummaries: List<GradeSummary>, newSummary: List<GradeSummary>, notify: Boolean) {
|
||||
private suspend fun refreshGradeSummaries(
|
||||
oldSummaries: List<GradeSummary>,
|
||||
newSummary: List<GradeSummary>,
|
||||
notify: Boolean
|
||||
) {
|
||||
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
||||
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
||||
val oldSummary = oldSummaries.find { oldSummary -> oldSummary.subject == summary.subject }
|
||||
val oldSummary = oldSummaries.find { old -> old.subject == summary.subject }
|
||||
summary.isPredictedGradeNotified = when {
|
||||
summary.predictedGrade.isEmpty() -> true
|
||||
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
||||
|
@ -39,9 +39,19 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
private val semesterCacheKey = "grade_stats_semester"
|
||||
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,
|
||||
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) },
|
||||
fetch = {
|
||||
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,
|
||||
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) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
@ -94,10 +114,12 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
val itemsWithAverage = items.map { item ->
|
||||
item.copy().apply {
|
||||
val denominator = item.amounts.sum()
|
||||
average = if (denominator == 0) "" else (item.amounts.mapIndexed { gradeValue, amount ->
|
||||
(gradeValue + 1) * amount
|
||||
}.sum().toDouble() / denominator).let {
|
||||
"%.2f".format(Locale.FRANCE, it)
|
||||
average = if (denominator == 0) "" else {
|
||||
(item.amounts.mapIndexed { gradeValue, amount ->
|
||||
(gradeValue + 1) * amount
|
||||
}.sum().toDouble() / denominator).let {
|
||||
"%.2f".format(Locale.FRANCE, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -109,7 +131,9 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
|
||||
studentGrade = 0
|
||||
).apply {
|
||||
average = itemsWithAverage.mapNotNull { it.average.replace(",", ".").toDoubleOrNull() }.average().let {
|
||||
average = itemsWithAverage.mapNotNull {
|
||||
it.average.replace(",", ".").toDoubleOrNull()
|
||||
}.average().let {
|
||||
"%.2f".format(Locale.FRANCE, it)
|
||||
}
|
||||
}).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,
|
||||
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) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
|
@ -30,16 +30,19 @@ class HomeworkRepository @Inject constructor(
|
||||
private val cacheKey = "homework"
|
||||
|
||||
fun getHomework(
|
||||
student: Student, semester: Semester,
|
||||
start: LocalDate, end: LocalDate,
|
||||
forceRefresh: Boolean, notify: Boolean = false
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, semester, start, end)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
homeworkDb.loadAll(
|
||||
|
@ -23,11 +23,17 @@ class LuckyNumberRepository @Inject constructor(
|
||||
|
||||
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,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
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 ->
|
||||
if (new != old) {
|
||||
old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
||||
@ -41,9 +47,11 @@ class LuckyNumberRepository @Inject constructor(
|
||||
fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) =
|
||||
luckyNumberDb.getAll(student.studentId, start, end)
|
||||
|
||||
suspend fun getNotNotifiedLuckyNumber(student: Student) = luckyNumberDb.load(student.studentId, now()).map {
|
||||
if (it?.isNotified == false) it else null
|
||||
}.first()
|
||||
suspend fun getNotNotifiedLuckyNumber(student: Student) =
|
||||
luckyNumberDb.load(student.studentId, now()).map {
|
||||
if (it?.isNotified == false) it else null
|
||||
}.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")
|
||||
fun getMessages(
|
||||
student: Student, semester: Semester,
|
||||
folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
folder: MessageFolder,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
): Flow<Resource<List<Message>>> = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(
|
||||
getRefreshKey(cacheKey, student, folder)
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, student, folder)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
||||
fetch = {
|
||||
@ -77,7 +81,8 @@ class MessageRepository @Inject constructor(
|
||||
)
|
||||
|
||||
private fun getMessagesWithReadByChange(
|
||||
old: List<Message>, new: List<Message>,
|
||||
old: List<Message>,
|
||||
new: List<Message>,
|
||||
setNotified: Boolean
|
||||
): List<Message> {
|
||||
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
|
||||
@ -96,7 +101,9 @@ class MessageRepository @Inject constructor(
|
||||
}
|
||||
|
||||
fun getMessage(
|
||||
student: Student, message: Message, markAsRead: Boolean = false
|
||||
student: Student,
|
||||
message: Message,
|
||||
markAsRead: Boolean = false,
|
||||
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
|
||||
shouldFetch = {
|
||||
checkNotNull(it, { "This message no longer exist!" })
|
||||
@ -135,8 +142,10 @@ class MessageRepository @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun sendMessage(
|
||||
student: Student, subject: String, content: String,
|
||||
recipients: List<Recipient>
|
||||
student: Student,
|
||||
subject: String,
|
||||
content: String,
|
||||
recipients: List<Recipient>,
|
||||
): SentMessage = sdk.init(student).sendMessage(
|
||||
subject = subject,
|
||||
content = content,
|
||||
|
@ -28,9 +28,16 @@ class MobileDeviceRepository @Inject constructor(
|
||||
|
||||
private val cacheKey = "devices"
|
||||
|
||||
fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
fun getDevices(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
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) },
|
||||
fetch = {
|
||||
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.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -28,9 +27,19 @@ class NoteRepository @Inject constructor(
|
||||
|
||||
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,
|
||||
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) },
|
||||
fetch = {
|
||||
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.grade.GradeAverageMode
|
||||
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.toTimestamp
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@ -108,6 +107,22 @@ class PreferencesRepository @Inject constructor(
|
||||
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 isDebugNotificationEnable: Boolean
|
||||
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
||||
@ -176,10 +191,8 @@ class PreferencesRepository @Inject constructor(
|
||||
)
|
||||
|
||||
var lasSyncDate: LocalDateTime
|
||||
get() = getLong(
|
||||
R.string.pref_key_last_sync_date,
|
||||
R.string.pref_default_last_sync_date
|
||||
).toLocalDateTime()
|
||||
get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date)
|
||||
.toLocalDateTime()
|
||||
set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply()
|
||||
|
||||
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()
|
||||
|
||||
var inAppReviewDate: LocalDate?
|
||||
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }?.toLocalDate()
|
||||
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp()).apply()
|
||||
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }
|
||||
?.toLocalDate()
|
||||
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp())
|
||||
.apply()
|
||||
|
||||
var isAppReviewDone: Boolean
|
||||
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)
|
||||
|
@ -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.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import javax.inject.Inject
|
||||
@ -15,26 +17,34 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class RecipientRepository @Inject constructor(
|
||||
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) {
|
||||
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
|
||||
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||
|
||||
recipientDb.deleteAll(old uniqueSubtract new)
|
||||
recipientDb.insertAll(new uniqueSubtract old)
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
|
||||
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> {
|
||||
return recipientDb.loadAll(unit.studentId, unit.unitId, role).ifEmpty {
|
||||
refreshRecipients(student, unit, role)
|
||||
val cached = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
return if (cached.isEmpty() || isExpired) {
|
||||
refreshRecipients(student, unit, role)
|
||||
recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||
}
|
||||
} else cached
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
suspend fun sendRecoverRequest(url: String, symbol: String, email: String, reCaptchaResponse: String): String {
|
||||
return sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
||||
}
|
||||
suspend fun sendRecoverRequest(
|
||||
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.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
@ -12,7 +11,6 @@ import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -30,17 +28,15 @@ class SchoolAnnouncementRepository @Inject constructor(
|
||||
|
||||
fun getSchoolAnnouncements(
|
||||
student: Student,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false
|
||||
forceRefresh: Boolean, notify: Boolean = false
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
it.isEmpty() || forceRefresh
|
||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
schoolAnnouncementDb.loadAll(
|
||||
student.studentId)
|
||||
schoolAnnouncementDb.loadAll(student.studentId)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
@ -57,9 +53,11 @@ class SchoolAnnouncementRepository @Inject constructor(
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
)
|
||||
|
||||
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
||||
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.mappers.mapToEntity
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@ -14,29 +16,41 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class SchoolRepository @Inject constructor(
|
||||
private val schoolDb: SchoolDao,
|
||||
private val sdk: Sdk
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
|
||||
.mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(schoolDb) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
} else if (old == null) {
|
||||
schoolDb.insertAll(listOf(new))
|
||||
private val cacheKey = "school_info"
|
||||
|
||||
fun getSchoolInfo(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, student)
|
||||
)
|
||||
it == null || forceRefresh || isExpired
|
||||
},
|
||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
|
||||
.mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(schoolDb) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
} else if (old == null) {
|
||||
schoolDb.insertAll(listOf(new))
|
||||
}
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -22,7 +22,11 @@ class SemesterRepository @Inject constructor(
|
||||
private val dispatchers: DispatchersProvider
|
||||
) {
|
||||
|
||||
suspend fun getSemesters(student: Student, forceRefresh: Boolean = false, refreshOnNoCurrent: Boolean = false) = withContext(dispatchers.backgroundThread) {
|
||||
suspend fun getSemesters(
|
||||
student: Student,
|
||||
forceRefresh: Boolean = false,
|
||||
refreshOnNoCurrent: Boolean = false
|
||||
) = withContext(dispatchers.backgroundThread) {
|
||||
val semesters = semesterDb.loadAll(student.studentId, student.classId)
|
||||
|
||||
if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) {
|
||||
@ -31,14 +35,21 @@ class SemesterRepository @Inject constructor(
|
||||
} else semesters
|
||||
}
|
||||
|
||||
private fun isShouldFetch(student: Student, semesters: List<Semester>, forceRefresh: Boolean, refreshOnNoCurrent: Boolean): Boolean {
|
||||
private fun isShouldFetch(
|
||||
student: Student,
|
||||
semesters: List<Semester>,
|
||||
forceRefresh: Boolean,
|
||||
refreshOnNoCurrent: Boolean
|
||||
): Boolean {
|
||||
val isNoSemesters = semesters.isEmpty()
|
||||
|
||||
val isRefreshOnModeChangeRequired = if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
semesters.firstOrNull { it.isCurrent }?.diaryId == 0
|
||||
} else false
|
||||
val isRefreshOnModeChangeRequired =
|
||||
if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
semesters.firstOrNull { it.isCurrent }?.diaryId == 0
|
||||
} else false
|
||||
|
||||
val isRefreshOnNoCurrentAppropriate = refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent }
|
||||
val isRefreshOnNoCurrentAppropriate =
|
||||
refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent }
|
||||
|
||||
return forceRefresh || isNoSemesters || isRefreshOnModeChangeRequired || isRefreshOnNoCurrentAppropriate
|
||||
}
|
||||
@ -52,7 +63,8 @@ class SemesterRepository @Inject constructor(
|
||||
semesterDb.insertSemesters(new.uniqueSubtract(old))
|
||||
}
|
||||
|
||||
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) = withContext(dispatchers.backgroundThread) {
|
||||
getSemesters(student, forceRefresh).getCurrentOrLast()
|
||||
}
|
||||
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
|
||||
withContext(dispatchers.backgroundThread) {
|
||||
getSemesters(student, forceRefresh).getCurrentOrLast()
|
||||
}
|
||||
}
|
||||
|
@ -19,24 +19,27 @@ class StudentInfoRepository @Inject constructor(
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getStudentInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getStudentInfo().mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(studentInfoDao) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
} else if (old == null) {
|
||||
studentInfoDao.insertAll(listOf(new))
|
||||
fun getStudentInfo(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getStudentInfo().mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(studentInfoDao) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
} else if (old == null) {
|
||||
studentInfoDao.insertAll(listOf(new))
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.withTransaction
|
||||
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.StudentDao
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
@ -25,7 +27,8 @@ class StudentRepository @Inject constructor(
|
||||
private val studentDb: StudentDao,
|
||||
private val semesterDb: SemesterDao,
|
||||
private val sdk: Sdk,
|
||||
private val appInfo: AppInfo
|
||||
private val appInfo: AppInfo,
|
||||
private val appDatabase: AppDatabase
|
||||
) {
|
||||
|
||||
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
|
||||
@ -92,7 +95,7 @@ class StudentRepository @Inject constructor(
|
||||
return student
|
||||
}
|
||||
|
||||
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>): List<Long> {
|
||||
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||
val semesters = studentsWithSemesters.flatMap { it.semesters }
|
||||
val students = studentsWithSemesters.map { it.student }
|
||||
.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
|
||||
}
|
||||
|
||||
semesterDb.insertSemesters(semesters)
|
||||
return studentDb.insertAll(students)
|
||||
appDatabase.withTransaction {
|
||||
studentDb.resetCurrent()
|
||||
semesterDb.insertSemesters(semesters)
|
||||
studentDb.insertAll(students)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) {
|
||||
with(studentDb) {
|
||||
resetCurrent()
|
||||
updateCurrent(studentWithSemesters.student.id)
|
||||
}
|
||||
studentDb.switchCurrent(studentWithSemesters.student.id)
|
||||
}
|
||||
|
||||
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.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
@ -15,14 +17,24 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class SubjectRepository @Inject constructor(
|
||||
private val subjectDao: SubjectDao,
|
||||
private val sdk: Sdk
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
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,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
@ -31,6 +43,8 @@ class SubjectRepository @Inject constructor(
|
||||
saveFetchResult = { old, new ->
|
||||
subjectDao.deleteAll(old uniqueSubtract new)
|
||||
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.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
@ -15,14 +17,24 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class TeacherRepository @Inject constructor(
|
||||
private val teacherDb: TeacherDao,
|
||||
private val sdk: Sdk
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
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,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
@ -32,6 +44,8 @@ class TeacherRepository @Inject constructor(
|
||||
saveFetchResult = { old, new ->
|
||||
teacherDb.deleteAll(old uniqueSubtract new)
|
||||
teacherDb.insertAll(new uniqueSubtract old)
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -41,18 +41,22 @@ class TimetableRepository @Inject constructor(
|
||||
private val cacheKey = "timetable"
|
||||
|
||||
fun getTimetable(
|
||||
student: Student, semester: Semester, start: LocalDate, end: LocalDate,
|
||||
forceRefresh: Boolean, refreshAdditional: Boolean = false
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
refreshAdditional: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { (timetable, additional, headers) ->
|
||||
val refreshKey = getRefreshKey(cacheKey, semester, start, end)
|
||||
val isShouldRefresh = refreshHelper.isShouldBeRefreshed(refreshKey)
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(refreshKey)
|
||||
val isRefreshAdditional = additional.isEmpty() && refreshAdditional
|
||||
|
||||
val isNoData = timetable.isEmpty() || isRefreshAdditional || headers.isEmpty()
|
||||
|
||||
isNoData || forceRefresh || isShouldRefresh
|
||||
isNoData || forceRefresh || isExpired
|
||||
},
|
||||
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
||||
fetch = {
|
||||
@ -79,8 +83,10 @@ class TimetableRepository @Inject constructor(
|
||||
)
|
||||
|
||||
private fun getFullTimetableFromDatabase(
|
||||
student: Student, semester: Semester,
|
||||
start: LocalDate, end: LocalDate
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
): Flow<TimetableFull> {
|
||||
val timetableFlow = timetableDb.loadAll(
|
||||
diaryId = semester.diaryId,
|
||||
@ -113,20 +119,11 @@ class TimetableRepository @Inject constructor(
|
||||
|
||||
private suspend fun refreshTimetable(
|
||||
student: Student,
|
||||
lessonsOld: List<Timetable>, lessonsNew: List<Timetable>
|
||||
lessonsOld: List<Timetable>,
|
||||
lessonsNew: List<Timetable>,
|
||||
) {
|
||||
val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew
|
||||
val lessonsToAdd = (lessonsNew uniqueSubtract lessonsOld).map { new ->
|
||||
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
|
||||
}
|
||||
val lessonsToAdd = lessonsNew uniqueSubtract lessonsOld
|
||||
|
||||
timetableDb.deleteAll(lessonsToRemove)
|
||||
timetableDb.insertAll(lessonsToAdd)
|
||||
|
@ -11,6 +11,7 @@ import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.services.HiltBroadcastReceiver
|
||||
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
|
||||
@ -32,6 +33,9 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||
@Inject
|
||||
lateinit var studentRepository: StudentRepository
|
||||
|
||||
@Inject
|
||||
lateinit var preferencesRepository: PreferencesRepository
|
||||
|
||||
companion object {
|
||||
const val NOTIFICATION_TYPE_CURRENT = 1
|
||||
const val NOTIFICATION_TYPE_UPCOMING = 2
|
||||
@ -68,6 +72,7 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||
private fun prepareNotification(context: Context, intent: Intent) {
|
||||
val type = intent.getIntExtra(LESSON_TYPE, 0)
|
||||
val notificationId = intent.getIntExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||
val isPersistent = preferencesRepository.isUpcomingLessonsNotificationsPersistent
|
||||
|
||||
if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) {
|
||||
return NotificationManagerCompat.from(context).cancel(notificationId)
|
||||
@ -87,33 +92,57 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||
|
||||
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,
|
||||
context.getString(if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next, "($room) $subject".removePrefix("()")),
|
||||
nextSubject?.let { context.getString(R.string.timetable_later, "($nextRoom) $nextSubject".removePrefix("()")) }
|
||||
context.getString(
|
||||
if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next,
|
||||
"($room) $subject".removePrefix("()")
|
||||
),
|
||||
nextSubject?.let {
|
||||
context.getString(
|
||||
R.string.timetable_later,
|
||||
"($nextRoom) $nextSubject".removePrefix("()")
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun showNotification(context: Context, notificationId: Int, studentName: String?, countDown: Long, timeout: Long, title: String, next: String?) {
|
||||
NotificationManagerCompat.from(context).notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setContentTitle(title)
|
||||
.setContentText(next)
|
||||
.setAutoCancel(false)
|
||||
.setOngoing(true)
|
||||
.setWhen(countDown)
|
||||
.apply {
|
||||
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
||||
}
|
||||
.setTimeoutAfter(timeout)
|
||||
.setSmallIcon(R.drawable.ic_stat_timetable)
|
||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||
.setStyle(NotificationCompat.InboxStyle().also {
|
||||
it.setSummaryText(studentName)
|
||||
it.addLine(next)
|
||||
})
|
||||
.setContentIntent(PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id,
|
||||
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT))
|
||||
.build()
|
||||
)
|
||||
private fun showNotification(
|
||||
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)
|
||||
.setContentText(next)
|
||||
.setAutoCancel(false)
|
||||
.setWhen(countDown)
|
||||
.setOngoing(isPersistent)
|
||||
.apply {
|
||||
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
||||
}
|
||||
.setTimeoutAfter(timeout)
|
||||
.setSmallIcon(R.drawable.ic_stat_timetable)
|
||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||
.setStyle(NotificationCompat.InboxStyle().also {
|
||||
it.setSummaryText(studentName)
|
||||
it.addLine(next)
|
||||
})
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
MainView.Section.TIMETABLE.id,
|
||||
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true),
|
||||
FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import io.github.wulkanowy.utils.nickOrName
|
||||
import io.github.wulkanowy.utils.toTimestamp
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalDateTime.now
|
||||
import javax.inject.Inject
|
||||
@ -57,10 +58,13 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
|
||||
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
|
||||
cancelScheduledTo(
|
||||
upcomingTime..lesson.start,
|
||||
getRequestCode(upcomingTime, studentId)
|
||||
range = upcomingTime..lesson.start,
|
||||
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")
|
||||
}
|
||||
@ -82,6 +86,11 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
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) {
|
||||
lessons.groupBy { it.date }
|
||||
.map { it.value.sortedBy { lesson -> lesson.start } }
|
||||
@ -96,26 +105,26 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
|
||||
if (lesson.start > now()) {
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_UPCOMING,
|
||||
getUpcomingLessonTime(index, active, lesson)
|
||||
intent = intent,
|
||||
studentId = student.studentId,
|
||||
notificationType = NOTIFICATION_TYPE_UPCOMING,
|
||||
time = getUpcomingLessonTime(index, active, lesson)
|
||||
)
|
||||
}
|
||||
|
||||
if (lesson.end > now()) {
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_CURRENT,
|
||||
lesson.start
|
||||
intent = intent,
|
||||
studentId = student.studentId,
|
||||
notificationType = NOTIFICATION_TYPE_CURRENT,
|
||||
time = lesson.start
|
||||
)
|
||||
if (active.lastIndex == index) {
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
||||
lesson.end
|
||||
intent = intent,
|
||||
studentId = student.studentId,
|
||||
notificationType = NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
||||
time = lesson.end
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -143,17 +152,21 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
notificationType: Int,
|
||||
time: LocalDateTime
|
||||
) {
|
||||
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
||||
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||
it.putExtra(LESSON_TYPE, notificationType)
|
||||
}, FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
Timber.d(
|
||||
"TimetableNotification scheduled: type: $notificationType, subject: ${
|
||||
intent.getStringExtra(LESSON_TITLE)
|
||||
}, start: $time, student: $studentId"
|
||||
)
|
||||
try {
|
||||
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
||||
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||
it.putExtra(LESSON_TYPE, notificationType)
|
||||
}, FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
Timber.d(
|
||||
"TimetableNotification scheduled: type: $notificationType, subject: ${
|
||||
intent.getStringExtra(LESSON_TITLE)
|
||||
}, 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) {
|
||||
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
|
||||
workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
||||
PeriodicWorkRequestBuilder<SyncWorker>(preferencesRepository.servicesInterval, MINUTES)
|
||||
val serviceInterval = preferencesRepository.servicesInterval
|
||||
|
||||
workManager.enqueueUniquePeriodicWork(
|
||||
SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
||||
PeriodicWorkRequestBuilder<SyncWorker>(serviceInterval, MINUTES)
|
||||
.setInitialDelay(10, MINUTES)
|
||||
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
|
||||
.setConstraints(Constraints.Builder()
|
||||
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
||||
.build())
|
||||
.build())
|
||||
.setConstraints(
|
||||
Constraints.Builder()
|
||||
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,7 +83,11 @@ class SyncManager @Inject constructor(
|
||||
)
|
||||
.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()
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Conference
|
||||
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.utils.toFormattedString
|
||||
import java.time.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewConferenceNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<Conference>, student: Student) {
|
||||
suspend fun notify(items: List<Conference>, student: Student) {
|
||||
val today = LocalDateTime.now()
|
||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
|
||||
}.ifEmpty { return }
|
||||
|
||||
val notification = MultipleNotifications(
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_CONFERENCE,
|
||||
icon = R.drawable.ic_more_conferences,
|
||||
titleStringRes = R.plurals.conference_notify_new_item_title,
|
||||
@ -33,6 +29,6 @@ class NewConferenceNotification @Inject constructor(
|
||||
lines = lines
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,25 @@
|
||||
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.data.db.entities.Exam
|
||||
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.utils.toFormattedString
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewExamNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<Exam>, student: Student) {
|
||||
suspend fun notify(items: List<Exam>, student: Student) {
|
||||
val today = LocalDate.now()
|
||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
|
||||
}.ifEmpty { return }
|
||||
|
||||
val notification = MultipleNotifications(
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_EXAM,
|
||||
icon = R.drawable.ic_main_exam,
|
||||
titleStringRes = R.plurals.exam_notify_new_item_title,
|
||||
@ -33,6 +29,6 @@ class NewExamNotification @Inject constructor(
|
||||
lines = lines
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,19 @@
|
||||
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.data.db.entities.Grade
|
||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
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 javax.inject.Inject
|
||||
|
||||
class NewGradeNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notifyDetails(items: List<Grade>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notifyDetails(items: List<Grade>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_GRADE_DETAILS,
|
||||
icon = R.drawable.ic_stat_grade,
|
||||
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) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notifyPredicted(items: List<GradeSummary>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_GRADE_PREDICTED,
|
||||
icon = R.drawable.ic_stat_grade,
|
||||
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) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notifyFinal(items: List<GradeSummary>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_GRADE_FINAL,
|
||||
icon = R.drawable.ic_stat_grade,
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Homework
|
||||
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.utils.toFormattedString
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewHomeworkNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<Homework>, student: Student) {
|
||||
suspend fun notify(items: List<Homework>, student: Student) {
|
||||
val today = LocalDate.now()
|
||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
|
||||
}.ifEmpty { return }
|
||||
|
||||
val notification = MultipleNotifications(
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_HOMEWORK,
|
||||
icon = R.drawable.ic_more_homework,
|
||||
titleStringRes = R.plurals.homework_notify_new_item_title,
|
||||
@ -33,6 +29,6 @@ class NewHomeworkNotification @Inject constructor(
|
||||
lines = lines
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,26 @@
|
||||
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.data.db.entities.LuckyNumber
|
||||
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 javax.inject.Inject
|
||||
|
||||
class NewLuckyNumberNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(item: LuckyNumber, student: Student) {
|
||||
val notification = OneNotification(
|
||||
type = NotificationType.NEW_LUCKY_NUMBER,
|
||||
icon = R.drawable.ic_stat_luckynumber,
|
||||
titleStringRes = R.string.lucky_number_notify_new_item_title,
|
||||
contentStringRes = R.string.lucky_number_notify_new_item,
|
||||
startMenu = MainView.Section.LUCKY_NUMBER,
|
||||
contentValues = listOf(item.luckyNumber.toString())
|
||||
)
|
||||
suspend fun notify(item: LuckyNumber, student: Student) {
|
||||
val notification = OneNotificationData(
|
||||
type = NotificationType.NEW_LUCKY_NUMBER,
|
||||
icon = R.drawable.ic_stat_luckynumber,
|
||||
titleStringRes = R.string.lucky_number_notify_new_item_title,
|
||||
contentStringRes = R.string.lucky_number_notify_new_item,
|
||||
startMenu = MainView.Section.LUCKY_NUMBER,
|
||||
contentValues = listOf(item.luckyNumber.toString())
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,18 @@
|
||||
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.data.db.entities.Message
|
||||
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 javax.inject.Inject
|
||||
|
||||
class NewMessageNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<Message>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notify(items: List<Message>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_MESSAGE,
|
||||
icon = R.drawable.ic_stat_message,
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Note
|
||||
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.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewNoteNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<Note>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notify(items: List<Note>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_NOTE,
|
||||
icon = R.drawable.ic_stat_note,
|
||||
titleStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
||||
@ -41,6 +37,6 @@ class NewNoteNotification @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,29 @@
|
||||
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.data.db.entities.SchoolAnnouncement
|
||||
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 javax.inject.Inject
|
||||
|
||||
class NewSchoolAnnouncementNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_ANNOUNCEMENT,
|
||||
icon = R.drawable.ic_all_about,
|
||||
titleStringRes = R.plurals.school_announcement_notify_new_item_title,
|
||||
contentStringRes = R.plurals.school_announcement_notify_new_items,
|
||||
summaryStringRes = R.plurals.school_announcement_number_item,
|
||||
startMenu = MainView.Section.SCHOOL_ANNOUNCEMENT,
|
||||
lines = items.map {
|
||||
"${it.subject}: ${it.content}"
|
||||
}
|
||||
suspend fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_ANNOUNCEMENT,
|
||||
icon = R.drawable.ic_all_about,
|
||||
titleStringRes = R.plurals.school_announcement_notify_new_item_title,
|
||||
contentStringRes = R.plurals.school_announcement_notify_new_items,
|
||||
summaryStringRes = R.plurals.school_announcement_number_item,
|
||||
startMenu = MainView.Section.SCHOOL_ANNOUNCEMENT,
|
||||
lines = items.map {
|
||||
"${it.subject}: ${it.content}"
|
||||
}
|
||||
)
|
||||
|
||||
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.NewNotesChannel
|
||||
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_EXAM("new_exam_group", NewExamChannel.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_NOTE("new_notes_group", NewNotesChannel.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 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) {
|
||||
attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true).waitForResult()
|
||||
attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true)
|
||||
.waitForResult()
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ open class BasePresenter<T : BaseView>(
|
||||
protected val studentRepository: StudentRepository
|
||||
) : CoroutineScope {
|
||||
|
||||
private var job: Job = Job()
|
||||
private var job = Job()
|
||||
|
||||
private val jobs = mutableMapOf<String, Job>()
|
||||
|
||||
|
@ -8,7 +8,7 @@ import androidx.core.view.get
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.databinding.FragmentAccountBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
|
||||
@ -75,9 +75,7 @@ class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_a
|
||||
}
|
||||
}
|
||||
|
||||
override fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters) {
|
||||
(activity as? MainActivity)?.pushView(
|
||||
AccountDetailsFragment.newInstance(studentWithSemesters)
|
||||
)
|
||||
override fun openAccountDetailsView(student: Student) {
|
||||
(activity as? MainActivity)?.pushView(AccountDetailsFragment.newInstance(student))
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ class AccountPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onItemSelected(studentWithSemesters: StudentWithSemesters) {
|
||||
view?.openAccountDetailsView(studentWithSemesters)
|
||||
view?.openAccountDetailsView(studentWithSemesters.student)
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
|
@ -1,6 +1,6 @@
|
||||
package io.github.wulkanowy.ui.modules.account
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface AccountView : BaseView {
|
||||
@ -11,5 +11,5 @@ interface AccountView : BaseView {
|
||||
|
||||
fun openLoginView()
|
||||
|
||||
fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters)
|
||||
fun openAccountDetailsView(student: Student)
|
||||
}
|
||||
|
@ -37,9 +37,9 @@ class AccountDetailsFragment :
|
||||
|
||||
private const val ARGUMENT_KEY = "Data"
|
||||
|
||||
fun newInstance(studentWithSemesters: StudentWithSemesters) =
|
||||
fun newInstance(student: Student) =
|
||||
AccountDetailsFragment().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, studentWithSemesters) }
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, student) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ class AccountDetailsFragment :
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentAccountDetailsBinding.bind(view)
|
||||
presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as StudentWithSemesters)
|
||||
presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
@ -121,10 +121,14 @@ class AccountDetailsFragment :
|
||||
}
|
||||
}
|
||||
|
||||
override fun popView() {
|
||||
override fun popViewToMain() {
|
||||
(requireActivity() as MainActivity).popView(2)
|
||||
}
|
||||
|
||||
override fun popViewToAccounts() {
|
||||
(requireActivity() as MainActivity).popView(1)
|
||||
}
|
||||
|
||||
override fun recreateMainView() {
|
||||
requireActivity().recreate()
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.account.accountdetails
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.services.sync.SyncManager
|
||||
@ -27,9 +28,9 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
|
||||
private var studentId: Long? = null
|
||||
|
||||
fun onAttachView(view: AccountDetailsView, studentWithSemesters: StudentWithSemesters) {
|
||||
fun onAttachView(view: AccountDetailsView, student: Student) {
|
||||
super.onAttachView(view)
|
||||
studentId = studentWithSemesters.student.id
|
||||
studentId = student.id
|
||||
|
||||
view.initView()
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
@ -118,7 +119,7 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
view?.popView()
|
||||
view?.popViewToMain()
|
||||
}.launch("switch")
|
||||
}
|
||||
|
||||
@ -151,11 +152,14 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
syncManager.stopSyncWorker()
|
||||
openClearLoginView()
|
||||
}
|
||||
studentWithSemesters!!.student.isCurrent -> {
|
||||
studentWithSemesters?.student?.isCurrent == true -> {
|
||||
Timber.i("Logout result: Logout student and switch to another")
|
||||
recreateMainView()
|
||||
}
|
||||
else -> Timber.i("Logout result: Logout student")
|
||||
else -> {
|
||||
Timber.i("Logout result: Logout student")
|
||||
recreateMainView()
|
||||
}
|
||||
}
|
||||
}
|
||||
Status.ERROR -> {
|
||||
@ -164,7 +168,11 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
view?.popView()
|
||||
if (studentWithSemesters?.student?.isCurrent == true) {
|
||||
view?.popViewToMain()
|
||||
} else {
|
||||
view?.popViewToAccounts()
|
||||
}
|
||||
}.launch("logout")
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,9 @@ interface AccountDetailsView : BaseView {
|
||||
|
||||
fun showLogoutConfirmDialog()
|
||||
|
||||
fun popView()
|
||||
fun popViewToMain()
|
||||
|
||||
fun popViewToAccounts()
|
||||
|
||||
fun recreateMainView()
|
||||
|
||||
|
@ -245,7 +245,9 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||
presenter.onDateSet(date.year, date.monthValue, date.dayOfMonth)
|
||||
}
|
||||
|
||||
datePicker.show(this@AttendanceFragment.parentFragmentManager, null)
|
||||
if (!parentFragmentManager.isStateSaved) {
|
||||
datePicker.show(parentFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun showExcuseDialog() {
|
||||
|
@ -152,6 +152,8 @@ class AttendancePresenter @Inject constructor(
|
||||
fun onExcuseDialogSubmit(reason: String) {
|
||||
view?.finishActionMode()
|
||||
|
||||
if (attendanceToExcuseList.isEmpty()) return
|
||||
|
||||
if (isVulcanExcusedFunctionEnabled) {
|
||||
excuseAbsence(
|
||||
reason = reason.takeIf { it.isNotBlank() },
|
||||
@ -234,6 +236,7 @@ class AttendancePresenter @Inject constructor(
|
||||
enableSwipe(true)
|
||||
showRefresh(true)
|
||||
showProgress(false)
|
||||
showErrorView(false)
|
||||
showEmpty(filteredAttendance.isEmpty())
|
||||
showContent(filteredAttendance.isNotEmpty())
|
||||
updateData(filteredAttendance.sortedBy { item -> item.number })
|
||||
|
@ -82,7 +82,13 @@ class AttendanceSummaryPresenter @Inject constructor(
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
attendanceSummaryRepository.getAttendanceSummary(student, semester, subjectId, forceRefresh)
|
||||
|
||||
attendanceSummaryRepository.getAttendanceSummary(
|
||||
student = student,
|
||||
semester = semester,
|
||||
subjectId = subjectId,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
@ -92,6 +98,7 @@ class AttendanceSummaryPresenter @Inject constructor(
|
||||
showRefresh(true)
|
||||
showProgress(false)
|
||||
showContent(true)
|
||||
showErrorView(false)
|
||||
updateDataSet(sortItems(it.data))
|
||||
}
|
||||
}
|
||||
@ -99,6 +106,7 @@ class AttendanceSummaryPresenter @Inject constructor(
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading attendance summary result: Success")
|
||||
view?.apply {
|
||||
showErrorView(false)
|
||||
showEmpty(it.data!!.isEmpty())
|
||||
showContent(it.data.isNotEmpty())
|
||||
updateDataSet(sortItems(it.data))
|
||||
|
@ -14,6 +14,8 @@ class ConferenceAdapter @Inject constructor() :
|
||||
|
||||
var items = emptyList<Conference>()
|
||||
|
||||
var onItemClickListener: (Conference) -> Unit = {}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
||||
@ -28,7 +30,10 @@ class ConferenceAdapter @Inject constructor() :
|
||||
conferenceItemTitle.text = item.title
|
||||
conferenceItemSubject.text = item.subject
|
||||
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.databinding.FragmentConferenceBinding
|
||||
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.widgets.DividerItemDecoration
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
@ -41,6 +42,8 @@ class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.frag
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
conferencesAdapter.onItemClickListener = presenter::onItemSelected
|
||||
|
||||
with(binding.conferenceRecycler) {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = conferencesAdapter
|
||||
@ -50,7 +53,11 @@ class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.frag
|
||||
with(binding) {
|
||||
conferenceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
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() }
|
||||
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
|
||||
}
|
||||
|
||||
override fun openConferenceDialog(conference: Conference) {
|
||||
(activity as? MainActivity)?.showDialogFragment(ConferenceDialog.newInstance(conference))
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.github.wulkanowy.ui.modules.conference
|
||||
|
||||
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.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
@ -43,6 +44,10 @@ class ConferencePresenter @Inject constructor(
|
||||
loadData(true)
|
||||
}
|
||||
|
||||
fun onItemSelected(conference: Conference) {
|
||||
view?.openConferenceDialog(conference)
|
||||
}
|
||||
|
||||
fun onDetailsClick() {
|
||||
view?.showErrorDetailsDialog(lastError)
|
||||
}
|
||||
|
@ -26,4 +26,6 @@ interface ConferenceView : BaseView {
|
||||
fun enableSwipe(enable: Boolean)
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun openConferenceDialog(conference: Conference)
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.data.db.entities.TimetableHeader
|
||||
import io.github.wulkanowy.databinding.ItemDashboardAccountBinding
|
||||
@ -30,6 +31,7 @@ import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.left
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import timber.log.Timber
|
||||
import java.time.Duration
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
@ -41,7 +43,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
|
||||
private var lessonsTimer: Timer? = null
|
||||
|
||||
var onAccountTileClickListener: () -> Unit = {}
|
||||
var onAccountTileClickListener: (Student) -> Unit = {}
|
||||
|
||||
var onLuckyNumberTileClickListener: () -> Unit = {}
|
||||
|
||||
@ -51,7 +53,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
|
||||
var onAttendanceTileClickListener: () -> Unit = {}
|
||||
|
||||
var onLessonsTileClickListener: () -> Unit = {}
|
||||
var onLessonsTileClickListener: (LocalDate) -> Unit = {}
|
||||
|
||||
var onHomeworkTileClickListener: () -> Unit = {}
|
||||
|
||||
@ -152,7 +154,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
dashboardAccountItemName.text = student?.nickOrName.orEmpty()
|
||||
dashboardAccountItemSchoolName.text = student?.schoolName.orEmpty()
|
||||
|
||||
root.setOnClickListener { onAccountTileClickListener() }
|
||||
root.setOnClickListener { student?.let(onAccountTileClickListener) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,39 +171,43 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
val isLoading = item.isLoading
|
||||
val binding = horizontalGroupViewHolder.binding
|
||||
val context = binding.root.context
|
||||
val isLoadingVisible =
|
||||
(isLoading && !item.isDataLoaded) || (isLoading && !item.isFullDataLoaded)
|
||||
val attendanceColor = when {
|
||||
attendancePercentage ?: 0.0 <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> {
|
||||
attendancePercentage == null || attendancePercentage == .0 -> {
|
||||
context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||
}
|
||||
attendancePercentage <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> {
|
||||
context.getThemeAttrColor(R.attr.colorPrimary)
|
||||
}
|
||||
attendancePercentage ?: 0.0 <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> {
|
||||
attendancePercentage <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> {
|
||||
context.getThemeAttrColor(R.attr.colorTimetableChange)
|
||||
}
|
||||
else -> context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||
}
|
||||
val attendanceString = if (attendancePercentage == null || attendancePercentage == .0) {
|
||||
context.getString(R.string.dashboard_horizontal_group_no_data)
|
||||
} else {
|
||||
"%.2f%%".format(attendancePercentage)
|
||||
}
|
||||
|
||||
with(binding.dashboardHorizontalGroupItemAttendanceValue) {
|
||||
text = "%.2f%%".format(attendancePercentage)
|
||||
text = attendanceString
|
||||
setTextColor(attendanceColor)
|
||||
}
|
||||
|
||||
with(binding) {
|
||||
dashboardHorizontalGroupItemMessageValue.text = unreadMessagesCount.toString()
|
||||
dashboardHorizontalGroupItemLuckyValue.text = if (luckyNumber == -1) {
|
||||
context.getString(R.string.dashboard_horizontal_group_no_lukcy_number)
|
||||
dashboardHorizontalGroupItemLuckyValue.text = if (luckyNumber == 0) {
|
||||
context.getString(R.string.dashboard_horizontal_group_no_data)
|
||||
} else luckyNumber?.toString()
|
||||
|
||||
if (dashboardHorizontalGroupItemInfoContainer.isVisible != (error != null || isLoading)) {
|
||||
dashboardHorizontalGroupItemInfoContainer.isVisible = error != null || isLoading
|
||||
}
|
||||
|
||||
if (dashboardHorizontalGroupItemInfoProgress.isVisible != isLoading) {
|
||||
dashboardHorizontalGroupItemInfoProgress.isVisible = isLoading
|
||||
}
|
||||
|
||||
dashboardHorizontalGroupItemInfoContainer.isVisible = error != null || isLoadingVisible
|
||||
dashboardHorizontalGroupItemInfoProgress.isVisible = isLoadingVisible
|
||||
dashboardHorizontalGroupItemInfoErrorText.isVisible = error != null
|
||||
|
||||
with(dashboardHorizontalGroupItemLuckyContainer) {
|
||||
isVisible = error == null && !isLoading && luckyNumber != null
|
||||
isVisible = luckyNumber != null && luckyNumber != -1 && !isLoadingVisible
|
||||
setOnClickListener { onLuckyNumberTileClickListener() }
|
||||
|
||||
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
@ -216,7 +222,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
}
|
||||
|
||||
with(dashboardHorizontalGroupItemAttendanceContainer) {
|
||||
isVisible = error == null && !isLoading && attendancePercentage != null
|
||||
isVisible =
|
||||
attendancePercentage != null && attendancePercentage != -1.0 && !isLoadingVisible
|
||||
updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
matchConstraintPercentWidth = when {
|
||||
luckyNumber == null && unreadMessagesCount == null -> 1.0f
|
||||
@ -228,7 +235,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
}
|
||||
|
||||
with(dashboardHorizontalGroupItemMessageContainer) {
|
||||
isVisible = error == null && !isLoading && unreadMessagesCount != null
|
||||
isVisible =
|
||||
unreadMessagesCount != null && unreadMessagesCount != -1 && !isLoadingVisible
|
||||
setOnClickListener { onMessageTileClickListener() }
|
||||
}
|
||||
}
|
||||
@ -267,10 +275,12 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
val item = items[position] as DashboardItem.Lessons
|
||||
val timetableFull = item.lessons
|
||||
val binding = lessonsViewHolder.binding
|
||||
var dateToNavigate = LocalDate.now()
|
||||
|
||||
fun updateLessonState() {
|
||||
val currentDateTime = LocalDateTime.now()
|
||||
val currentDate = LocalDate.now()
|
||||
val tomorrowDate = currentDate.plusDays(1)
|
||||
|
||||
val currentTimetable = timetableFull?.lessons
|
||||
.orEmpty()
|
||||
@ -288,22 +298,27 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
|
||||
when {
|
||||
currentTimetable.isNotEmpty() -> {
|
||||
dateToNavigate = currentDate
|
||||
updateLessonView(item, currentTimetable, binding)
|
||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
||||
}
|
||||
currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
|
||||
updateLessonView(item, emptyList(), binding, currentDayHeader)
|
||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
||||
}
|
||||
tomorrowTimetable.isNotEmpty() -> {
|
||||
dateToNavigate = tomorrowDate
|
||||
updateLessonView(item, tomorrowTimetable, binding)
|
||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
||||
}
|
||||
currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
|
||||
dateToNavigate = currentDate
|
||||
updateLessonView(item, emptyList(), binding, currentDayHeader)
|
||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
||||
}
|
||||
tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> {
|
||||
dateToNavigate = tomorrowDate
|
||||
updateLessonView(item, emptyList(), binding, tomorrowDayHeader)
|
||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
||||
}
|
||||
else -> {
|
||||
dateToNavigate = tomorrowDate
|
||||
updateLessonView(item, emptyList(), binding)
|
||||
binding.dashboardLessonsItemTitleTomorrow.isVisible =
|
||||
!(item.isLoading && item.error == null)
|
||||
@ -318,7 +333,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
Handler(Looper.getMainLooper()).post { updateLessonState() }
|
||||
}
|
||||
|
||||
binding.root.setOnClickListener { onLessonsTileClickListener() }
|
||||
binding.root.setOnClickListener { onLessonsTileClickListener(dateToNavigate) }
|
||||
}
|
||||
|
||||
private fun updateLessonView(
|
||||
@ -348,6 +363,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun updateFirstLessonView(
|
||||
binding: ItemDashboardLessonsBinding,
|
||||
firstLesson: Timetable?,
|
||||
@ -367,7 +383,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
firstLesson ?: return
|
||||
|
||||
val minutesToStartLesson =
|
||||
Duration.between(currentDateTime, firstLesson.start).toMinutes()
|
||||
Duration.between(currentDateTime, firstLesson.start).toMinutes() + 1
|
||||
val isFirstTimeVisible: Boolean
|
||||
val isFirstTimeRangeVisible: Boolean
|
||||
val firstTimeText: String
|
||||
@ -376,12 +392,12 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
val firstTitleAndValueTextColor: Int
|
||||
val firstTitleAndValueTextFont: Typeface
|
||||
|
||||
if (currentDateTime.isBefore(firstLesson.start)) {
|
||||
if (currentDateTime < firstLesson.start) {
|
||||
if (minutesToStartLesson > 60) {
|
||||
val formattedStartTime = firstLesson.start.toFormattedString("HH:mm")
|
||||
val formattedEndTime = firstLesson.end.toFormattedString("HH:mm")
|
||||
|
||||
firstTimeRangeText = "${formattedStartTime}-${formattedEndTime}"
|
||||
firstTimeRangeText = "$formattedStartTime - $formattedEndTime"
|
||||
firstTimeText = ""
|
||||
|
||||
isFirstTimeRangeVisible = true
|
||||
@ -421,7 +437,10 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val minutesToEndLesson = firstLesson.left!!.toMinutes()
|
||||
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(
|
||||
R.plurals.dashboard_timetable_first_lesson_time_more_minutes,
|
||||
@ -454,11 +473,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
with(binding.dashboardLessonsItemFirstValue) {
|
||||
setTextColor(firstTitleAndValueTextColor)
|
||||
typeface = firstTitleAndValueTextFont
|
||||
text = context.getString(
|
||||
R.string.dashboard_timetable_lesson_value,
|
||||
firstLesson.subject,
|
||||
firstLesson.room
|
||||
)
|
||||
text =
|
||||
"${firstLesson.subject} ${if (firstLesson.room.isNotBlank()) "(${firstLesson.room})" else ""}"
|
||||
}
|
||||
}
|
||||
|
||||
@ -472,13 +488,11 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
val formattedStartTime = secondLesson?.start?.toFormattedString("HH:mm")
|
||||
val formattedEndTime = secondLesson?.end?.toFormattedString("HH:mm")
|
||||
|
||||
val secondTimeText = "${formattedStartTime}-${formattedEndTime}"
|
||||
val secondTimeText = "$formattedStartTime - $formattedEndTime"
|
||||
val secondValueText = if (secondLesson != null) {
|
||||
context.getString(
|
||||
R.string.dashboard_timetable_lesson_value,
|
||||
secondLesson.subject,
|
||||
secondLesson.room
|
||||
)
|
||||
val roomString = if (secondLesson.room.isNotBlank()) "(${secondLesson.room})" else ""
|
||||
|
||||
"${secondLesson.subject} $roomString"
|
||||
} else {
|
||||
context.getString(R.string.dashboard_timetable_second_lesson_value_end)
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.databinding.FragmentDashboardBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.account.AccountFragment
|
||||
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
|
||||
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
|
||||
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
|
||||
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
||||
@ -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.MainView
|
||||
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.timetable.TimetableFragment
|
||||
import io.github.wulkanowy.utils.capitalise
|
||||
@ -70,14 +71,13 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||
override fun initView() {
|
||||
val mainActivity = requireActivity() as MainActivity
|
||||
val itemTouchHelper = ItemTouchHelper(
|
||||
DashboardItemMoveCallback(
|
||||
dashboardAdapter,
|
||||
presenter::onDragAndDropEnd
|
||||
)
|
||||
DashboardItemMoveCallback(dashboardAdapter, presenter::onDragAndDropEnd)
|
||||
)
|
||||
|
||||
dashboardAdapter.apply {
|
||||
onAccountTileClickListener = { mainActivity.pushView(AccountFragment.newInstance()) }
|
||||
onAccountTileClickListener = {
|
||||
mainActivity.pushView(AccountDetailsFragment.newInstance(it))
|
||||
}
|
||||
onLuckyNumberTileClickListener = {
|
||||
mainActivity.pushView(LuckyNumberFragment.newInstance())
|
||||
}
|
||||
@ -85,7 +85,9 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||
onAttendanceTileClickListener = {
|
||||
mainActivity.pushView(AttendanceSummaryFragment.newInstance())
|
||||
}
|
||||
onLessonsTileClickListener = { mainActivity.pushView(TimetableFragment.newInstance()) }
|
||||
onLessonsTileClickListener = {
|
||||
mainActivity.pushView(TimetableFragment.newInstance(it))
|
||||
}
|
||||
onGradeTileClickListener = { mainActivity.pushView(GradeFragment.newInstance()) }
|
||||
onHomeworkTileClickListener = { mainActivity.pushView(HomeworkFragment.newInstance()) }
|
||||
onAnnouncementsTileClickListener = {
|
||||
@ -119,6 +121,7 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.dashboard_menu_tiles -> presenter.onDashboardTileSettingsSelected()
|
||||
R.id.dashboard_menu_notifaction_list -> presenter.onNotificationsCenterSelected()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@ -181,6 +184,10 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||
if (::presenter.isInitialized) presenter.onViewReselected()
|
||||
}
|
||||
|
||||
override fun openNotificationsCenterView() {
|
||||
(requireActivity() as MainActivity).pushView(NotificationsCenterFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
dashboardAdapter.clearTimers()
|
||||
presenter.onDetachView()
|
||||
|
@ -35,6 +35,9 @@ sealed class DashboardItem(val type: Type) {
|
||||
|
||||
override val isDataLoaded
|
||||
get() = unreadMessagesCount != null || attendancePercentage != null || luckyNumber != null
|
||||
|
||||
val isFullDataLoaded
|
||||
get() = luckyNumber != -1 && attendancePercentage != -1.0 && unreadMessagesCount != -1
|
||||
}
|
||||
|
||||
data class Grades(
|
||||
|
@ -2,6 +2,8 @@ package io.github.wulkanowy.ui.modules.dashboard
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.enums.MessageFolder
|
||||
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
|
||||
import io.github.wulkanowy.data.repositories.ConferenceRepository
|
||||
@ -18,11 +20,18 @@ import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.calculatePercentage
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import io.github.wulkanowy.utils.flowWithResourceIn
|
||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.filterNot
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
@ -48,9 +57,11 @@ class DashboardPresenter @Inject constructor(
|
||||
|
||||
private val dashboardItemRefreshLoadedList = mutableListOf<DashboardItem>()
|
||||
|
||||
private lateinit var dashboardItemsToLoad: Set<DashboardItem.Type>
|
||||
private var dashboardItemsToLoad = emptySet<DashboardItem.Type>()
|
||||
|
||||
private var dashboardTilesToLoad: Set<DashboardItem.Tile> = emptySet()
|
||||
private var dashboardTileLoadedList = emptySet<DashboardItem.Tile>()
|
||||
|
||||
private val firstLoadedItemList = mutableListOf<DashboardItem.Type>()
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
@ -69,8 +80,10 @@ class DashboardPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onDragAndDropEnd(list: List<DashboardItem>) {
|
||||
dashboardItemLoadedList.clear()
|
||||
dashboardItemLoadedList.addAll(list)
|
||||
with(dashboardItemLoadedList) {
|
||||
clear()
|
||||
addAll(list)
|
||||
}
|
||||
|
||||
val positionList =
|
||||
list.mapIndexed { index, dashboardItem -> Pair(dashboardItem.type, index) }.toMap()
|
||||
@ -78,87 +91,102 @@ class DashboardPresenter @Inject constructor(
|
||||
preferencesRepository.dashboardItemsPosition = positionList
|
||||
}
|
||||
|
||||
fun loadData(forceRefresh: Boolean = false, tilesToLoad: Set<DashboardItem.Tile>) {
|
||||
val oldDashboardDataToLoad = dashboardTilesToLoad
|
||||
fun loadData(
|
||||
tilesToLoad: Set<DashboardItem.Tile>,
|
||||
forceRefresh: Boolean = false,
|
||||
) {
|
||||
val oldDashboardTileLoadedList = dashboardTileLoadedList
|
||||
dashboardItemsToLoad = tilesToLoad.map { it.toDashboardItemType() }.toSet()
|
||||
dashboardTileLoadedList = tilesToLoad
|
||||
|
||||
dashboardTilesToLoad = tilesToLoad
|
||||
dashboardItemsToLoad = dashboardTilesToLoad.map { it.toDashboardItemType() }.toSet()
|
||||
val itemsToLoad = generateDashboardTileListToLoad(
|
||||
dashboardTilesToLoad = tilesToLoad,
|
||||
dashboardLoadedTiles = oldDashboardTileLoadedList,
|
||||
forceRefresh = forceRefresh
|
||||
).map { it.toDashboardItemType() }
|
||||
|
||||
removeUnselectedTiles()
|
||||
|
||||
val newTileList = generateTileListToLoad(oldDashboardDataToLoad, forceRefresh)
|
||||
loadTiles(forceRefresh, newTileList)
|
||||
removeUnselectedTiles(tilesToLoad.toList())
|
||||
loadTiles(tileList = itemsToLoad, forceRefresh = forceRefresh)
|
||||
}
|
||||
|
||||
private fun removeUnselectedTiles() {
|
||||
val isLuckyNumberToLoad =
|
||||
dashboardTilesToLoad.any { it == DashboardItem.Tile.LUCKY_NUMBER }
|
||||
val isMessagesToLoad =
|
||||
dashboardTilesToLoad.any { it == DashboardItem.Tile.MESSAGES }
|
||||
val isAttendanceToLoad =
|
||||
dashboardTilesToLoad.any { it == DashboardItem.Tile.ATTENDANCE }
|
||||
private fun generateDashboardTileListToLoad(
|
||||
dashboardTilesToLoad: Set<DashboardItem.Tile>,
|
||||
dashboardLoadedTiles: Set<DashboardItem.Tile>,
|
||||
forceRefresh: Boolean
|
||||
) = dashboardTilesToLoad.filter { newItemToLoad ->
|
||||
dashboardLoadedTiles.none { it == newItemToLoad } || forceRefresh
|
||||
}
|
||||
|
||||
private fun removeUnselectedTiles(tilesToLoad: List<DashboardItem.Tile>) {
|
||||
dashboardItemLoadedList.removeAll { loadedTile -> dashboardItemsToLoad.none { it == loadedTile.type } }
|
||||
|
||||
val horizontalGroup =
|
||||
dashboardItemLoadedList.find { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup?
|
||||
|
||||
if (horizontalGroup != null) {
|
||||
val horizontalIndex = dashboardItemLoadedList.indexOf(horizontalGroup)
|
||||
dashboardItemLoadedList.remove(horizontalGroup)
|
||||
val isLuckyNumberToLoad = DashboardItem.Tile.LUCKY_NUMBER in tilesToLoad
|
||||
val isMessagesToLoad = DashboardItem.Tile.MESSAGES in tilesToLoad
|
||||
val isAttendanceToLoad = DashboardItem.Tile.ATTENDANCE in tilesToLoad
|
||||
|
||||
var updatedHorizontalGroup = horizontalGroup
|
||||
val horizontalGroupIndex = dashboardItemLoadedList.indexOf(horizontalGroup)
|
||||
|
||||
if (horizontalGroup.luckyNumber != null && !isLuckyNumberToLoad) {
|
||||
updatedHorizontalGroup = updatedHorizontalGroup.copy(luckyNumber = null)
|
||||
val newHorizontalGroup = horizontalGroup.copy(
|
||||
attendancePercentage = horizontalGroup.attendancePercentage.takeIf { isAttendanceToLoad },
|
||||
unreadMessagesCount = horizontalGroup.unreadMessagesCount.takeIf { isMessagesToLoad },
|
||||
luckyNumber = horizontalGroup.luckyNumber.takeIf { isLuckyNumberToLoad }
|
||||
)
|
||||
|
||||
with(dashboardItemLoadedList) {
|
||||
removeAt(horizontalGroupIndex)
|
||||
add(horizontalGroupIndex, newHorizontalGroup)
|
||||
}
|
||||
|
||||
if (horizontalGroup.attendancePercentage != null && !isAttendanceToLoad) {
|
||||
updatedHorizontalGroup = updatedHorizontalGroup.copy(attendancePercentage = null)
|
||||
}
|
||||
|
||||
if (horizontalGroup.unreadMessagesCount != null && !isMessagesToLoad) {
|
||||
updatedHorizontalGroup = updatedHorizontalGroup.copy(unreadMessagesCount = null)
|
||||
}
|
||||
|
||||
if (horizontalGroup.error != null) {
|
||||
updatedHorizontalGroup = updatedHorizontalGroup.copy(error = null, isLoading = true)
|
||||
}
|
||||
|
||||
dashboardItemLoadedList.add(horizontalIndex, updatedHorizontalGroup)
|
||||
}
|
||||
|
||||
view?.updateData(dashboardItemLoadedList)
|
||||
}
|
||||
|
||||
private fun loadTiles(forceRefresh: Boolean, tileList: List<DashboardItem.Tile>) {
|
||||
tileList.forEach {
|
||||
when (it) {
|
||||
DashboardItem.Tile.ACCOUNT -> loadCurrentAccount(forceRefresh)
|
||||
DashboardItem.Tile.LUCKY_NUMBER -> loadLuckyNumber(forceRefresh)
|
||||
DashboardItem.Tile.MESSAGES -> loadMessages(forceRefresh)
|
||||
DashboardItem.Tile.ATTENDANCE -> loadAttendance(forceRefresh)
|
||||
DashboardItem.Tile.LESSONS -> loadLessons(forceRefresh)
|
||||
DashboardItem.Tile.GRADES -> loadGrades(forceRefresh)
|
||||
DashboardItem.Tile.HOMEWORK -> loadHomework(forceRefresh)
|
||||
DashboardItem.Tile.ANNOUNCEMENTS -> loadSchoolAnnouncements(forceRefresh)
|
||||
DashboardItem.Tile.EXAMS -> loadExams(forceRefresh)
|
||||
DashboardItem.Tile.CONFERENCES -> loadConferences(forceRefresh)
|
||||
DashboardItem.Tile.ADS -> TODO()
|
||||
private fun loadTiles(
|
||||
tileList: List<DashboardItem.Type>,
|
||||
forceRefresh: Boolean
|
||||
) {
|
||||
launch {
|
||||
Timber.i("Loading dashboard account data started")
|
||||
val student = runCatching { studentRepository.getCurrentStudent(true) }
|
||||
.onFailure {
|
||||
Timber.i("Loading dashboard account result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
updateData(DashboardItem.Account(error = it), forceRefresh)
|
||||
}
|
||||
.onSuccess { Timber.i("Loading dashboard account result: Success") }
|
||||
.getOrNull() ?: return@launch
|
||||
|
||||
tileList.forEach {
|
||||
when (it) {
|
||||
DashboardItem.Type.ACCOUNT -> {
|
||||
updateData(DashboardItem.Account(student), forceRefresh)
|
||||
}
|
||||
DashboardItem.Type.HORIZONTAL_GROUP -> {
|
||||
loadHorizontalGroup(student, forceRefresh)
|
||||
}
|
||||
DashboardItem.Type.LESSONS -> loadLessons(student, forceRefresh)
|
||||
DashboardItem.Type.GRADES -> loadGrades(student, forceRefresh)
|
||||
DashboardItem.Type.HOMEWORK -> loadHomework(student, forceRefresh)
|
||||
DashboardItem.Type.ANNOUNCEMENTS -> {
|
||||
loadSchoolAnnouncements(student, forceRefresh)
|
||||
}
|
||||
DashboardItem.Type.EXAMS -> loadExams(student, forceRefresh)
|
||||
DashboardItem.Type.CONFERENCES -> {
|
||||
loadConferences(student, forceRefresh)
|
||||
}
|
||||
DashboardItem.Type.ADS -> TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateTileListToLoad(
|
||||
oldDashboardTileToLoad: Set<DashboardItem.Tile>,
|
||||
forceRefresh: Boolean
|
||||
) = dashboardTilesToLoad.filter { newTileToLoad ->
|
||||
oldDashboardTileToLoad.none { it == newTileToLoad } || forceRefresh
|
||||
}
|
||||
|
||||
fun onSwipeRefresh() {
|
||||
Timber.i("Force refreshing the dashboard")
|
||||
loadData(true, preferencesRepository.selectedDashboardTiles)
|
||||
loadData(preferencesRepository.selectedDashboardTiles, forceRefresh = true)
|
||||
}
|
||||
|
||||
fun onRetry() {
|
||||
@ -166,7 +194,7 @@ class DashboardPresenter @Inject constructor(
|
||||
showErrorView(false)
|
||||
showProgress(true)
|
||||
}
|
||||
loadData(true, preferencesRepository.selectedDashboardTiles)
|
||||
loadData(preferencesRepository.selectedDashboardTiles, forceRefresh = true)
|
||||
}
|
||||
|
||||
fun onViewReselected() {
|
||||
@ -181,6 +209,11 @@ class DashboardPresenter @Inject constructor(
|
||||
view?.showErrorDetailsDialog(lastError)
|
||||
}
|
||||
|
||||
fun onNotificationsCenterSelected(): Boolean {
|
||||
view?.openNotificationsCenterView()
|
||||
return true
|
||||
}
|
||||
|
||||
fun onDashboardTileSettingsSelected(): Boolean {
|
||||
view?.showDashboardTileSettings(preferencesRepository.selectedDashboardTiles.toList())
|
||||
return true
|
||||
@ -192,139 +225,86 @@ class DashboardPresenter @Inject constructor(
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
private fun loadCurrentAccount(forceRefresh: Boolean) {
|
||||
flowWithResource { studentRepository.getCurrentStudent(false) }
|
||||
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
|
||||
flow {
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
val selectedTiles = preferencesRepository.selectedDashboardTiles
|
||||
|
||||
val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh)
|
||||
.map {
|
||||
if (it.data == null) {
|
||||
it.copy(data = LuckyNumber(0, LocalDate.now(), 0))
|
||||
} else it
|
||||
}
|
||||
.takeIf { DashboardItem.Tile.LUCKY_NUMBER in selectedTiles } ?: flowOf(null)
|
||||
|
||||
val messageFLow = messageRepository.getMessages(
|
||||
student = student,
|
||||
semester = semester,
|
||||
folder = MessageFolder.RECEIVED,
|
||||
forceRefresh = forceRefresh
|
||||
).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowOf(null)
|
||||
|
||||
val attendanceFlow = attendanceSummaryRepository.getAttendanceSummary(
|
||||
student = student,
|
||||
semester = semester,
|
||||
subjectId = -1,
|
||||
forceRefresh = forceRefresh
|
||||
).takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowOf(null)
|
||||
|
||||
emitAll(
|
||||
combine(
|
||||
luckyNumberFlow,
|
||||
messageFLow,
|
||||
attendanceFlow
|
||||
) { luckyNumberResource, messageResource, attendanceResource ->
|
||||
val error =
|
||||
luckyNumberResource?.error ?: messageResource?.error ?: attendanceResource?.error
|
||||
error?.let { throw it }
|
||||
|
||||
val luckyNumber = luckyNumberResource?.data?.luckyNumber
|
||||
val messageCount = messageResource?.data?.count { it.unread }
|
||||
val attendancePercentage = attendanceResource?.data?.calculatePercentage()
|
||||
|
||||
val isLoading =
|
||||
luckyNumberResource?.status == Status.LOADING || messageResource?.status == Status.LOADING || attendanceResource?.status == Status.LOADING
|
||||
|
||||
DashboardItem.HorizontalGroup(
|
||||
isLoading = isLoading,
|
||||
attendancePercentage = if (attendancePercentage == 0.0 && isLoading) -1.0 else attendancePercentage,
|
||||
unreadMessagesCount = if (messageCount == 0 && isLoading) -1 else messageCount,
|
||||
luckyNumber = if (luckyNumber == 0 && isLoading) -1 else luckyNumber
|
||||
)
|
||||
})
|
||||
}
|
||||
.filterNot { it.isLoading && forceRefresh }
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard account data started")
|
||||
if (forceRefresh) return@onEach
|
||||
updateData(DashboardItem.Account(it.data, isLoading = true), forceRefresh)
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard account result: Success")
|
||||
updateData(DashboardItem.Account(it.data), forceRefresh)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading dashboard account result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
updateData(DashboardItem.Account(error = it.error), forceRefresh)
|
||||
updateData(it, forceRefresh)
|
||||
|
||||
if (it.isLoading) {
|
||||
Timber.i("Loading horizontal group data started")
|
||||
|
||||
if (it.isFullDataLoaded) {
|
||||
firstLoadedItemList += DashboardItem.Type.HORIZONTAL_GROUP
|
||||
}
|
||||
} else {
|
||||
Timber.i("Loading horizontal group result: Success")
|
||||
}
|
||||
}
|
||||
.launch("dashboard_account")
|
||||
}
|
||||
|
||||
private fun loadLuckyNumber(forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
|
||||
luckyNumberRepository.getLuckyNumber(student, forceRefresh)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard lucky number data started")
|
||||
if (forceRefresh) return@onEach
|
||||
processHorizontalGroupData(
|
||||
luckyNumber = it.data?.luckyNumber,
|
||||
isLoading = true,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard lucky number result: Success")
|
||||
processHorizontalGroupData(
|
||||
luckyNumber = it.data?.luckyNumber ?: -1,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading dashboard lucky number result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
processHorizontalGroupData(error = it.error, forceRefresh = forceRefresh)
|
||||
}
|
||||
.catch {
|
||||
Timber.i("Loading horizontal group result: An exception occurred")
|
||||
updateData(
|
||||
DashboardItem.HorizontalGroup(error = it),
|
||||
forceRefresh,
|
||||
)
|
||||
errorHandler.dispatch(it)
|
||||
}
|
||||
}.launch("dashboard_lucky_number")
|
||||
.launch("horizontal_group")
|
||||
}
|
||||
|
||||
private fun loadMessages(forceRefresh: Boolean) {
|
||||
private fun loadGrades(student: Student, forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
|
||||
messageRepository.getMessages(student, semester, MessageFolder.RECEIVED, forceRefresh)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard messages data started")
|
||||
if (forceRefresh) return@onEach
|
||||
val unreadMessagesCount = it.data?.count { message -> message.unread }
|
||||
|
||||
processHorizontalGroupData(
|
||||
unreadMessagesCount = unreadMessagesCount,
|
||||
isLoading = true,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard messages result: Success")
|
||||
val unreadMessagesCount = it.data?.count { message -> message.unread }
|
||||
|
||||
processHorizontalGroupData(
|
||||
unreadMessagesCount = unreadMessagesCount,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading dashboard messages result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
processHorizontalGroupData(error = it.error, forceRefresh = forceRefresh)
|
||||
}
|
||||
}
|
||||
}.launch("dashboard_messages")
|
||||
}
|
||||
|
||||
private fun loadAttendance(forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
|
||||
attendanceSummaryRepository.getAttendanceSummary(student, semester, -1, forceRefresh)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard attendance data started")
|
||||
if (forceRefresh) return@onEach
|
||||
val attendancePercentage = it.data?.calculatePercentage()
|
||||
|
||||
processHorizontalGroupData(
|
||||
attendancePercentage = attendancePercentage,
|
||||
isLoading = true,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard attendance result: Success")
|
||||
val attendancePercentage = it.data?.calculatePercentage()
|
||||
|
||||
processHorizontalGroupData(
|
||||
attendancePercentage = attendancePercentage,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading dashboard attendance result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
|
||||
processHorizontalGroupData(error = it.error, forceRefresh = forceRefresh)
|
||||
}
|
||||
}
|
||||
}.launch("dashboard_attendance")
|
||||
}
|
||||
|
||||
private fun loadGrades(forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
|
||||
gradeRepository.getGrades(student, semester, forceRefresh)
|
||||
@ -353,6 +333,7 @@ class DashboardPresenter @Inject constructor(
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard grades data started")
|
||||
if (forceRefresh) return@onEach
|
||||
|
||||
updateData(
|
||||
DashboardItem.Grades(
|
||||
subjectWithGrades = it.data,
|
||||
@ -360,6 +341,10 @@ class DashboardPresenter @Inject constructor(
|
||||
isLoading = true
|
||||
), forceRefresh
|
||||
)
|
||||
|
||||
if (!it.data.isNullOrEmpty()) {
|
||||
firstLoadedItemList += DashboardItem.Type.GRADES
|
||||
}
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard grades result: Success")
|
||||
@ -367,7 +352,8 @@ class DashboardPresenter @Inject constructor(
|
||||
DashboardItem.Grades(
|
||||
subjectWithGrades = it.data,
|
||||
gradeTheme = preferencesRepository.gradeColorTheme
|
||||
), forceRefresh
|
||||
),
|
||||
forceRefresh
|
||||
)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
@ -379,9 +365,8 @@ class DashboardPresenter @Inject constructor(
|
||||
}.launch("dashboard_grades")
|
||||
}
|
||||
|
||||
private fun loadLessons(forceRefresh: Boolean) {
|
||||
private fun loadLessons(student: Student, forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
val date = LocalDate.now().nextOrSameSchoolDay
|
||||
|
||||
@ -398,24 +383,34 @@ class DashboardPresenter @Inject constructor(
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard lessons data started")
|
||||
if (forceRefresh) return@onEach
|
||||
updateData(DashboardItem.Lessons(it.data, isLoading = true), forceRefresh)
|
||||
updateData(
|
||||
DashboardItem.Lessons(it.data, isLoading = true),
|
||||
forceRefresh
|
||||
)
|
||||
|
||||
if (!it.data?.lessons.isNullOrEmpty()) {
|
||||
firstLoadedItemList += DashboardItem.Type.LESSONS
|
||||
}
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard lessons result: Success")
|
||||
updateData(DashboardItem.Lessons(it.data), forceRefresh)
|
||||
updateData(
|
||||
DashboardItem.Lessons(it.data), forceRefresh
|
||||
)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading dashboard lessons result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
updateData(DashboardItem.Lessons(error = it.error), forceRefresh)
|
||||
updateData(
|
||||
DashboardItem.Lessons(error = it.error), forceRefresh
|
||||
)
|
||||
}
|
||||
}
|
||||
}.launch("dashboard_lessons")
|
||||
}
|
||||
|
||||
private fun loadHomework(forceRefresh: Boolean) {
|
||||
private fun loadHomework(student: Student, forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
val date = LocalDate.now().nextOrSameSchoolDay
|
||||
|
||||
@ -443,6 +438,10 @@ class DashboardPresenter @Inject constructor(
|
||||
DashboardItem.Homework(it.data ?: emptyList(), isLoading = true),
|
||||
forceRefresh
|
||||
)
|
||||
|
||||
if (!it.data.isNullOrEmpty()) {
|
||||
firstLoadedItemList += DashboardItem.Type.HOMEWORK
|
||||
}
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard homework result: Success")
|
||||
@ -457,10 +456,8 @@ class DashboardPresenter @Inject constructor(
|
||||
}.launch("dashboard_homework")
|
||||
}
|
||||
|
||||
private fun loadSchoolAnnouncements(forceRefresh: Boolean) {
|
||||
private fun loadSchoolAnnouncements(student: Student, forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
|
||||
schoolAnnouncementRepository.getSchoolAnnouncements(student, forceRefresh)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
@ -468,11 +465,13 @@ class DashboardPresenter @Inject constructor(
|
||||
Timber.i("Loading dashboard announcements data started")
|
||||
if (forceRefresh) return@onEach
|
||||
updateData(
|
||||
DashboardItem.Announcements(
|
||||
it.data ?: emptyList(),
|
||||
isLoading = true
|
||||
), forceRefresh
|
||||
DashboardItem.Announcements(it.data ?: emptyList(), isLoading = true),
|
||||
forceRefresh
|
||||
)
|
||||
|
||||
if (!it.data.isNullOrEmpty()) {
|
||||
firstLoadedItemList += DashboardItem.Type.ANNOUNCEMENTS
|
||||
}
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard announcements result: Success")
|
||||
@ -487,9 +486,8 @@ class DashboardPresenter @Inject constructor(
|
||||
}.launch("dashboard_announcements")
|
||||
}
|
||||
|
||||
private fun loadExams(forceRefresh: Boolean) {
|
||||
private fun loadExams(student: Student, forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
|
||||
examRepository.getExams(
|
||||
@ -499,32 +497,41 @@ class DashboardPresenter @Inject constructor(
|
||||
end = LocalDate.now().plusDays(7),
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard exams data started")
|
||||
if (forceRefresh) return@onEach
|
||||
updateData(
|
||||
DashboardItem.Exams(it.data.orEmpty(), isLoading = true),
|
||||
forceRefresh
|
||||
)
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard exams result: Success")
|
||||
updateData(DashboardItem.Exams(it.data ?: emptyList()), forceRefresh)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading dashboard exams result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
updateData(DashboardItem.Exams(error = it.error), forceRefresh)
|
||||
}
|
||||
}
|
||||
.map { examResource ->
|
||||
val sortedExams = examResource.data?.sortedBy { it.date }
|
||||
|
||||
examResource.copy(data = sortedExams)
|
||||
}
|
||||
}.launch("dashboard_exams")
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard exams data started")
|
||||
if (forceRefresh) return@onEach
|
||||
updateData(
|
||||
DashboardItem.Exams(it.data.orEmpty(), isLoading = true),
|
||||
forceRefresh
|
||||
)
|
||||
|
||||
if (!it.data.isNullOrEmpty()) {
|
||||
firstLoadedItemList += DashboardItem.Type.EXAMS
|
||||
}
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard exams result: Success")
|
||||
updateData(DashboardItem.Exams(it.data ?: emptyList()), forceRefresh)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading dashboard exams result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
updateData(DashboardItem.Exams(error = it.error), forceRefresh)
|
||||
}
|
||||
}
|
||||
}.launch("dashboard_exams")
|
||||
}
|
||||
|
||||
private fun loadConferences(forceRefresh: Boolean) {
|
||||
private fun loadConferences(student: Student, forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
|
||||
conferenceRepository.getConferences(
|
||||
@ -542,6 +549,10 @@ class DashboardPresenter @Inject constructor(
|
||||
DashboardItem.Conferences(it.data ?: emptyList(), isLoading = true),
|
||||
forceRefresh
|
||||
)
|
||||
|
||||
if (!it.data.isNullOrEmpty()) {
|
||||
firstLoadedItemList += DashboardItem.Type.CONFERENCES
|
||||
}
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard conferences result: Success")
|
||||
@ -556,145 +567,119 @@ class DashboardPresenter @Inject constructor(
|
||||
}.launch("dashboard_conferences")
|
||||
}
|
||||
|
||||
private fun processHorizontalGroupData(
|
||||
luckyNumber: Int? = null,
|
||||
unreadMessagesCount: Int? = null,
|
||||
attendancePercentage: Double? = null,
|
||||
error: Throwable? = null,
|
||||
isLoading: Boolean = false,
|
||||
forceRefresh: Boolean
|
||||
) {
|
||||
val isLuckyNumberToLoad =
|
||||
dashboardTilesToLoad.any { it == DashboardItem.Tile.LUCKY_NUMBER }
|
||||
val isMessagesToLoad =
|
||||
dashboardTilesToLoad.any { it == DashboardItem.Tile.MESSAGES }
|
||||
val isAttendanceToLoad =
|
||||
dashboardTilesToLoad.any { it == DashboardItem.Tile.ATTENDANCE }
|
||||
val isPushedToList =
|
||||
dashboardItemLoadedList.any { it.type == DashboardItem.Type.HORIZONTAL_GROUP }
|
||||
private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) {
|
||||
val isForceRefreshError = forceRefresh && dashboardItem.error != null
|
||||
val isFirstRunDataLoadedError =
|
||||
dashboardItem.type in firstLoadedItemList && dashboardItem.error != null
|
||||
|
||||
if (error != null) {
|
||||
updateData(DashboardItem.HorizontalGroup(error = error), forceRefresh)
|
||||
return
|
||||
with(dashboardItemLoadedList) {
|
||||
removeAll { it.type == dashboardItem.type && !isForceRefreshError && !isFirstRunDataLoadedError }
|
||||
if (!isForceRefreshError && !isFirstRunDataLoadedError) add(dashboardItem)
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
val horizontalGroup =
|
||||
dashboardItemLoadedList.find { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup?
|
||||
val updatedHorizontalGroup =
|
||||
horizontalGroup?.copy(isLoading = true) ?: DashboardItem.HorizontalGroup(isLoading = true)
|
||||
sortDashboardItems()
|
||||
|
||||
updateData(updatedHorizontalGroup, forceRefresh)
|
||||
}
|
||||
|
||||
if (forceRefresh && !isPushedToList) {
|
||||
updateData(DashboardItem.HorizontalGroup(), forceRefresh)
|
||||
}
|
||||
|
||||
val horizontalGroup =
|
||||
dashboardItemLoadedList.single { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup
|
||||
|
||||
when {
|
||||
luckyNumber != null -> {
|
||||
updateData(horizontalGroup.copy(luckyNumber = luckyNumber), forceRefresh)
|
||||
}
|
||||
unreadMessagesCount != null -> {
|
||||
updateData(
|
||||
horizontalGroup.copy(unreadMessagesCount = unreadMessagesCount),
|
||||
forceRefresh
|
||||
)
|
||||
}
|
||||
attendancePercentage != null -> {
|
||||
updateData(
|
||||
horizontalGroup.copy(attendancePercentage = attendancePercentage),
|
||||
forceRefresh
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val isHorizontalGroupLoaded = dashboardItemLoadedList.any {
|
||||
if (it !is DashboardItem.HorizontalGroup) return@any false
|
||||
|
||||
val isLuckyNumberStateCorrect = (it.luckyNumber != null) == isLuckyNumberToLoad
|
||||
val isMessagesStateCorrect = (it.unreadMessagesCount != null) == isMessagesToLoad
|
||||
val isAttendanceStateCorrect = (it.attendancePercentage != null) == isAttendanceToLoad
|
||||
|
||||
isLuckyNumberStateCorrect && isAttendanceStateCorrect && isMessagesStateCorrect
|
||||
}
|
||||
|
||||
if (isHorizontalGroupLoaded) {
|
||||
val updatedHorizontalGroup =
|
||||
dashboardItemLoadedList.single { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup
|
||||
|
||||
updateData(updatedHorizontalGroup.copy(isLoading = false, error = null), forceRefresh)
|
||||
if (forceRefresh) {
|
||||
updateForceRefreshData(dashboardItem)
|
||||
} else {
|
||||
updateNormalData()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) {
|
||||
val isForceRefreshError = forceRefresh && dashboardItem.error != null
|
||||
val dashboardItemsPosition = preferencesRepository.dashboardItemsPosition
|
||||
|
||||
with(dashboardItemLoadedList) {
|
||||
removeAll { it.type == dashboardItem.type && !isForceRefreshError }
|
||||
if (!isForceRefreshError) add(dashboardItem)
|
||||
sortBy { tile -> dashboardItemsToLoad.single { it == tile.type }.ordinal }
|
||||
private fun updateNormalData() {
|
||||
val isItemsLoaded =
|
||||
dashboardItemsToLoad.all { type -> dashboardItemLoadedList.any { it.type == type } }
|
||||
val isItemsDataLoaded = isItemsLoaded && dashboardItemLoadedList.all {
|
||||
it.isDataLoaded || it.error != null
|
||||
}
|
||||
|
||||
if (forceRefresh) {
|
||||
with(dashboardItemRefreshLoadedList) {
|
||||
removeAll { it.type == dashboardItem.type }
|
||||
add(dashboardItem)
|
||||
if (isItemsDataLoaded) {
|
||||
view?.run {
|
||||
showProgress(false)
|
||||
showErrorView(false)
|
||||
showContent(true)
|
||||
updateData(dashboardItemLoadedList.toList())
|
||||
}
|
||||
}
|
||||
|
||||
showErrorIfExists(
|
||||
isItemsLoaded = isItemsLoaded,
|
||||
itemsLoadedList = dashboardItemLoadedList,
|
||||
forceRefresh = false
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateForceRefreshData(dashboardItem: DashboardItem) {
|
||||
with(dashboardItemRefreshLoadedList) {
|
||||
removeAll { it.type == dashboardItem.type }
|
||||
add(dashboardItem)
|
||||
}
|
||||
|
||||
val isRefreshItemLoaded =
|
||||
dashboardItemsToLoad.all { type -> dashboardItemRefreshLoadedList.any { it.type == type } }
|
||||
val isRefreshItemsDataLoaded = isRefreshItemLoaded && dashboardItemRefreshLoadedList.all {
|
||||
it.isDataLoaded || it.error != null
|
||||
}
|
||||
|
||||
if (isRefreshItemsDataLoaded) {
|
||||
view?.run {
|
||||
showRefresh(false)
|
||||
showErrorView(false)
|
||||
showContent(true)
|
||||
updateData(dashboardItemLoadedList.toList())
|
||||
}
|
||||
}
|
||||
|
||||
showErrorIfExists(
|
||||
isItemsLoaded = isRefreshItemLoaded,
|
||||
itemsLoadedList = dashboardItemRefreshLoadedList,
|
||||
forceRefresh = true
|
||||
)
|
||||
|
||||
if (isRefreshItemsDataLoaded) dashboardItemRefreshLoadedList.clear()
|
||||
}
|
||||
|
||||
private fun showErrorIfExists(
|
||||
isItemsLoaded: Boolean,
|
||||
itemsLoadedList: List<DashboardItem>,
|
||||
forceRefresh: Boolean
|
||||
) {
|
||||
val filteredItems = itemsLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT }
|
||||
val isAccountItemError =
|
||||
itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
|
||||
val isGeneralError =
|
||||
filteredItems.none { it.error == null } && filteredItems.isNotEmpty() || isAccountItemError
|
||||
val errorMessage = itemsLoadedList.map { it.error?.stackTraceToString() }.toString()
|
||||
|
||||
val filteredOriginalLoadedList =
|
||||
dashboardItemLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT }
|
||||
val wasAccountItemError =
|
||||
dashboardItemLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
|
||||
val wasGeneralError =
|
||||
filteredOriginalLoadedList.none { it.error == null } && filteredOriginalLoadedList.isNotEmpty() || wasAccountItemError
|
||||
|
||||
if (isGeneralError && isItemsLoaded) {
|
||||
lastError = Exception(errorMessage)
|
||||
|
||||
view?.run {
|
||||
showProgress(false)
|
||||
showRefresh(false)
|
||||
if ((forceRefresh && wasGeneralError) || !forceRefresh) {
|
||||
showContent(false)
|
||||
showErrorView(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sortDashboardItems() {
|
||||
val dashboardItemsPosition = preferencesRepository.dashboardItemsPosition
|
||||
|
||||
dashboardItemLoadedList.sortBy { tile ->
|
||||
dashboardItemsPosition?.getOrDefault(
|
||||
tile.type,
|
||||
tile.type.ordinal + 100
|
||||
) ?: tile.type.ordinal
|
||||
}
|
||||
|
||||
val isItemsLoaded =
|
||||
dashboardItemsToLoad.all { type -> dashboardItemLoadedList.any { it.type == type } }
|
||||
val isRefreshItemLoaded =
|
||||
dashboardItemsToLoad.all { type -> dashboardItemRefreshLoadedList.any { it.type == type } }
|
||||
val isItemsDataLoaded = isItemsLoaded && dashboardItemLoadedList.all {
|
||||
it.isDataLoaded || it.error != null
|
||||
}
|
||||
val isRefreshItemsDataLoaded = isRefreshItemLoaded && dashboardItemRefreshLoadedList.all {
|
||||
it.isDataLoaded || it.error != null
|
||||
}
|
||||
|
||||
if (isRefreshItemsDataLoaded) {
|
||||
view?.showRefresh(false)
|
||||
dashboardItemRefreshLoadedList.clear()
|
||||
}
|
||||
|
||||
view?.run {
|
||||
if (!forceRefresh) {
|
||||
showProgress(!isItemsDataLoaded)
|
||||
showContent(isItemsDataLoaded)
|
||||
}
|
||||
updateData(dashboardItemLoadedList.toList())
|
||||
}
|
||||
|
||||
if (isItemsLoaded) {
|
||||
val filteredItems =
|
||||
dashboardItemLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT }
|
||||
val isAccountItemError =
|
||||
dashboardItemLoadedList.single { it.type == DashboardItem.Type.ACCOUNT }.error != null
|
||||
val isGeneralError =
|
||||
filteredItems.all { it.error != null } && filteredItems.isNotEmpty() || isAccountItemError
|
||||
|
||||
val errorMessage = filteredItems.map { it.error?.stackTraceToString() }.toString()
|
||||
|
||||
lastError = Exception(errorMessage)
|
||||
|
||||
view?.run {
|
||||
showProgress(false)
|
||||
showContent(!isGeneralError)
|
||||
showErrorView(isGeneralError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -23,4 +23,6 @@ interface DashboardView : BaseView {
|
||||
fun resetView()
|
||||
|
||||
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 {
|
||||
block(studentRepository.getCurrentStudent(false))
|
||||
}
|
||||
|
@ -131,7 +131,9 @@ class GradeAverageProvider @Inject constructor(
|
||||
val updatedFirstSemesterGrades =
|
||||
firstSemesterSubject?.grades?.updateModifiers(student).orEmpty()
|
||||
|
||||
(updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage(isOptionalArithmeticAverage)
|
||||
(updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage(
|
||||
isOptionalArithmeticAverage
|
||||
)
|
||||
} else {
|
||||
secondSemesterSubject.average
|
||||
}
|
||||
@ -147,7 +149,8 @@ class GradeAverageProvider @Inject constructor(
|
||||
|
||||
return if (!isAnyVulcanAverage || isGradeAverageForceCalc) {
|
||||
val secondSemesterAverage =
|
||||
secondSemesterSubject.grades.updateModifiers(student).calcAverage(isOptionalArithmeticAverage)
|
||||
secondSemesterSubject.grades.updateModifiers(student)
|
||||
.calcAverage(isOptionalArithmeticAverage)
|
||||
val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student)
|
||||
?.calcAverage(isOptionalArithmeticAverage) ?: secondSemesterAverage
|
||||
|
||||
@ -213,7 +216,8 @@ class GradeAverageProvider @Inject constructor(
|
||||
proposedPoints = "",
|
||||
finalPoints = "",
|
||||
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.databinding.ItemGradeSummaryBinding
|
||||
import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding
|
||||
import io.github.wulkanowy.utils.calcAverage
|
||||
import io.github.wulkanowy.utils.calcFinalAverage
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -25,6 +25,10 @@ class GradeSummaryAdapter @Inject constructor(
|
||||
|
||||
var items = emptyList<GradeSummary>()
|
||||
|
||||
var onCalculatedHelpClickListener: () -> Unit = {}
|
||||
|
||||
var onFinalHelpClickListener: () -> Unit = {}
|
||||
|
||||
override fun getItemCount() = items.size + if (items.isNotEmpty()) 1 else 0
|
||||
|
||||
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 calculatedItemsCount = items.count { value -> value.average != 0.0 }
|
||||
val allItemsCount = items.count { !it.subject.equals("zachowanie", true) }
|
||||
val finalAverage = items.calcAverage(
|
||||
val finalAverage = items.calcFinalAverage(
|
||||
preferencesRepository.gradePlusModifier,
|
||||
preferencesRepository.gradeMinusModifier
|
||||
)
|
||||
@ -83,6 +87,9 @@ class GradeSummaryAdapter @Inject constructor(
|
||||
calculatedItemsCount,
|
||||
allItemsCount
|
||||
)
|
||||
|
||||
gradeSummaryCalculatedAverageHelp.setOnClickListener { onCalculatedHelpClickListener() }
|
||||
gradeSummaryFinalAverageHelp.setOnClickListener { onFinalHelpClickListener() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
@ -48,6 +49,11 @@ class GradeSummaryFragment :
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
with(gradeSummaryAdapter) {
|
||||
onCalculatedHelpClickListener = presenter::onCalculatedAverageHelpClick
|
||||
onFinalHelpClickListener = presenter::onFinalAverageHelpClick
|
||||
}
|
||||
|
||||
with(binding.gradeSummaryRecycler) {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = gradeSummaryAdapter
|
||||
@ -55,7 +61,11 @@ class GradeSummaryFragment :
|
||||
with(binding) {
|
||||
gradeSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
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() }
|
||||
gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
@ -107,6 +117,22 @@ class GradeSummaryFragment :
|
||||
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) {
|
||||
presenter.onParentViewLoadData(semesterId, forceRefresh)
|
||||
}
|
||||
|
@ -135,6 +135,14 @@ class GradeSummaryPresenter @Inject constructor(
|
||||
cancelJobs("load")
|
||||
}
|
||||
|
||||
fun onCalculatedAverageHelpClick() {
|
||||
view?.showCalculatedAverageHelpDialog()
|
||||
}
|
||||
|
||||
fun onFinalAverageHelpClick() {
|
||||
view?.showFinalAverageHelpDialog()
|
||||
}
|
||||
|
||||
private fun createGradeSummaryItems(items: List<GradeSubject>): List<GradeSummary> {
|
||||
return items
|
||||
.filter { !checkEmpty(it) }
|
||||
|
@ -33,6 +33,10 @@ interface GradeSummaryView : BaseView {
|
||||
|
||||
fun showEmpty(show: Boolean)
|
||||
|
||||
fun showCalculatedAverageHelpDialog()
|
||||
|
||||
fun showFinalAverageHelpDialog()
|
||||
|
||||
fun notifyParentDataLoaded(semesterId: Int)
|
||||
|
||||
fun notifyParentRefresh()
|
||||
|
@ -103,9 +103,8 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
|
||||
}
|
||||
|
||||
override fun notifyInitSymbolFragment(loginData: Triple<String, String, String>) {
|
||||
(loginAdapter.getFragmentInstance(1) as? LoginSymbolFragment)?.onParentInitSymbolFragment(
|
||||
loginData
|
||||
)
|
||||
(loginAdapter.getFragmentInstance(1) as? LoginSymbolFragment)
|
||||
?.onParentInitSymbolFragment(loginData)
|
||||
}
|
||||
|
||||
override fun notifyInitStudentSelectFragment(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||
|
@ -13,7 +13,7 @@ import javax.inject.Inject
|
||||
|
||||
class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
|
||||
|
||||
var onBadCredentials: () -> Unit = {}
|
||||
var onBadCredentials: (String?) -> Unit = {}
|
||||
|
||||
var onInvalidToken: (String) -> Unit = {}
|
||||
|
||||
@ -25,7 +25,7 @@ class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler
|
||||
|
||||
override fun proceed(error: Throwable) {
|
||||
when (error) {
|
||||
is BadCredentialsException -> onBadCredentials()
|
||||
is BadCredentialsException -> onBadCredentials(error.message)
|
||||
is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student))
|
||||
is TokenDeadException -> onInvalidToken(resources.getString(R.string.login_expired_token))
|
||||
is InvalidTokenException -> onInvalidToken(resources.getString(R.string.login_invalid_token))
|
||||
|
@ -51,10 +51,12 @@ class LoginAdvancedFragment :
|
||||
private lateinit var hostSymbols: Array<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
|
||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty()
|
||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||
.orEmpty()
|
||||
|
||||
override val formPinValue: String
|
||||
get() = binding.loginFormPin.text.toString().trim()
|
||||
@ -92,39 +94,62 @@ class LoginAdvancedFragment :
|
||||
loginFormSignIn.setOnClickListener { presenter.onSignInClick() }
|
||||
|
||||
loginTypeSwitch.setOnCheckedChangeListener { _, checkedId ->
|
||||
presenter.onLoginModeSelected(when (checkedId) {
|
||||
R.id.loginTypeApi -> Sdk.Mode.API
|
||||
R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER
|
||||
else -> Sdk.Mode.HYBRID
|
||||
})
|
||||
presenter.onLoginModeSelected(
|
||||
when (checkedId) {
|
||||
R.id.loginTypeApi -> Sdk.Mode.API
|
||||
R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER
|
||||
else -> Sdk.Mode.HYBRID
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
loginFormPin.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) {
|
||||
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() }
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
binding.loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_scraper)
|
||||
binding.loginFormAdvancedWarningInfo.text =
|
||||
getString(R.string.login_advanced_warning_scraper)
|
||||
}
|
||||
|
||||
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) {
|
||||
loginFormUsername.setText(username)
|
||||
loginFormPass.setText(pass)
|
||||
@ -177,10 +202,10 @@ class LoginAdvancedFragment :
|
||||
}
|
||||
}
|
||||
|
||||
override fun setErrorPassIncorrect() {
|
||||
override fun setErrorPassIncorrect(message: String?) {
|
||||
with(binding.loginFormPassLayout) {
|
||||
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>) {
|
||||
(activity as? LoginActivity)?.onFormFragmentAccountLogged(studentsWithSemesters, Triple(
|
||||
binding.loginFormUsername.text.toString(),
|
||||
binding.loginFormPass.text.toString(),
|
||||
resources.getStringArray(R.array.hosts_values)[1]
|
||||
))
|
||||
(activity as? LoginActivity)?.onFormFragmentAccountLogged(
|
||||
studentsWithSemesters, Triple(
|
||||
binding.loginFormUsername.text.toString(),
|
||||
binding.loginFormPass.text.toString(),
|
||||
resources.getStringArray(R.array.hosts_values)[1]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -34,9 +34,9 @@ class LoginAdvancedPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun onBadCredentials() {
|
||||
private fun onBadCredentials(message: String?) {
|
||||
view?.run {
|
||||
setErrorPassIncorrect()
|
||||
setErrorPassIncorrect(message)
|
||||
showSoftKeyboard()
|
||||
Timber.i("Entered wrong username or password")
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ interface LoginAdvancedView : BaseView {
|
||||
|
||||
fun setErrorPassInvalid(focus: Boolean)
|
||||
|
||||
fun setErrorPassIncorrect()
|
||||
fun setErrorPassIncorrect(message: String?)
|
||||
|
||||
fun clearUsernameError()
|
||||
|
||||
|
@ -5,6 +5,7 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
@ -41,10 +42,12 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
get() = binding.loginFormPass.text.toString()
|
||||
|
||||
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
|
||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty()
|
||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||
.orEmpty()
|
||||
|
||||
override val nicknameLabel: String
|
||||
get() = getString(R.string.login_nickname_hint)
|
||||
@ -88,7 +91,13 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
|
||||
with(binding.loginFormHost) {
|
||||
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() }
|
||||
}
|
||||
}
|
||||
@ -142,24 +151,31 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
}
|
||||
}
|
||||
|
||||
override fun setErrorPassIncorrect() {
|
||||
with(binding.loginFormPassLayout) {
|
||||
error = getString(R.string.login_incorrect_password)
|
||||
override fun setErrorPassIncorrect(message: String?) {
|
||||
val error = message ?: getString(R.string.login_incorrect_password_default)
|
||||
|
||||
with(binding) {
|
||||
loginFormUsernameLayout.error = " "
|
||||
loginFormPassLayout.error = " "
|
||||
loginFormErrorBox.text = getString(R.string.login_incorrect_password, error)
|
||||
loginFormErrorBox.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun setErrorEmailInvalid(domain: String) {
|
||||
with(binding.loginFormUsernameLayout) {
|
||||
error = getString(R.string.login_invalid_custom_email,domain)
|
||||
error = getString(R.string.login_invalid_custom_email, domain)
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearUsernameError() {
|
||||
binding.loginFormUsernameLayout.error = null
|
||||
binding.loginFormErrorBox.isVisible = false
|
||||
}
|
||||
|
||||
override fun clearPassError() {
|
||||
binding.loginFormPassLayout.error = null
|
||||
binding.loginFormErrorBox.isVisible = false
|
||||
}
|
||||
|
||||
override fun showSoftKeyboard() {
|
||||
@ -183,12 +199,18 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -210,7 +232,10 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -223,7 +248,8 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
chooserTitle = requireContext().getString(R.string.login_email_intent_title),
|
||||
email = "wulkanowyinc@gmail.com",
|
||||
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.systemVersion.toString(),
|
||||
appInfo.versionName,
|
||||
|
@ -30,7 +30,7 @@ class LoginFormPresenter @Inject constructor(
|
||||
showVersion()
|
||||
|
||||
loginErrorHandler.onBadCredentials = {
|
||||
setErrorPassIncorrect()
|
||||
setErrorPassIncorrect(it)
|
||||
showSoftKeyboard()
|
||||
Timber.i("Entered wrong username or password")
|
||||
}
|
||||
@ -90,10 +90,10 @@ class LoginFormPresenter @Inject constructor(
|
||||
|
||||
flowWithResource {
|
||||
studentRepository.getStudentsScrapper(
|
||||
email,
|
||||
password,
|
||||
host,
|
||||
symbol
|
||||
email = email,
|
||||
password = password,
|
||||
scrapperBaseUrl = host,
|
||||
symbol = symbol
|
||||
)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
|
@ -37,7 +37,7 @@ interface LoginFormView : BaseView {
|
||||
|
||||
fun setErrorPassInvalid(focus: Boolean)
|
||||
|
||||
fun setErrorPassIncorrect()
|
||||
fun setErrorPassIncorrect(message: String?)
|
||||
|
||||
fun setErrorEmailInvalid(domain: String)
|
||||
|
||||
|
@ -78,7 +78,9 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.d("Login student select students load started")
|
||||
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 -> {
|
||||
errorHandler.dispatch(it.error!!)
|
||||
@ -95,35 +97,32 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun registerStudents(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||
flowWithResource {
|
||||
val savedStudents = studentRepository.saveStudents(studentsWithSemesters)
|
||||
val firstRegistered = studentsWithSemesters.first().apply { student.id = savedStudents.first() }
|
||||
studentRepository.switchStudent(firstRegistered)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> view?.run {
|
||||
Timber.i("Registration started")
|
||||
showProgress(true)
|
||||
showContent(false)
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Registration result: Success")
|
||||
view?.openMainView()
|
||||
logRegisterEvent(studentsWithSemesters)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Registration result: An exception occurred ")
|
||||
view?.apply {
|
||||
showProgress(false)
|
||||
showContent(true)
|
||||
showContact(true)
|
||||
flowWithResource { studentRepository.saveStudents(studentsWithSemesters) }
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> view?.run {
|
||||
Timber.i("Registration started")
|
||||
showProgress(true)
|
||||
showContent(false)
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Registration result: Success")
|
||||
view?.openMainView()
|
||||
logRegisterEvent(studentsWithSemesters)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Registration result: An exception occurred ")
|
||||
view?.apply {
|
||||
showProgress(false)
|
||||
showContent(true)
|
||||
showContact(true)
|
||||
}
|
||||
lastError = it.error
|
||||
loginErrorHandler.dispatch(it.error!!)
|
||||
logRegisterEvent(studentsWithSemesters, it.error)
|
||||
}
|
||||
lastError = it.error
|
||||
loginErrorHandler.dispatch(it.error!!)
|
||||
logRegisterEvent(studentsWithSemesters, it.error)
|
||||
}
|
||||
}
|
||||
}.launch("register")
|
||||
}.launch("register")
|
||||
}
|
||||
|
||||
fun onDiscordClick() {
|
||||
@ -134,7 +133,10 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
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 ->
|
||||
analytics.logEvent(
|
||||
"registration_student_select",
|
||||
|
@ -131,7 +131,9 @@ class LuckyNumberHistoryFragment :
|
||||
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) {
|
||||
|
@ -79,12 +79,7 @@ class MessagePreviewAdapter @Inject constructor() :
|
||||
|
||||
val readText = when {
|
||||
recipientCount > 1 -> {
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.message_read_by,
|
||||
message.readBy,
|
||||
message.readBy,
|
||||
recipientCount
|
||||
)
|
||||
context.getString(R.string.message_read_by, message.readBy, recipientCount)
|
||||
}
|
||||
message.readBy == 1 -> {
|
||||
context.getString(R.string.message_read, context.getString(R.string.all_yes))
|
||||
|
@ -117,6 +117,7 @@ class MessageTabPresenter @Inject constructor(
|
||||
if (!it.data.isNullOrEmpty()) {
|
||||
view?.run {
|
||||
enableSwipe(true)
|
||||
showErrorView(false)
|
||||
showRefresh(true)
|
||||
showProgress(false)
|
||||
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.exam.ExamFragment
|
||||
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
|
||||
@ -66,6 +67,9 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
|
||||
override val examRes: Pair<String, Drawable?>?
|
||||
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?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentMoreBinding.bind(view)
|
||||
@ -128,6 +132,10 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
|
||||
(activity as? MainActivity)?.pushView(ExamFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun openLuckyNumberView() {
|
||||
(activity as? MainActivity)?.pushView(LuckyNumberFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun popView(depth: Int) {
|
||||
(activity as? MainActivity)?.popView(depth)
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ class MorePresenter @Inject constructor(
|
||||
schoolAndTeachersRes?.first -> openSchoolAndTeachersView()
|
||||
mobileDevicesRes?.first -> openMobileDevicesView()
|
||||
settingsRes?.first -> openSettingsView()
|
||||
luckyNumberRes?.first -> openLuckyNumberView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -48,6 +49,7 @@ class MorePresenter @Inject constructor(
|
||||
examRes,
|
||||
homeworkRes,
|
||||
noteRes,
|
||||
luckyNumberRes,
|
||||
conferencesRes,
|
||||
schoolAnnouncementRes,
|
||||
schoolAndTeachersRes,
|
||||
|
@ -23,6 +23,8 @@ interface MoreView : BaseView {
|
||||
|
||||
val examRes: Pair<String, Drawable?>?
|
||||
|
||||
val luckyNumberRes: Pair<String, Drawable?>?
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<Pair<String, Drawable?>>)
|
||||
@ -46,4 +48,6 @@ interface MoreView : BaseView {
|
||||
fun openMobileDevicesView()
|
||||
|
||||
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)
|
||||
}
|
@ -2,7 +2,7 @@ package io.github.wulkanowy.ui.modules.schoolannouncement
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.databinding.ItemSchoolAnnouncementBinding
|
||||
@ -14,6 +14,8 @@ class SchoolAnnouncementAdapter @Inject constructor() :
|
||||
|
||||
var items = emptyList<SchoolAnnouncement>()
|
||||
|
||||
var onItemClickListener: (SchoolAnnouncement) -> Unit = {}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
@ -26,9 +28,9 @@ class SchoolAnnouncementAdapter @Inject constructor() :
|
||||
with(holder.binding) {
|
||||
schoolAnnouncementItemDate.text = item.date.toFormattedString()
|
||||
schoolAnnouncementItemType.text = item.subject
|
||||
schoolAnnouncementItemContent.text = HtmlCompat.fromHtml(
|
||||
item.content, HtmlCompat.FROM_HTML_MODE_COMPACT
|
||||
)
|
||||
schoolAnnouncementItemContent.text = item.content.parseAsHtml()
|
||||
|
||||
root.setOnClickListener { onItemClickListener(item) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,54 @@
|
||||
package io.github.wulkanowy.ui.modules.schoolannouncement
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.databinding.DialogSchoolAnnouncementBinding
|
||||
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
|
||||
class SchoolAnnouncementDialog : DialogFragment() {
|
||||
|
||||
private var binding: DialogSchoolAnnouncementBinding by lifecycleAwareVariable()
|
||||
|
||||
private lateinit var announcement: SchoolAnnouncement
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "item"
|
||||
|
||||
fun newInstance(exam: SchoolAnnouncement) = SchoolAnnouncementDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
arguments?.run {
|
||||
announcement = getSerializable(ARGUMENT_KEY) as SchoolAnnouncement
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogSchoolAnnouncementBinding.inflate(inflater).also { binding = it }.root
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
with(binding) {
|
||||
announcementDialogSubjectValue.text = announcement.subject
|
||||
announcementDialogDateValue.text = announcement.date.toFormattedString()
|
||||
announcementDialogDescriptionValue.text = announcement.content.parseAsHtml()
|
||||
|
||||
announcementDialogClose.setOnClickListener { dismiss() }
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user