Compare commits
32 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 |
10
.github/workflows/deploy-store.yml
vendored
10
.github/workflows/deploy-store.yml
vendored
@ -28,15 +28,14 @@ jobs:
|
|||||||
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
||||||
run: |
|
run: |
|
||||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg
|
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg
|
||||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg
|
|
||||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
||||||
- name: Upload apk to google play
|
- name: Upload apk to google play
|
||||||
env:
|
env:
|
||||||
|
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||||
PLAY_SERVICE_ACCOUNT_EMAIL: ${{ secrets.PLAY_SERVICE_ACCOUNT_EMAIL }}
|
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
|
||||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
|
||||||
run: ./gradlew publishPlayRelease -PenableFirebase --stacktrace;
|
|
||||||
|
|
||||||
deploy-app-gallery:
|
deploy-app-gallery:
|
||||||
name: Deploy to AppGallery
|
name: Deploy to AppGallery
|
||||||
@ -60,7 +59,6 @@ jobs:
|
|||||||
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
||||||
run: |
|
run: |
|
||||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg
|
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg
|
||||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg
|
|
||||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
||||||
- name: Prepare credentials
|
- name: Prepare credentials
|
||||||
env:
|
env:
|
||||||
@ -68,7 +66,7 @@ jobs:
|
|||||||
run: echo $AGC_CREDENTIALS > ./app/src/release/agconnect-credentials.json
|
run: echo $AGC_CREDENTIALS > ./app/src/release/agconnect-credentials.json
|
||||||
- name: Build and publish HMS version
|
- name: Build and publish HMS version
|
||||||
env:
|
env:
|
||||||
|
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
|
||||||
run: ./gradlew bundleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace
|
run: ./gradlew bundleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace
|
||||||
|
2
LICENSE
2
LICENSE
@ -186,7 +186,7 @@
|
|||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright 2019 Wulkanowy
|
Copyright 2021 Wulkanowy
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -21,8 +21,8 @@ android {
|
|||||||
testApplicationId "io.github.tests.wulkanowy"
|
testApplicationId "io.github.tests.wulkanowy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 95
|
versionCode 97
|
||||||
versionName "1.2.2"
|
versionName "1.3.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|
||||||
@ -96,10 +96,20 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playConfigs {
|
||||||
|
play { enabled.set(true) }
|
||||||
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
viewBinding true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bundle {
|
||||||
|
language {
|
||||||
|
enableSplit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
testOptions.unitTests {
|
testOptions.unitTests {
|
||||||
includeAndroidResources = true
|
includeAndroidResources = true
|
||||||
}
|
}
|
||||||
@ -130,11 +140,10 @@ kapt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
play {
|
play {
|
||||||
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
|
|
||||||
serviceAccountCredentials = file('key.p12')
|
|
||||||
defaultToAppBundles = false
|
defaultToAppBundles = false
|
||||||
track = 'production'
|
track = 'production'
|
||||||
updatePriority = 3
|
updatePriority = 3
|
||||||
|
enabled.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
huaweiPublish {
|
huaweiPublish {
|
||||||
@ -157,7 +166,7 @@ ext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.github.wulkanowy:sdk:1.2.2"
|
implementation "io.github.wulkanowy:sdk:1.3.0"
|
||||||
|
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
@ -174,7 +183,7 @@ dependencies {
|
|||||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||||
implementation "androidx.viewpager:viewpager:1.0.0"
|
implementation "androidx.viewpager:viewpager:1.0.0"
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.1.0"
|
implementation "androidx.constraintlayout:constraintlayout:2.1.1"
|
||||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
|
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
|
||||||
implementation "com.google.android.material:material:1.4.0"
|
implementation "com.google.android.material:material:1.4.0"
|
||||||
implementation "com.github.wulkanowy:material-chips-input:2.3.1"
|
implementation "com.github.wulkanowy:material-chips-input:2.3.1"
|
||||||
@ -211,11 +220,11 @@ dependencies {
|
|||||||
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
||||||
implementation 'com.fredporciuncula:flow-preferences:1.5.0'
|
implementation 'com.fredporciuncula:flow-preferences:1.5.0'
|
||||||
|
|
||||||
playImplementation platform('com.google.firebase:firebase-bom:28.4.0')
|
playImplementation platform('com.google.firebase:firebase-bom:28.4.1')
|
||||||
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
||||||
playImplementation 'com.google.firebase:firebase-messaging:'
|
playImplementation 'com.google.firebase:firebase-messaging:'
|
||||||
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
||||||
playImplementation 'com.google.android.play:core:1.10.1'
|
playImplementation 'com.google.android.play:core:1.10.2'
|
||||||
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
||||||
|
|
||||||
hmsImplementation 'com.huawei.hms:hianalytics:6.2.0.301'
|
hmsImplementation 'com.huawei.hms:hianalytics:6.2.0.301'
|
||||||
|
BIN
app/key.p12.gpg
BIN
app/key.p12.gpg
Binary file not shown.
2316
app/schemas/io.github.wulkanowy.data.db.AppDatabase/40.json
Normal file
2316
app/schemas/io.github.wulkanowy.data.db.AppDatabase/40.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -45,6 +45,7 @@
|
|||||||
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.splash.SplashActivity"
|
android:name=".ui.modules.splash.SplashActivity"
|
||||||
|
android:exported="true"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/WulkanowyTheme.SplashScreen"
|
android:theme="@style/WulkanowyTheme.SplashScreen"
|
||||||
tools:ignore="LockedOrientationActivity">
|
tools:ignore="LockedOrientationActivity">
|
||||||
@ -74,6 +75,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="true"
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -83,6 +85,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity"
|
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="true"
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -93,6 +96,22 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".services.widgets.TimetableWidgetService"
|
android:name=".services.widgets.TimetableWidgetService"
|
||||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||||
|
<service
|
||||||
|
android:name=".services.piggyback.VulcanNotificationListenerService"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.service.notification.NotificationListenerService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
<service
|
||||||
|
android:name=".services.messaging.AppMessagingService"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
|
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
|
||||||
@ -107,6 +126,7 @@
|
|||||||
</receiver>
|
</receiver>
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
|
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/lucky_number_title">
|
android:label="@string/lucky_number_title">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
@ -8,8 +8,8 @@ import androidx.preference.PreferenceManager
|
|||||||
import com.chuckerteam.chucker.api.ChuckerCollector
|
import com.chuckerteam.chucker.api.ChuckerCollector
|
||||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||||
import com.chuckerteam.chucker.api.RetentionManager
|
import com.chuckerteam.chucker.api.RetentionManager
|
||||||
import com.squareup.moshi.Moshi
|
|
||||||
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
@ -202,4 +202,8 @@ internal class RepositoryModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSchoolAnnouncementDao(database: AppDatabase) = database.schoolAnnouncementDao
|
fun provideSchoolAnnouncementDao(database: AppDatabase) = database.schoolAnnouncementDao
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideNotificationDao(database: AppDatabase) = database.notificationDao
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import io.github.wulkanowy.data.db.dao.AttendanceDao
|
|||||||
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||||
import io.github.wulkanowy.data.db.dao.ConferenceDao
|
import io.github.wulkanowy.data.db.dao.ConferenceDao
|
||||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
|
||||||
import io.github.wulkanowy.data.db.dao.ExamDao
|
import io.github.wulkanowy.data.db.dao.ExamDao
|
||||||
import io.github.wulkanowy.data.db.dao.GradeDao
|
import io.github.wulkanowy.data.db.dao.GradeDao
|
||||||
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
|
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
|
||||||
@ -23,8 +22,10 @@ import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
|||||||
import io.github.wulkanowy.data.db.dao.MessagesDao
|
import io.github.wulkanowy.data.db.dao.MessagesDao
|
||||||
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
||||||
import io.github.wulkanowy.data.db.dao.NoteDao
|
import io.github.wulkanowy.data.db.dao.NoteDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.NotificationDao
|
||||||
import io.github.wulkanowy.data.db.dao.RecipientDao
|
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||||
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||||
import io.github.wulkanowy.data.db.dao.SchoolDao
|
import io.github.wulkanowy.data.db.dao.SchoolDao
|
||||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||||
@ -38,7 +39,6 @@ import io.github.wulkanowy.data.db.entities.Attendance
|
|||||||
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||||
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
||||||
import io.github.wulkanowy.data.db.entities.Conference
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
|
||||||
import io.github.wulkanowy.data.db.entities.Exam
|
import io.github.wulkanowy.data.db.entities.Exam
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
|
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
|
||||||
@ -51,9 +51,11 @@ import io.github.wulkanowy.data.db.entities.Message
|
|||||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
||||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||||
import io.github.wulkanowy.data.db.entities.Note
|
import io.github.wulkanowy.data.db.entities.Note
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
import io.github.wulkanowy.data.db.entities.Recipient
|
import io.github.wulkanowy.data.db.entities.Recipient
|
||||||
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
||||||
import io.github.wulkanowy.data.db.entities.School
|
import io.github.wulkanowy.data.db.entities.School
|
||||||
|
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||||
@ -95,6 +97,7 @@ import io.github.wulkanowy.data.db.migrations.Migration37
|
|||||||
import io.github.wulkanowy.data.db.migrations.Migration38
|
import io.github.wulkanowy.data.db.migrations.Migration38
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration39
|
import io.github.wulkanowy.data.db.migrations.Migration39
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration40
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||||
@ -134,6 +137,7 @@ import javax.inject.Singleton
|
|||||||
StudentInfo::class,
|
StudentInfo::class,
|
||||||
TimetableHeader::class,
|
TimetableHeader::class,
|
||||||
SchoolAnnouncement::class,
|
SchoolAnnouncement::class,
|
||||||
|
Notification::class
|
||||||
],
|
],
|
||||||
version = AppDatabase.VERSION_SCHEMA,
|
version = AppDatabase.VERSION_SCHEMA,
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
@ -142,7 +146,7 @@ import javax.inject.Singleton
|
|||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VERSION_SCHEMA = 39
|
const val VERSION_SCHEMA = 40
|
||||||
|
|
||||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||||
Migration2(),
|
Migration2(),
|
||||||
@ -183,6 +187,7 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
Migration37(),
|
Migration37(),
|
||||||
Migration38(),
|
Migration38(),
|
||||||
Migration39(),
|
Migration39(),
|
||||||
|
Migration40()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
@ -252,4 +257,6 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
abstract val timetableHeaderDao: TimetableHeaderDao
|
abstract val timetableHeaderDao: TimetableHeaderDao
|
||||||
|
|
||||||
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
|
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
|
||||||
|
|
||||||
|
abstract val notificationDao: NotificationDao
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
package io.github.wulkanowy.data.db.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Dao
|
||||||
|
interface NotificationDao : BaseDao<Notification> {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Notifications WHERE student_id = :studentId OR student_id = -1")
|
||||||
|
fun loadAll(studentId: Long): Flow<List<Notification>>
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package io.github.wulkanowy.data.db.entities
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
@Entity(tableName = "Notifications")
|
||||||
|
data class Notification(
|
||||||
|
|
||||||
|
@ColumnInfo(name = "student_id")
|
||||||
|
val studentId: Long,
|
||||||
|
|
||||||
|
val title: String,
|
||||||
|
|
||||||
|
val content: String,
|
||||||
|
|
||||||
|
val type: NotificationType,
|
||||||
|
|
||||||
|
val date: LocalDateTime,
|
||||||
|
|
||||||
|
val data: String? = null
|
||||||
|
) {
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
var id: Long = 0
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package io.github.wulkanowy.data.db.migrations
|
||||||
|
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
|
||||||
|
class Migration40 : Migration(39, 40) {
|
||||||
|
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS `Notifications` (
|
||||||
|
`student_id` INTEGER NOT NULL,
|
||||||
|
`title` TEXT NOT NULL,
|
||||||
|
`content` TEXT NOT NULL,
|
||||||
|
`type` TEXT NOT NULL,
|
||||||
|
`date` INTEGER NOT NULL,
|
||||||
|
`data` TEXT,
|
||||||
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@ import androidx.annotation.StringRes
|
|||||||
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
|
|
||||||
sealed interface Notification {
|
sealed interface NotificationData {
|
||||||
val type: NotificationType
|
val type: NotificationType
|
||||||
val startMenu: MainView.Section
|
val startMenu: MainView.Section
|
||||||
val icon: Int
|
val icon: Int
|
||||||
@ -14,7 +14,7 @@ sealed interface Notification {
|
|||||||
val contentStringRes: Int
|
val contentStringRes: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MultipleNotifications(
|
data class MultipleNotificationsData(
|
||||||
override val type: NotificationType,
|
override val type: NotificationType,
|
||||||
override val startMenu: MainView.Section,
|
override val startMenu: MainView.Section,
|
||||||
@DrawableRes override val icon: Int,
|
@DrawableRes override val icon: Int,
|
||||||
@ -23,9 +23,9 @@ data class MultipleNotifications(
|
|||||||
|
|
||||||
@PluralsRes val summaryStringRes: Int,
|
@PluralsRes val summaryStringRes: Int,
|
||||||
val lines: List<String>,
|
val lines: List<String>,
|
||||||
) : Notification
|
) : NotificationData
|
||||||
|
|
||||||
data class OneNotification(
|
data class OneNotificationData(
|
||||||
override val type: NotificationType,
|
override val type: NotificationType,
|
||||||
override val startMenu: MainView.Section,
|
override val startMenu: MainView.Section,
|
||||||
@DrawableRes override val icon: Int,
|
@DrawableRes override val icon: Int,
|
||||||
@ -33,4 +33,4 @@ data class OneNotification(
|
|||||||
@StringRes override val contentStringRes: Int,
|
@StringRes override val contentStringRes: Int,
|
||||||
|
|
||||||
val contentValues: List<String>,
|
val contentValues: List<String>,
|
||||||
) : Notification
|
) : NotificationData
|
@ -32,10 +32,23 @@ class AttendanceRepository @Inject constructor(
|
|||||||
|
|
||||||
private val cacheKey = "attendance"
|
private val cacheKey = "attendance"
|
||||||
|
|
||||||
fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
fun getAttendance(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
start: LocalDate,
|
||||||
|
end: LocalDate,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
shouldFetch = {
|
||||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) },
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
key = getRefreshKey(cacheKey, semester, start, end)
|
||||||
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
|
query = {
|
||||||
|
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
||||||
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
.getAttendance(start.monday, end.sunday, semester.semesterId)
|
.getAttendance(start.monday, end.sunday, semester.semesterId)
|
||||||
@ -50,12 +63,17 @@ class AttendanceRepository @Inject constructor(
|
|||||||
filterResult = { it.filter { item -> item.date in start..end } }
|
filterResult = { it.filter { item -> item.date in start..end } }
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun excuseForAbsence(student: Student, semester: Semester, absenceList: List<Attendance>, reason: String? = null) {
|
suspend fun excuseForAbsence(
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).excuseForAbsence(absenceList.map { attendance ->
|
student: Student, semester: Semester,
|
||||||
|
absenceList: List<Attendance>, reason: String? = null
|
||||||
|
) {
|
||||||
|
val items = absenceList.map { attendance ->
|
||||||
Absent(
|
Absent(
|
||||||
date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)),
|
date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)),
|
||||||
timeId = attendance.timeId
|
timeId = attendance.timeId
|
||||||
)
|
)
|
||||||
}, reason)
|
}
|
||||||
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
.excuseForAbsence(items, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,12 +29,12 @@ class AttendanceSummaryRepository @Inject constructor(
|
|||||||
student: Student,
|
student: Student,
|
||||||
semester: Semester,
|
semester: Semester,
|
||||||
subjectId: Int,
|
subjectId: Int,
|
||||||
forceRefresh: Boolean
|
forceRefresh: Boolean,
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
it.isEmpty() || forceRefresh
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
|
@ -28,10 +28,28 @@ class CompletedLessonsRepository @Inject constructor(
|
|||||||
|
|
||||||
private val cacheKey = "completed"
|
private val cacheKey = "completed"
|
||||||
|
|
||||||
fun getCompletedLessons(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
fun getCompletedLessons(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
start: LocalDate,
|
||||||
|
end: LocalDate,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
shouldFetch = {
|
||||||
query = { completedLessonsDb.loadAll(semester.studentId, semester.diaryId, start.monday, end.sunday) },
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
key = getRefreshKey(cacheKey, semester, start, end)
|
||||||
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
|
query = {
|
||||||
|
completedLessonsDb.loadAll(
|
||||||
|
studentId = semester.studentId,
|
||||||
|
diaryId = semester.diaryId,
|
||||||
|
from = start.monday,
|
||||||
|
end = end.sunday
|
||||||
|
)
|
||||||
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
.getCompletedLessons(start.monday, end.sunday)
|
.getCompletedLessons(start.monday, end.sunday)
|
||||||
|
@ -35,12 +35,12 @@ class ConferenceRepository @Inject constructor(
|
|||||||
semester: Semester,
|
semester: Semester,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
notify: Boolean = false,
|
notify: Boolean = false,
|
||||||
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)
|
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
it.isEmpty() || forceRefresh
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
|
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
|
||||||
|
@ -36,14 +36,14 @@ class ExamRepository @Inject constructor(
|
|||||||
start: LocalDate,
|
start: LocalDate,
|
||||||
end: LocalDate,
|
end: LocalDate,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
notify: Boolean = false
|
notify: Boolean = false,
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
key = getRefreshKey(cacheKey, semester, start, end)
|
key = getRefreshKey(cacheKey, semester, start, end)
|
||||||
)
|
)
|
||||||
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
examDb.loadAll(
|
examDb.loadAll(
|
||||||
|
@ -37,13 +37,12 @@ class GradeRepository @Inject constructor(
|
|||||||
student: Student,
|
student: Student,
|
||||||
semester: Semester,
|
semester: Semester,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
notify: Boolean = false
|
notify: Boolean = false,
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { (details, summaries) ->
|
shouldFetch = { (details, summaries) ->
|
||||||
val isShouldBeRefreshed =
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired
|
||||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed
|
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
||||||
@ -71,8 +70,8 @@ class GradeRepository @Inject constructor(
|
|||||||
newDetails: List<Grade>,
|
newDetails: List<Grade>,
|
||||||
notify: Boolean
|
notify: Boolean
|
||||||
) {
|
) {
|
||||||
val notifyBreakDate =
|
val notifyBreakDate = oldGrades.maxByOrNull {it.date }
|
||||||
oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate()
|
?.date ?: student.registrationDate.toLocalDate()
|
||||||
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
||||||
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
||||||
if (it.date >= notifyBreakDate) it.apply {
|
if (it.date >= notifyBreakDate) it.apply {
|
||||||
@ -89,8 +88,7 @@ class GradeRepository @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
||||||
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
||||||
val oldSummary =
|
val oldSummary = oldSummaries.find { old -> old.subject == summary.subject }
|
||||||
oldSummaries.find { oldSummary -> oldSummary.subject == summary.subject }
|
|
||||||
summary.isPredictedGradeNotified = when {
|
summary.isPredictedGradeNotified = when {
|
||||||
summary.predictedGrade.isEmpty() -> true
|
summary.predictedGrade.isEmpty() -> true
|
||||||
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
||||||
|
@ -39,9 +39,19 @@ class GradeStatisticsRepository @Inject constructor(
|
|||||||
private val semesterCacheKey = "grade_stats_semester"
|
private val semesterCacheKey = "grade_stats_semester"
|
||||||
private val pointsCacheKey = "grade_stats_points"
|
private val pointsCacheKey = "grade_stats_points"
|
||||||
|
|
||||||
fun getGradesPartialStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
fun getGradesPartialStatistics(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
subjectName: String,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = partialMutex,
|
mutex = partialMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(partialCacheKey, semester)) },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
key = getRefreshKey(partialCacheKey, semester)
|
||||||
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
@ -76,9 +86,19 @@ class GradeStatisticsRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getGradesSemesterStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
fun getGradesSemesterStatistics(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
subjectName: String,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = semesterMutex,
|
mutex = semesterMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(semesterCacheKey, semester)) },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
key = getRefreshKey(semesterCacheKey, semester)
|
||||||
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
@ -94,10 +114,12 @@ class GradeStatisticsRepository @Inject constructor(
|
|||||||
val itemsWithAverage = items.map { item ->
|
val itemsWithAverage = items.map { item ->
|
||||||
item.copy().apply {
|
item.copy().apply {
|
||||||
val denominator = item.amounts.sum()
|
val denominator = item.amounts.sum()
|
||||||
average = if (denominator == 0) "" else (item.amounts.mapIndexed { gradeValue, amount ->
|
average = if (denominator == 0) "" else {
|
||||||
(gradeValue + 1) * amount
|
(item.amounts.mapIndexed { gradeValue, amount ->
|
||||||
}.sum().toDouble() / denominator).let {
|
(gradeValue + 1) * amount
|
||||||
"%.2f".format(Locale.FRANCE, it)
|
}.sum().toDouble() / denominator).let {
|
||||||
|
"%.2f".format(Locale.FRANCE, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,7 +131,9 @@ class GradeStatisticsRepository @Inject constructor(
|
|||||||
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
|
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
|
||||||
studentGrade = 0
|
studentGrade = 0
|
||||||
).apply {
|
).apply {
|
||||||
average = itemsWithAverage.mapNotNull { it.average.replace(",", ".").toDoubleOrNull() }.average().let {
|
average = itemsWithAverage.mapNotNull {
|
||||||
|
it.average.replace(",", ".").toDoubleOrNull()
|
||||||
|
}.average().let {
|
||||||
"%.2f".format(Locale.FRANCE, it)
|
"%.2f".format(Locale.FRANCE, it)
|
||||||
}
|
}
|
||||||
}).reversed()
|
}).reversed()
|
||||||
@ -118,9 +142,17 @@ class GradeStatisticsRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
fun getGradesPointsStatistics(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
subjectName: String,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = pointsMutex,
|
mutex = pointsMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(pointsCacheKey, semester)) },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester))
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
@ -30,16 +30,19 @@ class HomeworkRepository @Inject constructor(
|
|||||||
private val cacheKey = "homework"
|
private val cacheKey = "homework"
|
||||||
|
|
||||||
fun getHomework(
|
fun getHomework(
|
||||||
student: Student, semester: Semester,
|
student: Student,
|
||||||
start: LocalDate, end: LocalDate,
|
semester: Semester,
|
||||||
forceRefresh: Boolean, notify: Boolean = false
|
start: LocalDate,
|
||||||
|
end: LocalDate,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
notify: Boolean = false,
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
key = getRefreshKey(cacheKey, semester, start, end)
|
key = getRefreshKey(cacheKey, semester, start, end)
|
||||||
)
|
)
|
||||||
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
homeworkDb.loadAll(
|
homeworkDb.loadAll(
|
||||||
|
@ -23,11 +23,17 @@ class LuckyNumberRepository @Inject constructor(
|
|||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getLuckyNumber(student: Student, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
fun getLuckyNumber(
|
||||||
|
student: Student,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
notify: Boolean = false,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it == null || forceRefresh },
|
shouldFetch = { it == null || forceRefresh },
|
||||||
query = { luckyNumberDb.load(student.studentId, now()) },
|
query = { luckyNumberDb.load(student.studentId, now()) },
|
||||||
fetch = { sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student) },
|
fetch = {
|
||||||
|
sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student)
|
||||||
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
if (new != old) {
|
if (new != old) {
|
||||||
old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
||||||
@ -41,9 +47,11 @@ class LuckyNumberRepository @Inject constructor(
|
|||||||
fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) =
|
fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) =
|
||||||
luckyNumberDb.getAll(student.studentId, start, end)
|
luckyNumberDb.getAll(student.studentId, start, end)
|
||||||
|
|
||||||
suspend fun getNotNotifiedLuckyNumber(student: Student) = luckyNumberDb.load(student.studentId, now()).map {
|
suspend fun getNotNotifiedLuckyNumber(student: Student) =
|
||||||
if (it?.isNotified == false) it else null
|
luckyNumberDb.load(student.studentId, now()).map {
|
||||||
}.first()
|
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")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun getMessages(
|
fun getMessages(
|
||||||
student: Student, semester: Semester,
|
student: Student,
|
||||||
folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false
|
semester: Semester,
|
||||||
|
folder: MessageFolder,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
notify: Boolean = false,
|
||||||
): Flow<Resource<List<Message>>> = networkBoundResource(
|
): Flow<Resource<List<Message>>> = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
getRefreshKey(cacheKey, student, folder)
|
key = getRefreshKey(cacheKey, student, folder)
|
||||||
)
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
||||||
fetch = {
|
fetch = {
|
||||||
@ -77,7 +81,8 @@ class MessageRepository @Inject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private fun getMessagesWithReadByChange(
|
private fun getMessagesWithReadByChange(
|
||||||
old: List<Message>, new: List<Message>,
|
old: List<Message>,
|
||||||
|
new: List<Message>,
|
||||||
setNotified: Boolean
|
setNotified: Boolean
|
||||||
): List<Message> {
|
): List<Message> {
|
||||||
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
|
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
|
||||||
@ -96,7 +101,9 @@ class MessageRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getMessage(
|
fun getMessage(
|
||||||
student: Student, message: Message, markAsRead: Boolean = false
|
student: Student,
|
||||||
|
message: Message,
|
||||||
|
markAsRead: Boolean = false,
|
||||||
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
|
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
checkNotNull(it, { "This message no longer exist!" })
|
checkNotNull(it, { "This message no longer exist!" })
|
||||||
@ -135,8 +142,10 @@ class MessageRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendMessage(
|
suspend fun sendMessage(
|
||||||
student: Student, subject: String, content: String,
|
student: Student,
|
||||||
recipients: List<Recipient>
|
subject: String,
|
||||||
|
content: String,
|
||||||
|
recipients: List<Recipient>,
|
||||||
): SentMessage = sdk.init(student).sendMessage(
|
): SentMessage = sdk.init(student).sendMessage(
|
||||||
subject = subject,
|
subject = subject,
|
||||||
content = content,
|
content = content,
|
||||||
|
@ -28,9 +28,16 @@ class MobileDeviceRepository @Inject constructor(
|
|||||||
|
|
||||||
private val cacheKey = "devices"
|
private val cacheKey = "devices"
|
||||||
|
|
||||||
fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
fun getDevices(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student)) },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
|
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
@ -12,7 +12,6 @@ import io.github.wulkanowy.utils.init
|
|||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@ -28,9 +27,19 @@ class NoteRepository @Inject constructor(
|
|||||||
|
|
||||||
private val cacheKey = "note"
|
private val cacheKey = "note"
|
||||||
|
|
||||||
fun getNotes(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
fun getNotes(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
notify: Boolean = false,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
getRefreshKey(cacheKey, semester)
|
||||||
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { noteDb.loadAll(student.studentId) },
|
query = { noteDb.loadAll(student.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.dao.NotificationDao
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class NotificationRepository @Inject constructor(
|
||||||
|
private val notificationDao: NotificationDao,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun getNotifications(studentId: Long) = notificationDao.loadAll(studentId)
|
||||||
|
|
||||||
|
suspend fun saveNotification(notification: Notification) =
|
||||||
|
notificationDao.insertAll(listOf(notification))
|
||||||
|
}
|
@ -14,7 +14,6 @@ import io.github.wulkanowy.sdk.toLocalDate
|
|||||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
|
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
|
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
|
||||||
import io.github.wulkanowy.utils.toTimestamp
|
|
||||||
import io.github.wulkanowy.utils.toLocalDateTime
|
import io.github.wulkanowy.utils.toLocalDateTime
|
||||||
import io.github.wulkanowy.utils.toTimestamp
|
import io.github.wulkanowy.utils.toTimestamp
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
@ -108,6 +107,22 @@ class PreferencesRepository @Inject constructor(
|
|||||||
R.bool.pref_default_notification_upcoming_lessons_enable
|
R.bool.pref_default_notification_upcoming_lessons_enable
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val isUpcomingLessonsNotificationsPersistentKey =
|
||||||
|
context.getString(R.string.pref_key_notifications_upcoming_lessons_persistent)
|
||||||
|
val isUpcomingLessonsNotificationsPersistent: Boolean
|
||||||
|
get() = getBoolean(
|
||||||
|
isUpcomingLessonsNotificationsPersistentKey,
|
||||||
|
R.bool.pref_default_notification_upcoming_lessons_persistent
|
||||||
|
)
|
||||||
|
|
||||||
|
val isNotificationPiggybackEnabledKey =
|
||||||
|
context.getString(R.string.pref_key_notifications_piggyback)
|
||||||
|
val isNotificationPiggybackEnabled: Boolean
|
||||||
|
get() = getBoolean(
|
||||||
|
R.string.pref_key_notifications_piggyback,
|
||||||
|
R.bool.pref_default_notification_piggyback
|
||||||
|
)
|
||||||
|
|
||||||
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
|
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
|
||||||
val isDebugNotificationEnable: Boolean
|
val isDebugNotificationEnable: Boolean
|
||||||
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
||||||
@ -176,10 +191,8 @@ class PreferencesRepository @Inject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
var lasSyncDate: LocalDateTime
|
var lasSyncDate: LocalDateTime
|
||||||
get() = getLong(
|
get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date)
|
||||||
R.string.pref_key_last_sync_date,
|
.toLocalDateTime()
|
||||||
R.string.pref_default_last_sync_date
|
|
||||||
).toLocalDateTime()
|
|
||||||
set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply()
|
set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply()
|
||||||
|
|
||||||
var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
|
var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
|
||||||
@ -230,8 +243,10 @@ class PreferencesRepository @Inject constructor(
|
|||||||
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
|
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
|
||||||
|
|
||||||
var inAppReviewDate: LocalDate?
|
var inAppReviewDate: LocalDate?
|
||||||
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }?.toLocalDate()
|
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }
|
||||||
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp()).apply()
|
?.toLocalDate()
|
||||||
|
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp())
|
||||||
|
.apply()
|
||||||
|
|
||||||
var isAppReviewDone: Boolean
|
var isAppReviewDone: Boolean
|
||||||
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)
|
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)
|
||||||
|
@ -7,6 +7,8 @@ import io.github.wulkanowy.data.db.entities.ReportingUnit
|
|||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -15,26 +17,34 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class RecipientRepository @Inject constructor(
|
class RecipientRepository @Inject constructor(
|
||||||
private val recipientDb: RecipientDao,
|
private val recipientDb: RecipientDao,
|
||||||
private val sdk: Sdk
|
private val sdk: Sdk,
|
||||||
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
private val cacheKey = "recipient"
|
||||||
|
|
||||||
suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) {
|
suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) {
|
||||||
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
|
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
|
||||||
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||||
|
|
||||||
recipientDb.deleteAll(old uniqueSubtract new)
|
recipientDb.deleteAll(old uniqueSubtract new)
|
||||||
recipientDb.insertAll(new uniqueSubtract old)
|
recipientDb.insertAll(new uniqueSubtract old)
|
||||||
|
|
||||||
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> {
|
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> {
|
||||||
return recipientDb.loadAll(unit.studentId, unit.unitId, role).ifEmpty {
|
val cached = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||||
refreshRecipients(student, unit, role)
|
|
||||||
|
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||||
|
return if (cached.isEmpty() || isExpired) {
|
||||||
|
refreshRecipients(student, unit, role)
|
||||||
recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||||
}
|
} else cached
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> {
|
suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> {
|
||||||
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId).mapToEntities(student.studentId)
|
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId)
|
||||||
|
.mapToEntities(student.studentId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ class RecoverRepository @Inject constructor(private val sdk: Sdk) {
|
|||||||
return sdk.getPasswordResetCaptchaCode(host, symbol)
|
return sdk.getPasswordResetCaptchaCode(host, symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendRecoverRequest(url: String, symbol: String, email: String, reCaptchaResponse: String): String {
|
suspend fun sendRecoverRequest(
|
||||||
return sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
url: String, symbol: String, email: String, reCaptchaResponse: String
|
||||||
}
|
): String = sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package io.github.wulkanowy.data.repositories
|
|||||||
|
|
||||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
@ -12,7 +11,6 @@ import io.github.wulkanowy.utils.init
|
|||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@ -30,17 +28,15 @@ class SchoolAnnouncementRepository @Inject constructor(
|
|||||||
|
|
||||||
fun getSchoolAnnouncements(
|
fun getSchoolAnnouncements(
|
||||||
student: Student,
|
student: Student,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean, notify: Boolean = false
|
||||||
notify: Boolean = false
|
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
it.isEmpty() || forceRefresh
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student))
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
schoolAnnouncementDb.loadAll(
|
schoolAnnouncementDb.loadAll(student.studentId)
|
||||||
student.studentId)
|
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
@ -57,9 +53,11 @@ class SchoolAnnouncementRepository @Inject constructor(
|
|||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
||||||
return schoolAnnouncementDb.loadAll(student.studentId)
|
return schoolAnnouncementDb.loadAll(student.studentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) = schoolAnnouncementDb.updateAll(schoolAnnouncement)
|
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =
|
||||||
|
schoolAnnouncementDb.updateAll(schoolAnnouncement)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Semester
|
|||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntity
|
import io.github.wulkanowy.data.mappers.mapToEntity
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
@ -14,29 +16,41 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class SchoolRepository @Inject constructor(
|
class SchoolRepository @Inject constructor(
|
||||||
private val schoolDb: SchoolDao,
|
private val schoolDb: SchoolDao,
|
||||||
private val sdk: Sdk
|
private val sdk: Sdk,
|
||||||
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
private val cacheKey = "school_info"
|
||||||
networkBoundResource(
|
|
||||||
mutex = saveFetchResultMutex,
|
fun getSchoolInfo(
|
||||||
shouldFetch = { it == null || forceRefresh },
|
student: Student,
|
||||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
semester: Semester,
|
||||||
fetch = {
|
forceRefresh: Boolean,
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
|
) = networkBoundResource(
|
||||||
.mapToEntity(semester)
|
mutex = saveFetchResultMutex,
|
||||||
},
|
shouldFetch = {
|
||||||
saveFetchResult = { old, new ->
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
if (old != null && new != old) {
|
key = getRefreshKey(cacheKey, student)
|
||||||
with(schoolDb) {
|
)
|
||||||
deleteAll(listOf(old))
|
it == null || forceRefresh || isExpired
|
||||||
insertAll(listOf(new))
|
},
|
||||||
}
|
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||||
} else if (old == null) {
|
fetch = {
|
||||||
schoolDb.insertAll(listOf(new))
|
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))
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -19,24 +19,27 @@ class StudentInfoRepository @Inject constructor(
|
|||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getStudentInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
fun getStudentInfo(
|
||||||
networkBoundResource(
|
student: Student,
|
||||||
mutex = saveFetchResultMutex,
|
semester: Semester,
|
||||||
shouldFetch = { it == null || forceRefresh },
|
forceRefresh: Boolean,
|
||||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
) = networkBoundResource(
|
||||||
fetch = {
|
mutex = saveFetchResultMutex,
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
shouldFetch = { it == null || forceRefresh },
|
||||||
.getStudentInfo().mapToEntity(semester)
|
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||||
},
|
fetch = {
|
||||||
saveFetchResult = { old, new ->
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
if (old != null && new != old) {
|
.getStudentInfo().mapToEntity(semester)
|
||||||
with(studentInfoDao) {
|
},
|
||||||
deleteAll(listOf(old))
|
saveFetchResult = { old, new ->
|
||||||
insertAll(listOf(new))
|
if (old != null && new != old) {
|
||||||
}
|
with(studentInfoDao) {
|
||||||
} else if (old == null) {
|
deleteAll(listOf(old))
|
||||||
studentInfoDao.insertAll(listOf(new))
|
insertAll(listOf(new))
|
||||||
}
|
}
|
||||||
|
} else if (old == null) {
|
||||||
|
studentInfoDao.insertAll(listOf(new))
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Semester
|
|||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
@ -15,14 +17,24 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class SubjectRepository @Inject constructor(
|
class SubjectRepository @Inject constructor(
|
||||||
private val subjectDao: SubjectDao,
|
private val subjectDao: SubjectDao,
|
||||||
private val sdk: Sdk
|
private val sdk: Sdk,
|
||||||
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getSubjects(student: Student, semester: Semester, forceRefresh: Boolean = false) = networkBoundResource(
|
private val cacheKey = "subjects"
|
||||||
|
|
||||||
|
fun getSubjects(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
forceRefresh: Boolean = false,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
@ -31,6 +43,8 @@ class SubjectRepository @Inject constructor(
|
|||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
subjectDao.deleteAll(old uniqueSubtract new)
|
subjectDao.deleteAll(old uniqueSubtract new)
|
||||||
subjectDao.insertAll(new uniqueSubtract old)
|
subjectDao.insertAll(new uniqueSubtract old)
|
||||||
|
|
||||||
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Semester
|
|||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
@ -15,14 +17,24 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class TeacherRepository @Inject constructor(
|
class TeacherRepository @Inject constructor(
|
||||||
private val teacherDb: TeacherDao,
|
private val teacherDb: TeacherDao,
|
||||||
private val sdk: Sdk
|
private val sdk: Sdk,
|
||||||
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getTeachers(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
private val cacheKey = "teachers"
|
||||||
|
|
||||||
|
fun getTeachers(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
@ -32,6 +44,8 @@ class TeacherRepository @Inject constructor(
|
|||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
teacherDb.deleteAll(old uniqueSubtract new)
|
teacherDb.deleteAll(old uniqueSubtract new)
|
||||||
teacherDb.insertAll(new uniqueSubtract old)
|
teacherDb.insertAll(new uniqueSubtract old)
|
||||||
|
|
||||||
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -41,18 +41,22 @@ class TimetableRepository @Inject constructor(
|
|||||||
private val cacheKey = "timetable"
|
private val cacheKey = "timetable"
|
||||||
|
|
||||||
fun getTimetable(
|
fun getTimetable(
|
||||||
student: Student, semester: Semester, start: LocalDate, end: LocalDate,
|
student: Student,
|
||||||
forceRefresh: Boolean, refreshAdditional: Boolean = false
|
semester: Semester,
|
||||||
|
start: LocalDate,
|
||||||
|
end: LocalDate,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
refreshAdditional: Boolean = false,
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { (timetable, additional, headers) ->
|
shouldFetch = { (timetable, additional, headers) ->
|
||||||
val refreshKey = getRefreshKey(cacheKey, semester, start, end)
|
val refreshKey = getRefreshKey(cacheKey, semester, start, end)
|
||||||
val isShouldRefresh = refreshHelper.isShouldBeRefreshed(refreshKey)
|
val isExpired = refreshHelper.shouldBeRefreshed(refreshKey)
|
||||||
val isRefreshAdditional = additional.isEmpty() && refreshAdditional
|
val isRefreshAdditional = additional.isEmpty() && refreshAdditional
|
||||||
|
|
||||||
val isNoData = timetable.isEmpty() || isRefreshAdditional || headers.isEmpty()
|
val isNoData = timetable.isEmpty() || isRefreshAdditional || headers.isEmpty()
|
||||||
|
|
||||||
isNoData || forceRefresh || isShouldRefresh
|
isNoData || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
||||||
fetch = {
|
fetch = {
|
||||||
@ -79,8 +83,10 @@ class TimetableRepository @Inject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private fun getFullTimetableFromDatabase(
|
private fun getFullTimetableFromDatabase(
|
||||||
student: Student, semester: Semester,
|
student: Student,
|
||||||
start: LocalDate, end: LocalDate
|
semester: Semester,
|
||||||
|
start: LocalDate,
|
||||||
|
end: LocalDate,
|
||||||
): Flow<TimetableFull> {
|
): Flow<TimetableFull> {
|
||||||
val timetableFlow = timetableDb.loadAll(
|
val timetableFlow = timetableDb.loadAll(
|
||||||
diaryId = semester.diaryId,
|
diaryId = semester.diaryId,
|
||||||
@ -113,20 +119,11 @@ class TimetableRepository @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun refreshTimetable(
|
private suspend fun refreshTimetable(
|
||||||
student: Student,
|
student: Student,
|
||||||
lessonsOld: List<Timetable>, lessonsNew: List<Timetable>
|
lessonsOld: List<Timetable>,
|
||||||
|
lessonsNew: List<Timetable>,
|
||||||
) {
|
) {
|
||||||
val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew
|
val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew
|
||||||
val lessonsToAdd = (lessonsNew uniqueSubtract lessonsOld).map { new ->
|
val lessonsToAdd = lessonsNew uniqueSubtract lessonsOld
|
||||||
val matchingOld = lessonsOld.singleOrNull { new.start == it.start }
|
|
||||||
if (matchingOld != null) {
|
|
||||||
val useOldTeacher = new.teacher.isEmpty() && !new.changes && !matchingOld.changes
|
|
||||||
new.copy(
|
|
||||||
room = if (new.room.isEmpty()) matchingOld.room else new.room,
|
|
||||||
teacher = if (useOldTeacher) matchingOld.teacher
|
|
||||||
else new.teacher
|
|
||||||
)
|
|
||||||
} else new
|
|
||||||
}
|
|
||||||
|
|
||||||
timetableDb.deleteAll(lessonsToRemove)
|
timetableDb.deleteAll(lessonsToRemove)
|
||||||
timetableDb.insertAll(lessonsToAdd)
|
timetableDb.insertAll(lessonsToAdd)
|
||||||
|
@ -11,6 +11,7 @@ import androidx.core.app.NotificationManagerCompat
|
|||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.Status
|
import io.github.wulkanowy.data.Status
|
||||||
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.services.HiltBroadcastReceiver
|
import io.github.wulkanowy.services.HiltBroadcastReceiver
|
||||||
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
|
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
|
||||||
@ -32,6 +33,9 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var studentRepository: StudentRepository
|
lateinit var studentRepository: StudentRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var preferencesRepository: PreferencesRepository
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val NOTIFICATION_TYPE_CURRENT = 1
|
const val NOTIFICATION_TYPE_CURRENT = 1
|
||||||
const val NOTIFICATION_TYPE_UPCOMING = 2
|
const val NOTIFICATION_TYPE_UPCOMING = 2
|
||||||
@ -68,6 +72,7 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
|||||||
private fun prepareNotification(context: Context, intent: Intent) {
|
private fun prepareNotification(context: Context, intent: Intent) {
|
||||||
val type = intent.getIntExtra(LESSON_TYPE, 0)
|
val type = intent.getIntExtra(LESSON_TYPE, 0)
|
||||||
val notificationId = intent.getIntExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
val notificationId = intent.getIntExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||||
|
val isPersistent = preferencesRepository.isUpcomingLessonsNotificationsPersistent
|
||||||
|
|
||||||
if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) {
|
if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) {
|
||||||
return NotificationManagerCompat.from(context).cancel(notificationId)
|
return NotificationManagerCompat.from(context).cancel(notificationId)
|
||||||
@ -87,33 +92,57 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
|||||||
|
|
||||||
Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId")
|
Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId")
|
||||||
|
|
||||||
showNotification(context, notificationId, studentName,
|
showNotification(context, notificationId, isPersistent, studentName,
|
||||||
if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start,
|
if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start,
|
||||||
context.getString(if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next, "($room) $subject".removePrefix("()")),
|
context.getString(
|
||||||
nextSubject?.let { context.getString(R.string.timetable_later, "($nextRoom) $nextSubject".removePrefix("()")) }
|
if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next,
|
||||||
|
"($room) $subject".removePrefix("()")
|
||||||
|
),
|
||||||
|
nextSubject?.let {
|
||||||
|
context.getString(
|
||||||
|
R.string.timetable_later,
|
||||||
|
"($nextRoom) $nextSubject".removePrefix("()")
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showNotification(context: Context, notificationId: Int, studentName: String?, countDown: Long, timeout: Long, title: String, next: String?) {
|
private fun showNotification(
|
||||||
NotificationManagerCompat.from(context).notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID)
|
context: Context,
|
||||||
.setContentTitle(title)
|
notificationId: Int,
|
||||||
.setContentText(next)
|
isPersistent: Boolean,
|
||||||
.setAutoCancel(false)
|
studentName: String?,
|
||||||
.setOngoing(true)
|
countDown: Long,
|
||||||
.setWhen(countDown)
|
timeout: Long,
|
||||||
.apply {
|
title: String,
|
||||||
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
next: String?
|
||||||
}
|
) {
|
||||||
.setTimeoutAfter(timeout)
|
NotificationManagerCompat.from(context)
|
||||||
.setSmallIcon(R.drawable.ic_stat_timetable)
|
.notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
.setContentTitle(title)
|
||||||
.setStyle(NotificationCompat.InboxStyle().also {
|
.setContentText(next)
|
||||||
it.setSummaryText(studentName)
|
.setAutoCancel(false)
|
||||||
it.addLine(next)
|
.setWhen(countDown)
|
||||||
})
|
.setOngoing(isPersistent)
|
||||||
.setContentIntent(PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id,
|
.apply {
|
||||||
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT))
|
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
||||||
.build()
|
}
|
||||||
)
|
.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 io.github.wulkanowy.utils.toTimestamp
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.LocalDateTime.now
|
import java.time.LocalDateTime.now
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -57,10 +58,13 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
|||||||
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
|
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
|
||||||
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
|
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
|
||||||
cancelScheduledTo(
|
cancelScheduledTo(
|
||||||
upcomingTime..lesson.start,
|
range = upcomingTime..lesson.start,
|
||||||
getRequestCode(upcomingTime, studentId)
|
requestCode = getRequestCode(upcomingTime, studentId)
|
||||||
|
)
|
||||||
|
cancelScheduledTo(
|
||||||
|
range = lesson.start..lesson.end,
|
||||||
|
requestCode = getRequestCode(lesson.start, studentId)
|
||||||
)
|
)
|
||||||
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId))
|
|
||||||
|
|
||||||
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
|
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
|
||||||
}
|
}
|
||||||
@ -82,6 +86,11 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
|||||||
return cancelScheduled(lessons, student)
|
return cancelScheduled(lessons, student)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lessons.firstOrNull()?.date?.isAfter(LocalDate.now().plusDays(2)) == true) {
|
||||||
|
Timber.d("Timetable notification scheduling skipped - lessons are too far")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
withContext(dispatchersProvider.backgroundThread) {
|
withContext(dispatchersProvider.backgroundThread) {
|
||||||
lessons.groupBy { it.date }
|
lessons.groupBy { it.date }
|
||||||
.map { it.value.sortedBy { lesson -> lesson.start } }
|
.map { it.value.sortedBy { lesson -> lesson.start } }
|
||||||
@ -96,26 +105,26 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
|||||||
|
|
||||||
if (lesson.start > now()) {
|
if (lesson.start > now()) {
|
||||||
scheduleBroadcast(
|
scheduleBroadcast(
|
||||||
intent,
|
intent = intent,
|
||||||
student.studentId,
|
studentId = student.studentId,
|
||||||
NOTIFICATION_TYPE_UPCOMING,
|
notificationType = NOTIFICATION_TYPE_UPCOMING,
|
||||||
getUpcomingLessonTime(index, active, lesson)
|
time = getUpcomingLessonTime(index, active, lesson)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lesson.end > now()) {
|
if (lesson.end > now()) {
|
||||||
scheduleBroadcast(
|
scheduleBroadcast(
|
||||||
intent,
|
intent = intent,
|
||||||
student.studentId,
|
studentId = student.studentId,
|
||||||
NOTIFICATION_TYPE_CURRENT,
|
notificationType = NOTIFICATION_TYPE_CURRENT,
|
||||||
lesson.start
|
time = lesson.start
|
||||||
)
|
)
|
||||||
if (active.lastIndex == index) {
|
if (active.lastIndex == index) {
|
||||||
scheduleBroadcast(
|
scheduleBroadcast(
|
||||||
intent,
|
intent = intent,
|
||||||
student.studentId,
|
studentId = student.studentId,
|
||||||
NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
notificationType = NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
||||||
lesson.end
|
time = lesson.end
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,17 +152,21 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
|||||||
notificationType: Int,
|
notificationType: Int,
|
||||||
time: LocalDateTime
|
time: LocalDateTime
|
||||||
) {
|
) {
|
||||||
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
try {
|
||||||
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
||||||
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||||
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||||
it.putExtra(LESSON_TYPE, notificationType)
|
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||||
}, FLAG_UPDATE_CURRENT)
|
it.putExtra(LESSON_TYPE, notificationType)
|
||||||
)
|
}, FLAG_UPDATE_CURRENT)
|
||||||
Timber.d(
|
)
|
||||||
"TimetableNotification scheduled: type: $notificationType, subject: ${
|
Timber.d(
|
||||||
intent.getStringExtra(LESSON_TITLE)
|
"TimetableNotification scheduled: type: $notificationType, subject: ${
|
||||||
}, start: $time, student: $studentId"
|
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) {
|
fun startPeriodicSyncWorker(restart: Boolean = false) {
|
||||||
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
|
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
|
||||||
workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
val serviceInterval = preferencesRepository.servicesInterval
|
||||||
PeriodicWorkRequestBuilder<SyncWorker>(preferencesRepository.servicesInterval, MINUTES)
|
|
||||||
|
workManager.enqueueUniquePeriodicWork(
|
||||||
|
SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
||||||
|
PeriodicWorkRequestBuilder<SyncWorker>(serviceInterval, MINUTES)
|
||||||
.setInitialDelay(10, MINUTES)
|
.setInitialDelay(10, MINUTES)
|
||||||
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
|
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
|
||||||
.setConstraints(Constraints.Builder()
|
.setConstraints(
|
||||||
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
Constraints.Builder()
|
||||||
.build())
|
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +83,11 @@ class SyncManager @Inject constructor(
|
|||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
workManager.enqueueUniqueWork("${SyncWorker::class.java.simpleName}_one_time", ExistingWorkPolicy.REPLACE, work)
|
workManager.enqueueUniqueWork(
|
||||||
|
"${SyncWorker::class.java.simpleName}_one_time",
|
||||||
|
ExistingWorkPolicy.REPLACE,
|
||||||
|
work
|
||||||
|
)
|
||||||
|
|
||||||
return workManager.getWorkInfoByIdLiveData(work.id).asFlow()
|
return workManager.getWorkInfoByIdLiveData(work.id).asFlow()
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,145 @@
|
|||||||
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
|
import io.github.wulkanowy.data.pojos.NotificationData
|
||||||
|
import io.github.wulkanowy.data.pojos.OneNotificationData
|
||||||
|
import io.github.wulkanowy.data.repositories.NotificationRepository
|
||||||
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
|
import io.github.wulkanowy.utils.getCompatBitmap
|
||||||
|
import io.github.wulkanowy.utils.getCompatColor
|
||||||
|
import io.github.wulkanowy.utils.nickOrName
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
class AppNotificationManager @Inject constructor(
|
||||||
|
private val notificationManager: NotificationManagerCompat,
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
private val appInfo: AppInfo,
|
||||||
|
private val notificationRepository: NotificationRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun sendNotification(notificationData: NotificationData, student: Student) =
|
||||||
|
when (notificationData) {
|
||||||
|
is OneNotificationData -> sendOneNotification(notificationData, student)
|
||||||
|
is MultipleNotificationsData -> sendMultipleNotifications(notificationData, student)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun sendOneNotification(
|
||||||
|
notificationData: OneNotificationData,
|
||||||
|
student: Student
|
||||||
|
) {
|
||||||
|
val content = context.getString(
|
||||||
|
notificationData.contentStringRes,
|
||||||
|
*notificationData.contentValues.toTypedArray()
|
||||||
|
)
|
||||||
|
|
||||||
|
val title = context.getString(notificationData.titleStringRes)
|
||||||
|
|
||||||
|
val notification = getDefaultNotificationBuilder(notificationData)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(content)
|
||||||
|
.setStyle(
|
||||||
|
NotificationCompat.BigTextStyle()
|
||||||
|
.setSummaryText(student.nickOrName)
|
||||||
|
.bigText(content)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), notification)
|
||||||
|
|
||||||
|
saveNotification(title, content, notificationData, student)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun sendMultipleNotifications(
|
||||||
|
notificationData: MultipleNotificationsData,
|
||||||
|
student: Student
|
||||||
|
) {
|
||||||
|
val groupType = notificationData.type.group ?: return
|
||||||
|
val group = "${groupType}_${student.id}"
|
||||||
|
val groupId = student.id * 100 + notificationData.type.ordinal
|
||||||
|
|
||||||
|
notificationData.lines.forEach { item ->
|
||||||
|
val title = context.resources.getQuantityString(notificationData.titleStringRes, 1)
|
||||||
|
|
||||||
|
val notification = getDefaultNotificationBuilder(notificationData)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(item)
|
||||||
|
.setStyle(
|
||||||
|
NotificationCompat.BigTextStyle()
|
||||||
|
.setSummaryText(student.nickOrName)
|
||||||
|
.bigText(item)
|
||||||
|
)
|
||||||
|
.setGroup(group)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), notification)
|
||||||
|
|
||||||
|
saveNotification(title, item, notificationData, student)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
|
||||||
|
|
||||||
|
val summaryNotification = getDefaultNotificationBuilder(notificationData)
|
||||||
|
.setSmallIcon(notificationData.icon)
|
||||||
|
.setGroup(group)
|
||||||
|
.setStyle(NotificationCompat.InboxStyle().setSummaryText(student.nickOrName))
|
||||||
|
.setGroupSummary(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
notificationManager.notify(groupId.toInt(), summaryNotification)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
private fun getDefaultNotificationBuilder(notificationData: NotificationData): NotificationCompat.Builder {
|
||||||
|
val pendingIntentsFlags = if (appInfo.systemVersion >= Build.VERSION_CODES.M) {
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
} else {
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
}
|
||||||
|
|
||||||
|
return NotificationCompat.Builder(context, notificationData.type.channel)
|
||||||
|
.setLargeIcon(context.getCompatBitmap(notificationData.icon, R.color.colorPrimary))
|
||||||
|
.setSmallIcon(R.drawable.ic_stat_all)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||||
|
.setContentIntent(
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
context,
|
||||||
|
notificationData.startMenu.id,
|
||||||
|
MainActivity.getStartIntent(context, notificationData.startMenu, true),
|
||||||
|
pendingIntentsFlags
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun saveNotification(
|
||||||
|
title: String,
|
||||||
|
content: String,
|
||||||
|
notificationData: NotificationData,
|
||||||
|
student: Student
|
||||||
|
) {
|
||||||
|
val notificationEntity = Notification(
|
||||||
|
studentId = student.id,
|
||||||
|
title = title,
|
||||||
|
content = content,
|
||||||
|
type = notificationData.type,
|
||||||
|
date = LocalDateTime.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
notificationRepository.saveNotification(notificationEntity)
|
||||||
|
}
|
||||||
|
}
|
@ -1,102 +0,0 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
|
||||||
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.annotation.PluralsRes
|
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
|
||||||
import io.github.wulkanowy.data.pojos.Notification
|
|
||||||
import io.github.wulkanowy.data.pojos.OneNotification
|
|
||||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
|
||||||
import io.github.wulkanowy.utils.getCompatBitmap
|
|
||||||
import io.github.wulkanowy.utils.getCompatColor
|
|
||||||
import io.github.wulkanowy.utils.nickOrName
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
abstract class BaseNotification(
|
|
||||||
private val context: Context,
|
|
||||||
private val notificationManager: NotificationManagerCompat,
|
|
||||||
) {
|
|
||||||
|
|
||||||
protected fun sendNotification(notification: Notification, student: Student) =
|
|
||||||
when (notification) {
|
|
||||||
is OneNotification -> sendOneNotification(notification, student)
|
|
||||||
is MultipleNotifications -> sendMultipleNotifications(notification, student)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendOneNotification(notification: OneNotification, student: Student?) {
|
|
||||||
notificationManager.notify(
|
|
||||||
Random.nextInt(Int.MAX_VALUE),
|
|
||||||
getNotificationBuilder(notification).apply {
|
|
||||||
val content = context.getString(
|
|
||||||
notification.contentStringRes,
|
|
||||||
*notification.contentValues.toTypedArray()
|
|
||||||
)
|
|
||||||
setContentTitle(context.getString(notification.titleStringRes))
|
|
||||||
setContentText(content)
|
|
||||||
setStyle(
|
|
||||||
NotificationCompat.BigTextStyle()
|
|
||||||
.setSummaryText(student?.nickOrName)
|
|
||||||
.bigText(content)
|
|
||||||
)
|
|
||||||
}.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendMultipleNotifications(notification: MultipleNotifications, student: Student) {
|
|
||||||
val group = notification.type.group + student.id
|
|
||||||
val groupId = student.id * 100 + notification.type.ordinal
|
|
||||||
|
|
||||||
notification.lines.forEach { item ->
|
|
||||||
notificationManager.notify(
|
|
||||||
Random.nextInt(Int.MAX_VALUE),
|
|
||||||
getNotificationBuilder(notification).apply {
|
|
||||||
setContentTitle(getQuantityString(notification.titleStringRes, 1))
|
|
||||||
setContentText(item)
|
|
||||||
setStyle(
|
|
||||||
NotificationCompat.BigTextStyle()
|
|
||||||
.setSummaryText(student.nickOrName)
|
|
||||||
.bigText(item)
|
|
||||||
)
|
|
||||||
setGroup(group)
|
|
||||||
}.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
|
|
||||||
|
|
||||||
notificationManager.notify(
|
|
||||||
groupId.toInt(),
|
|
||||||
getNotificationBuilder(notification).apply {
|
|
||||||
setSmallIcon(notification.icon)
|
|
||||||
setGroup(group)
|
|
||||||
setStyle(NotificationCompat.InboxStyle().setSummaryText(student.nickOrName))
|
|
||||||
setGroupSummary(true)
|
|
||||||
}.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getNotificationBuilder(notification: Notification) = NotificationCompat
|
|
||||||
.Builder(context, notification.type.channel)
|
|
||||||
.setLargeIcon(context.getCompatBitmap(notification.icon, R.color.colorPrimary))
|
|
||||||
.setSmallIcon(R.drawable.ic_stat_all)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
||||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
|
||||||
.setContentIntent(
|
|
||||||
PendingIntent.getActivity(
|
|
||||||
context, notification.startMenu.id,
|
|
||||||
MainActivity.getStartIntent(context, notification.startMenu, true),
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getQuantityString(@PluralsRes id: Int, value: Int): String {
|
|
||||||
return context.resources.getQuantityString(id, value, value)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +1,25 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Conference
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewConferenceNotification @Inject constructor(
|
class NewConferenceNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notify(items: List<Conference>, student: Student) {
|
suspend fun notify(items: List<Conference>, student: Student) {
|
||||||
val today = LocalDateTime.now()
|
val today = LocalDateTime.now()
|
||||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||||
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
|
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
|
||||||
}.ifEmpty { return }
|
}.ifEmpty { return }
|
||||||
|
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_CONFERENCE,
|
type = NotificationType.NEW_CONFERENCE,
|
||||||
icon = R.drawable.ic_more_conferences,
|
icon = R.drawable.ic_more_conferences,
|
||||||
titleStringRes = R.plurals.conference_notify_new_item_title,
|
titleStringRes = R.plurals.conference_notify_new_item_title,
|
||||||
@ -33,6 +29,6 @@ class NewConferenceNotification @Inject constructor(
|
|||||||
lines = lines
|
lines = lines
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,25 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Exam
|
import io.github.wulkanowy.data.db.entities.Exam
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewExamNotification @Inject constructor(
|
class NewExamNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notify(items: List<Exam>, student: Student) {
|
suspend fun notify(items: List<Exam>, student: Student) {
|
||||||
val today = LocalDate.now()
|
val today = LocalDate.now()
|
||||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
|
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
|
||||||
}.ifEmpty { return }
|
}.ifEmpty { return }
|
||||||
|
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_EXAM,
|
type = NotificationType.NEW_EXAM,
|
||||||
icon = R.drawable.ic_main_exam,
|
icon = R.drawable.ic_main_exam,
|
||||||
titleStringRes = R.plurals.exam_notify_new_item_title,
|
titleStringRes = R.plurals.exam_notify_new_item_title,
|
||||||
@ -33,6 +29,6 @@ class NewExamNotification @Inject constructor(
|
|||||||
lines = lines
|
lines = lines
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,19 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewGradeNotification @Inject constructor(
|
class NewGradeNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notifyDetails(items: List<Grade>, student: Student) {
|
suspend fun notifyDetails(items: List<Grade>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_GRADE_DETAILS,
|
type = NotificationType.NEW_GRADE_DETAILS,
|
||||||
icon = R.drawable.ic_stat_grade,
|
icon = R.drawable.ic_stat_grade,
|
||||||
titleStringRes = R.plurals.grade_new_items,
|
titleStringRes = R.plurals.grade_new_items,
|
||||||
@ -29,11 +25,11 @@ class NewGradeNotification @Inject constructor(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notifyPredicted(items: List<GradeSummary>, student: Student) {
|
suspend fun notifyPredicted(items: List<GradeSummary>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_GRADE_PREDICTED,
|
type = NotificationType.NEW_GRADE_PREDICTED,
|
||||||
icon = R.drawable.ic_stat_grade,
|
icon = R.drawable.ic_stat_grade,
|
||||||
titleStringRes = R.plurals.grade_new_items_predicted,
|
titleStringRes = R.plurals.grade_new_items_predicted,
|
||||||
@ -45,11 +41,11 @@ class NewGradeNotification @Inject constructor(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notifyFinal(items: List<GradeSummary>, student: Student) {
|
suspend fun notifyFinal(items: List<GradeSummary>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_GRADE_FINAL,
|
type = NotificationType.NEW_GRADE_FINAL,
|
||||||
icon = R.drawable.ic_stat_grade,
|
icon = R.drawable.ic_stat_grade,
|
||||||
titleStringRes = R.plurals.grade_new_items_final,
|
titleStringRes = R.plurals.grade_new_items_final,
|
||||||
@ -61,6 +57,6 @@ class NewGradeNotification @Inject constructor(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,25 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Homework
|
import io.github.wulkanowy.data.db.entities.Homework
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewHomeworkNotification @Inject constructor(
|
class NewHomeworkNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notify(items: List<Homework>, student: Student) {
|
suspend fun notify(items: List<Homework>, student: Student) {
|
||||||
val today = LocalDate.now()
|
val today = LocalDate.now()
|
||||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
|
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
|
||||||
}.ifEmpty { return }
|
}.ifEmpty { return }
|
||||||
|
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_HOMEWORK,
|
type = NotificationType.NEW_HOMEWORK,
|
||||||
icon = R.drawable.ic_more_homework,
|
icon = R.drawable.ic_more_homework,
|
||||||
titleStringRes = R.plurals.homework_notify_new_item_title,
|
titleStringRes = R.plurals.homework_notify_new_item_title,
|
||||||
@ -33,6 +29,6 @@ class NewHomeworkNotification @Inject constructor(
|
|||||||
lines = lines
|
lines = lines
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,26 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.OneNotification
|
import io.github.wulkanowy.data.pojos.OneNotificationData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewLuckyNumberNotification @Inject constructor(
|
class NewLuckyNumberNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notify(item: LuckyNumber, student: Student) {
|
suspend fun notify(item: LuckyNumber, student: Student) {
|
||||||
val notification = OneNotification(
|
val notification = OneNotificationData(
|
||||||
type = NotificationType.NEW_LUCKY_NUMBER,
|
type = NotificationType.NEW_LUCKY_NUMBER,
|
||||||
icon = R.drawable.ic_stat_luckynumber,
|
icon = R.drawable.ic_stat_luckynumber,
|
||||||
titleStringRes = R.string.lucky_number_notify_new_item_title,
|
titleStringRes = R.string.lucky_number_notify_new_item_title,
|
||||||
contentStringRes = R.string.lucky_number_notify_new_item,
|
contentStringRes = R.string.lucky_number_notify_new_item,
|
||||||
startMenu = MainView.Section.LUCKY_NUMBER,
|
startMenu = MainView.Section.LUCKY_NUMBER,
|
||||||
contentValues = listOf(item.luckyNumber.toString())
|
contentValues = listOf(item.luckyNumber.toString())
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,18 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewMessageNotification @Inject constructor(
|
class NewMessageNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notify(items: List<Message>, student: Student) {
|
suspend fun notify(items: List<Message>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_MESSAGE,
|
type = NotificationType.NEW_MESSAGE,
|
||||||
icon = R.drawable.ic_stat_message,
|
icon = R.drawable.ic_stat_message,
|
||||||
titleStringRes = R.plurals.message_new_items,
|
titleStringRes = R.plurals.message_new_items,
|
||||||
@ -28,6 +24,6 @@ class NewMessageNotification @Inject constructor(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,19 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Note
|
import io.github.wulkanowy.data.db.entities.Note
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
|
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewNoteNotification @Inject constructor(
|
class NewNoteNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notify(items: List<Note>, student: Student) {
|
suspend fun notify(items: List<Note>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_NOTE,
|
type = NotificationType.NEW_NOTE,
|
||||||
icon = R.drawable.ic_stat_note,
|
icon = R.drawable.ic_stat_note,
|
||||||
titleStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
titleStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
||||||
@ -41,6 +37,6 @@ class NewNoteNotification @Inject constructor(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendNotification(notification, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,29 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewSchoolAnnouncementNotification @Inject constructor(
|
class NewSchoolAnnouncementNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager
|
||||||
notificationManager: NotificationManagerCompat,
|
) {
|
||||||
) : BaseNotification(context, notificationManager) {
|
|
||||||
|
|
||||||
fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
suspend fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notification = MultipleNotificationsData(
|
||||||
type = NotificationType.NEW_ANNOUNCEMENT,
|
type = NotificationType.NEW_ANNOUNCEMENT,
|
||||||
icon = R.drawable.ic_all_about,
|
icon = R.drawable.ic_all_about,
|
||||||
titleStringRes = R.plurals.school_announcement_notify_new_item_title,
|
titleStringRes = R.plurals.school_announcement_notify_new_item_title,
|
||||||
contentStringRes = R.plurals.school_announcement_notify_new_items,
|
contentStringRes = R.plurals.school_announcement_notify_new_items,
|
||||||
summaryStringRes = R.plurals.school_announcement_number_item,
|
summaryStringRes = R.plurals.school_announcement_number_item,
|
||||||
startMenu = MainView.Section.SCHOOL_ANNOUNCEMENT,
|
startMenu = MainView.Section.SCHOOL_ANNOUNCEMENT,
|
||||||
lines = items.map {
|
lines = items.map {
|
||||||
"${it.subject}: ${it.content}"
|
"${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.NewMessagesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
|
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
|
||||||
|
import io.github.wulkanowy.services.sync.channels.PushChannel
|
||||||
|
|
||||||
enum class NotificationType(val group: String, val channel: String) {
|
enum class NotificationType(val group: String?, val channel: String) {
|
||||||
NEW_CONFERENCE("new_conferences_group", NewConferencesChannel.CHANNEL_ID),
|
NEW_CONFERENCE("new_conferences_group", NewConferencesChannel.CHANNEL_ID),
|
||||||
NEW_EXAM("new_exam_group", NewExamChannel.CHANNEL_ID),
|
NEW_EXAM("new_exam_group", NewExamChannel.CHANNEL_ID),
|
||||||
NEW_GRADE_DETAILS("new_grade_details_group", NewGradesChannel.CHANNEL_ID),
|
NEW_GRADE_DETAILS("new_grade_details_group", NewGradesChannel.CHANNEL_ID),
|
||||||
@ -20,4 +21,5 @@ enum class NotificationType(val group: String, val channel: String) {
|
|||||||
NEW_MESSAGE("new_message_group", NewMessagesChannel.CHANNEL_ID),
|
NEW_MESSAGE("new_message_group", NewMessagesChannel.CHANNEL_ID),
|
||||||
NEW_NOTE("new_notes_group", NewNotesChannel.CHANNEL_ID),
|
NEW_NOTE("new_notes_group", NewNotesChannel.CHANNEL_ID),
|
||||||
NEW_ANNOUNCEMENT("new_school_announcements_group", NewSchoolAnnouncementsChannel.CHANNEL_ID),
|
NEW_ANNOUNCEMENT("new_school_announcements_group", NewSchoolAnnouncementsChannel.CHANNEL_ID),
|
||||||
|
PUSH(null, PushChannel.CHANNEL_ID)
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,12 @@ import io.github.wulkanowy.utils.waitForResult
|
|||||||
import java.time.LocalDate.now
|
import java.time.LocalDate.now
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AttendanceWork @Inject constructor(private val attendanceRepository: AttendanceRepository) : Work {
|
class AttendanceWork @Inject constructor(
|
||||||
|
private val attendanceRepository: AttendanceRepository
|
||||||
|
) : Work {
|
||||||
|
|
||||||
override suspend fun doWork(student: Student, semester: Semester) {
|
override suspend fun doWork(student: Student, semester: Semester) {
|
||||||
attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true).waitForResult()
|
attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true)
|
||||||
|
.waitForResult()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,10 +121,14 @@ class AccountDetailsFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popView() {
|
override fun popViewToMain() {
|
||||||
(requireActivity() as MainActivity).popView(2)
|
(requireActivity() as MainActivity).popView(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun popViewToAccounts() {
|
||||||
|
(requireActivity() as MainActivity).popView(1)
|
||||||
|
}
|
||||||
|
|
||||||
override fun recreateMainView() {
|
override fun recreateMainView() {
|
||||||
requireActivity().recreate()
|
requireActivity().recreate()
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ class AccountDetailsPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.afterLoading {
|
}.afterLoading {
|
||||||
view?.popView()
|
view?.popViewToMain()
|
||||||
}.launch("switch")
|
}.launch("switch")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,11 +152,14 @@ class AccountDetailsPresenter @Inject constructor(
|
|||||||
syncManager.stopSyncWorker()
|
syncManager.stopSyncWorker()
|
||||||
openClearLoginView()
|
openClearLoginView()
|
||||||
}
|
}
|
||||||
studentWithSemesters!!.student.isCurrent -> {
|
studentWithSemesters?.student?.isCurrent == true -> {
|
||||||
Timber.i("Logout result: Logout student and switch to another")
|
Timber.i("Logout result: Logout student and switch to another")
|
||||||
recreateMainView()
|
recreateMainView()
|
||||||
}
|
}
|
||||||
else -> Timber.i("Logout result: Logout student")
|
else -> {
|
||||||
|
Timber.i("Logout result: Logout student")
|
||||||
|
recreateMainView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Status.ERROR -> {
|
Status.ERROR -> {
|
||||||
@ -165,7 +168,11 @@ class AccountDetailsPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.afterLoading {
|
}.afterLoading {
|
||||||
view?.popView()
|
if (studentWithSemesters?.student?.isCurrent == true) {
|
||||||
|
view?.popViewToMain()
|
||||||
|
} else {
|
||||||
|
view?.popViewToAccounts()
|
||||||
|
}
|
||||||
}.launch("logout")
|
}.launch("logout")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,9 @@ interface AccountDetailsView : BaseView {
|
|||||||
|
|
||||||
fun showLogoutConfirmDialog()
|
fun showLogoutConfirmDialog()
|
||||||
|
|
||||||
fun popView()
|
fun popViewToMain()
|
||||||
|
|
||||||
|
fun popViewToAccounts()
|
||||||
|
|
||||||
fun recreateMainView()
|
fun recreateMainView()
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ class ConferenceAdapter @Inject constructor() :
|
|||||||
|
|
||||||
var items = emptyList<Conference>()
|
var items = emptyList<Conference>()
|
||||||
|
|
||||||
|
var onItemClickListener: (Conference) -> Unit = {}
|
||||||
|
|
||||||
override fun getItemCount() = items.size
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
||||||
@ -28,7 +30,10 @@ class ConferenceAdapter @Inject constructor() :
|
|||||||
conferenceItemTitle.text = item.title
|
conferenceItemTitle.text = item.title
|
||||||
conferenceItemSubject.text = item.subject
|
conferenceItemSubject.text = item.subject
|
||||||
conferenceItemContent.text = item.agenda
|
conferenceItemContent.text = item.agenda
|
||||||
conferenceItemContent.visibility = if (item.agenda.isBlank()) View.GONE else View.VISIBLE
|
conferenceItemContent.visibility =
|
||||||
|
if (item.agenda.isBlank()) View.GONE else View.VISIBLE
|
||||||
|
|
||||||
|
root.setOnClickListener { onItemClickListener(item) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.conference
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
|
import io.github.wulkanowy.databinding.DialogConferenceBinding
|
||||||
|
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||||
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
|
|
||||||
|
class ConferenceDialog : DialogFragment() {
|
||||||
|
|
||||||
|
private var binding: DialogConferenceBinding by lifecycleAwareVariable()
|
||||||
|
|
||||||
|
private lateinit var conference: Conference
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val ARGUMENT_KEY = "item"
|
||||||
|
|
||||||
|
fun newInstance(conference: Conference) = ConferenceDialog().apply {
|
||||||
|
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, conference) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setStyle(STYLE_NO_TITLE, 0)
|
||||||
|
arguments?.let {
|
||||||
|
conference = it.getSerializable(ARGUMENT_KEY) as Conference
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
) = DialogConferenceBinding.inflate(inflater).also { binding = it }.root
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
|
conferenceDialogClose.setOnClickListener { dismiss() }
|
||||||
|
|
||||||
|
conferenceDialogSubjectValue.text = conference.subject
|
||||||
|
conferenceDialogDateValue.text = conference.date.toFormattedString("dd.MM.yyyy HH:mm")
|
||||||
|
conferenceDialogHeaderValue.text = conference.title
|
||||||
|
conferenceDialogAgendaValue.text = conference.agenda
|
||||||
|
conferenceDialogPresentValue.text = conference.presentOnConference
|
||||||
|
conferenceDialogPresentValue.isVisible = conference.presentOnConference.isNotBlank()
|
||||||
|
conferenceDialogPresentTitle.isVisible = conference.presentOnConference.isNotBlank()
|
||||||
|
conferenceDialogAgendaValue.isVisible = conference.agenda.isNotBlank()
|
||||||
|
conferenceDialogAgendaTitle.isVisible = conference.agenda.isNotBlank()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import io.github.wulkanowy.R
|
|||||||
import io.github.wulkanowy.data.db.entities.Conference
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
import io.github.wulkanowy.databinding.FragmentConferenceBinding
|
import io.github.wulkanowy.databinding.FragmentConferenceBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
@ -41,6 +42,8 @@ class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.frag
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
|
conferencesAdapter.onItemClickListener = presenter::onItemSelected
|
||||||
|
|
||||||
with(binding.conferenceRecycler) {
|
with(binding.conferenceRecycler) {
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = conferencesAdapter
|
adapter = conferencesAdapter
|
||||||
@ -50,7 +53,11 @@ class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.frag
|
|||||||
with(binding) {
|
with(binding) {
|
||||||
conferenceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
conferenceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||||
conferenceSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
conferenceSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||||
conferenceSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
conferenceSwipe.setProgressBackgroundColorSchemeColor(
|
||||||
|
requireContext().getThemeAttrColor(
|
||||||
|
R.attr.colorSwipeRefresh
|
||||||
|
)
|
||||||
|
)
|
||||||
conferenceErrorRetry.setOnClickListener { presenter.onRetry() }
|
conferenceErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||||
conferenceErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
conferenceErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||||
}
|
}
|
||||||
@ -98,6 +105,10 @@ class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.frag
|
|||||||
binding.conferenceRecycler.visibility = if (show) View.VISIBLE else View.GONE
|
binding.conferenceRecycler.visibility = if (show) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openConferenceDialog(conference: Conference) {
|
||||||
|
(activity as? MainActivity)?.showDialogFragment(ConferenceDialog.newInstance(conference))
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
presenter.onDetachView()
|
presenter.onDetachView()
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.github.wulkanowy.ui.modules.conference
|
package io.github.wulkanowy.ui.modules.conference
|
||||||
|
|
||||||
import io.github.wulkanowy.data.Status
|
import io.github.wulkanowy.data.Status
|
||||||
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
import io.github.wulkanowy.data.repositories.ConferenceRepository
|
import io.github.wulkanowy.data.repositories.ConferenceRepository
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
@ -43,6 +44,10 @@ class ConferencePresenter @Inject constructor(
|
|||||||
loadData(true)
|
loadData(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onItemSelected(conference: Conference) {
|
||||||
|
view?.openConferenceDialog(conference)
|
||||||
|
}
|
||||||
|
|
||||||
fun onDetailsClick() {
|
fun onDetailsClick() {
|
||||||
view?.showErrorDetailsDialog(lastError)
|
view?.showErrorDetailsDialog(lastError)
|
||||||
}
|
}
|
||||||
|
@ -26,4 +26,6 @@ interface ConferenceView : BaseView {
|
|||||||
fun enableSwipe(enable: Boolean)
|
fun enableSwipe(enable: Boolean)
|
||||||
|
|
||||||
fun showContent(show: Boolean)
|
fun showContent(show: Boolean)
|
||||||
|
|
||||||
|
fun openConferenceDialog(conference: Conference)
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
|
|
||||||
var onAttendanceTileClickListener: () -> Unit = {}
|
var onAttendanceTileClickListener: () -> Unit = {}
|
||||||
|
|
||||||
var onLessonsTileClickListener: () -> Unit = {}
|
var onLessonsTileClickListener: (LocalDate) -> Unit = {}
|
||||||
|
|
||||||
var onHomeworkTileClickListener: () -> Unit = {}
|
var onHomeworkTileClickListener: () -> Unit = {}
|
||||||
|
|
||||||
@ -275,10 +275,12 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
val item = items[position] as DashboardItem.Lessons
|
val item = items[position] as DashboardItem.Lessons
|
||||||
val timetableFull = item.lessons
|
val timetableFull = item.lessons
|
||||||
val binding = lessonsViewHolder.binding
|
val binding = lessonsViewHolder.binding
|
||||||
|
var dateToNavigate = LocalDate.now()
|
||||||
|
|
||||||
fun updateLessonState() {
|
fun updateLessonState() {
|
||||||
val currentDateTime = LocalDateTime.now()
|
val currentDateTime = LocalDateTime.now()
|
||||||
val currentDate = LocalDate.now()
|
val currentDate = LocalDate.now()
|
||||||
|
val tomorrowDate = currentDate.plusDays(1)
|
||||||
|
|
||||||
val currentTimetable = timetableFull?.lessons
|
val currentTimetable = timetableFull?.lessons
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
@ -296,22 +298,27 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
|
|
||||||
when {
|
when {
|
||||||
currentTimetable.isNotEmpty() -> {
|
currentTimetable.isNotEmpty() -> {
|
||||||
|
dateToNavigate = currentDate
|
||||||
updateLessonView(item, currentTimetable, binding)
|
updateLessonView(item, currentTimetable, binding)
|
||||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
||||||
}
|
}
|
||||||
tomorrowTimetable.isNotEmpty() -> {
|
tomorrowTimetable.isNotEmpty() -> {
|
||||||
|
dateToNavigate = tomorrowDate
|
||||||
updateLessonView(item, tomorrowTimetable, binding)
|
updateLessonView(item, tomorrowTimetable, binding)
|
||||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
||||||
}
|
}
|
||||||
currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
|
currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
|
||||||
|
dateToNavigate = currentDate
|
||||||
updateLessonView(item, emptyList(), binding, currentDayHeader)
|
updateLessonView(item, emptyList(), binding, currentDayHeader)
|
||||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
||||||
}
|
}
|
||||||
tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> {
|
tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> {
|
||||||
|
dateToNavigate = tomorrowDate
|
||||||
updateLessonView(item, emptyList(), binding, tomorrowDayHeader)
|
updateLessonView(item, emptyList(), binding, tomorrowDayHeader)
|
||||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
dateToNavigate = tomorrowDate
|
||||||
updateLessonView(item, emptyList(), binding)
|
updateLessonView(item, emptyList(), binding)
|
||||||
binding.dashboardLessonsItemTitleTomorrow.isVisible =
|
binding.dashboardLessonsItemTitleTomorrow.isVisible =
|
||||||
!(item.isLoading && item.error == null)
|
!(item.isLoading && item.error == null)
|
||||||
@ -326,7 +333,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
Handler(Looper.getMainLooper()).post { updateLessonState() }
|
Handler(Looper.getMainLooper()).post { updateLessonState() }
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.root.setOnClickListener { onLessonsTileClickListener() }
|
binding.root.setOnClickListener { onLessonsTileClickListener(dateToNavigate) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateLessonView(
|
private fun updateLessonView(
|
||||||
|
@ -24,6 +24,7 @@ import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
|
|||||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment
|
||||||
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
||||||
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
||||||
import io.github.wulkanowy.utils.capitalise
|
import io.github.wulkanowy.utils.capitalise
|
||||||
@ -70,10 +71,7 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
|||||||
override fun initView() {
|
override fun initView() {
|
||||||
val mainActivity = requireActivity() as MainActivity
|
val mainActivity = requireActivity() as MainActivity
|
||||||
val itemTouchHelper = ItemTouchHelper(
|
val itemTouchHelper = ItemTouchHelper(
|
||||||
DashboardItemMoveCallback(
|
DashboardItemMoveCallback(dashboardAdapter, presenter::onDragAndDropEnd)
|
||||||
dashboardAdapter,
|
|
||||||
presenter::onDragAndDropEnd
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
dashboardAdapter.apply {
|
dashboardAdapter.apply {
|
||||||
@ -87,7 +85,9 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
|||||||
onAttendanceTileClickListener = {
|
onAttendanceTileClickListener = {
|
||||||
mainActivity.pushView(AttendanceSummaryFragment.newInstance())
|
mainActivity.pushView(AttendanceSummaryFragment.newInstance())
|
||||||
}
|
}
|
||||||
onLessonsTileClickListener = { mainActivity.pushView(TimetableFragment.newInstance()) }
|
onLessonsTileClickListener = {
|
||||||
|
mainActivity.pushView(TimetableFragment.newInstance(it))
|
||||||
|
}
|
||||||
onGradeTileClickListener = { mainActivity.pushView(GradeFragment.newInstance()) }
|
onGradeTileClickListener = { mainActivity.pushView(GradeFragment.newInstance()) }
|
||||||
onHomeworkTileClickListener = { mainActivity.pushView(HomeworkFragment.newInstance()) }
|
onHomeworkTileClickListener = { mainActivity.pushView(HomeworkFragment.newInstance()) }
|
||||||
onAnnouncementsTileClickListener = {
|
onAnnouncementsTileClickListener = {
|
||||||
@ -121,6 +121,7 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
|||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
return when (item.itemId) {
|
return when (item.itemId) {
|
||||||
R.id.dashboard_menu_tiles -> presenter.onDashboardTileSettingsSelected()
|
R.id.dashboard_menu_tiles -> presenter.onDashboardTileSettingsSelected()
|
||||||
|
R.id.dashboard_menu_notifaction_list -> presenter.onNotificationsCenterSelected()
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,6 +184,10 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
|||||||
if (::presenter.isInitialized) presenter.onViewReselected()
|
if (::presenter.isInitialized) presenter.onViewReselected()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openNotificationsCenterView() {
|
||||||
|
(requireActivity() as MainActivity).pushView(NotificationsCenterFragment.newInstance())
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
dashboardAdapter.clearTimers()
|
dashboardAdapter.clearTimers()
|
||||||
presenter.onDetachView()
|
presenter.onDetachView()
|
||||||
|
@ -209,6 +209,11 @@ class DashboardPresenter @Inject constructor(
|
|||||||
view?.showErrorDetailsDialog(lastError)
|
view?.showErrorDetailsDialog(lastError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onNotificationsCenterSelected(): Boolean {
|
||||||
|
view?.openNotificationsCenterView()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
fun onDashboardTileSettingsSelected(): Boolean {
|
fun onDashboardTileSettingsSelected(): Boolean {
|
||||||
view?.showDashboardTileSettings(preferencesRepository.selectedDashboardTiles.toList())
|
view?.showDashboardTileSettings(preferencesRepository.selectedDashboardTiles.toList())
|
||||||
return true
|
return true
|
||||||
@ -492,31 +497,37 @@ class DashboardPresenter @Inject constructor(
|
|||||||
end = LocalDate.now().plusDays(7),
|
end = LocalDate.now().plusDays(7),
|
||||||
forceRefresh = forceRefresh
|
forceRefresh = forceRefresh
|
||||||
)
|
)
|
||||||
}.onEach {
|
}
|
||||||
when (it.status) {
|
.map { examResource ->
|
||||||
Status.LOADING -> {
|
val sortedExams = examResource.data?.sortedBy { it.date }
|
||||||
Timber.i("Loading dashboard exams data started")
|
|
||||||
if (forceRefresh) return@onEach
|
|
||||||
updateData(
|
|
||||||
DashboardItem.Exams(it.data.orEmpty(), isLoading = true),
|
|
||||||
forceRefresh
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!it.data.isNullOrEmpty()) {
|
examResource.copy(data = sortedExams)
|
||||||
firstLoadedItemList += DashboardItem.Type.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Status.SUCCESS -> {
|
}.launch("dashboard_exams")
|
||||||
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(student: Student, forceRefresh: Boolean) {
|
private fun loadConferences(student: Student, forceRefresh: Boolean) {
|
||||||
|
@ -23,4 +23,6 @@ interface DashboardView : BaseView {
|
|||||||
fun resetView()
|
fun resetView()
|
||||||
|
|
||||||
fun popViewToRoot()
|
fun popViewToRoot()
|
||||||
|
|
||||||
|
fun openNotificationsCenterView()
|
||||||
}
|
}
|
@ -87,7 +87,7 @@ class NotificationDebugPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun withStudent(block: (Student) -> Unit) {
|
private fun withStudent(block: suspend (Student) -> Unit) {
|
||||||
launch {
|
launch {
|
||||||
block(studentRepository.getCurrentStudent(false))
|
block(studentRepository.getCurrentStudent(false))
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,9 @@ class GradeAverageProvider @Inject constructor(
|
|||||||
val updatedFirstSemesterGrades =
|
val updatedFirstSemesterGrades =
|
||||||
firstSemesterSubject?.grades?.updateModifiers(student).orEmpty()
|
firstSemesterSubject?.grades?.updateModifiers(student).orEmpty()
|
||||||
|
|
||||||
(updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage(isOptionalArithmeticAverage)
|
(updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage(
|
||||||
|
isOptionalArithmeticAverage
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
secondSemesterSubject.average
|
secondSemesterSubject.average
|
||||||
}
|
}
|
||||||
@ -147,7 +149,8 @@ class GradeAverageProvider @Inject constructor(
|
|||||||
|
|
||||||
return if (!isAnyVulcanAverage || isGradeAverageForceCalc) {
|
return if (!isAnyVulcanAverage || isGradeAverageForceCalc) {
|
||||||
val secondSemesterAverage =
|
val secondSemesterAverage =
|
||||||
secondSemesterSubject.grades.updateModifiers(student).calcAverage(isOptionalArithmeticAverage)
|
secondSemesterSubject.grades.updateModifiers(student)
|
||||||
|
.calcAverage(isOptionalArithmeticAverage)
|
||||||
val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student)
|
val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student)
|
||||||
?.calcAverage(isOptionalArithmeticAverage) ?: secondSemesterAverage
|
?.calcAverage(isOptionalArithmeticAverage) ?: secondSemesterAverage
|
||||||
|
|
||||||
@ -213,7 +216,8 @@ class GradeAverageProvider @Inject constructor(
|
|||||||
proposedPoints = "",
|
proposedPoints = "",
|
||||||
finalPoints = "",
|
finalPoints = "",
|
||||||
pointsSum = "",
|
pointsSum = "",
|
||||||
average = if (calcAverage) details.updateModifiers(student).calcAverage(isOptionalArithmeticAverage) else .0
|
average = if (calcAverage) details.updateModifiers(student)
|
||||||
|
.calcAverage(isOptionalArithmeticAverage) else .0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import io.github.wulkanowy.data.db.entities.GradeSummary
|
|||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.databinding.ItemGradeSummaryBinding
|
import io.github.wulkanowy.databinding.ItemGradeSummaryBinding
|
||||||
import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding
|
import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding
|
||||||
import io.github.wulkanowy.utils.calcAverage
|
import io.github.wulkanowy.utils.calcFinalAverage
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -25,6 +25,10 @@ class GradeSummaryAdapter @Inject constructor(
|
|||||||
|
|
||||||
var items = emptyList<GradeSummary>()
|
var items = emptyList<GradeSummary>()
|
||||||
|
|
||||||
|
var onCalculatedHelpClickListener: () -> Unit = {}
|
||||||
|
|
||||||
|
var onFinalHelpClickListener: () -> Unit = {}
|
||||||
|
|
||||||
override fun getItemCount() = items.size + if (items.isNotEmpty()) 1 else 0
|
override fun getItemCount() = items.size + if (items.isNotEmpty()) 1 else 0
|
||||||
|
|
||||||
override fun getItemViewType(position: Int) = when (position) {
|
override fun getItemViewType(position: Int) = when (position) {
|
||||||
@ -60,7 +64,7 @@ class GradeSummaryAdapter @Inject constructor(
|
|||||||
val finalItemsCount = items.count { it.finalGrade.matches("[0-6][+-]?".toRegex()) }
|
val finalItemsCount = items.count { it.finalGrade.matches("[0-6][+-]?".toRegex()) }
|
||||||
val calculatedItemsCount = items.count { value -> value.average != 0.0 }
|
val calculatedItemsCount = items.count { value -> value.average != 0.0 }
|
||||||
val allItemsCount = items.count { !it.subject.equals("zachowanie", true) }
|
val allItemsCount = items.count { !it.subject.equals("zachowanie", true) }
|
||||||
val finalAverage = items.calcAverage(
|
val finalAverage = items.calcFinalAverage(
|
||||||
preferencesRepository.gradePlusModifier,
|
preferencesRepository.gradePlusModifier,
|
||||||
preferencesRepository.gradeMinusModifier
|
preferencesRepository.gradeMinusModifier
|
||||||
)
|
)
|
||||||
@ -83,6 +87,9 @@ class GradeSummaryAdapter @Inject constructor(
|
|||||||
calculatedItemsCount,
|
calculatedItemsCount,
|
||||||
allItemsCount
|
allItemsCount
|
||||||
)
|
)
|
||||||
|
|
||||||
|
gradeSummaryCalculatedAverageHelp.setOnClickListener { onCalculatedHelpClickListener() }
|
||||||
|
gradeSummaryFinalAverageHelp.setOnClickListener { onFinalHelpClickListener() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import android.view.View
|
|||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.INVISIBLE
|
import android.view.View.INVISIBLE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
@ -48,6 +49,11 @@ class GradeSummaryFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
|
with(gradeSummaryAdapter) {
|
||||||
|
onCalculatedHelpClickListener = presenter::onCalculatedAverageHelpClick
|
||||||
|
onFinalHelpClickListener = presenter::onFinalAverageHelpClick
|
||||||
|
}
|
||||||
|
|
||||||
with(binding.gradeSummaryRecycler) {
|
with(binding.gradeSummaryRecycler) {
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = gradeSummaryAdapter
|
adapter = gradeSummaryAdapter
|
||||||
@ -55,7 +61,11 @@ class GradeSummaryFragment :
|
|||||||
with(binding) {
|
with(binding) {
|
||||||
gradeSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
gradeSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||||
gradeSummarySwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
gradeSummarySwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||||
gradeSummarySwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
gradeSummarySwipe.setProgressBackgroundColorSchemeColor(
|
||||||
|
requireContext().getThemeAttrColor(
|
||||||
|
R.attr.colorSwipeRefresh
|
||||||
|
)
|
||||||
|
)
|
||||||
gradeSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
|
gradeSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||||
gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||||
}
|
}
|
||||||
@ -107,6 +117,22 @@ class GradeSummaryFragment :
|
|||||||
binding.gradeSummarySwipe.isRefreshing = show
|
binding.gradeSummarySwipe.isRefreshing = show
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun showCalculatedAverageHelpDialog() {
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.grade_summary_calculated_average_help_dialog_title)
|
||||||
|
.setMessage(R.string.grade_summary_calculated_average_help_dialog_message)
|
||||||
|
.setPositiveButton(R.string.all_close) { _, _ -> }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showFinalAverageHelpDialog() {
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.grade_summary_final_average_help_dialog_title)
|
||||||
|
.setMessage(R.string.grade_summary_final_average_help_dialog_message)
|
||||||
|
.setPositiveButton(R.string.all_close) { _, _ -> }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) {
|
override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) {
|
||||||
presenter.onParentViewLoadData(semesterId, forceRefresh)
|
presenter.onParentViewLoadData(semesterId, forceRefresh)
|
||||||
}
|
}
|
||||||
|
@ -135,6 +135,14 @@ class GradeSummaryPresenter @Inject constructor(
|
|||||||
cancelJobs("load")
|
cancelJobs("load")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onCalculatedAverageHelpClick() {
|
||||||
|
view?.showCalculatedAverageHelpDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onFinalAverageHelpClick() {
|
||||||
|
view?.showFinalAverageHelpDialog()
|
||||||
|
}
|
||||||
|
|
||||||
private fun createGradeSummaryItems(items: List<GradeSubject>): List<GradeSummary> {
|
private fun createGradeSummaryItems(items: List<GradeSubject>): List<GradeSummary> {
|
||||||
return items
|
return items
|
||||||
.filter { !checkEmpty(it) }
|
.filter { !checkEmpty(it) }
|
||||||
|
@ -33,6 +33,10 @@ interface GradeSummaryView : BaseView {
|
|||||||
|
|
||||||
fun showEmpty(show: Boolean)
|
fun showEmpty(show: Boolean)
|
||||||
|
|
||||||
|
fun showCalculatedAverageHelpDialog()
|
||||||
|
|
||||||
|
fun showFinalAverageHelpDialog()
|
||||||
|
|
||||||
fun notifyParentDataLoaded(semesterId: Int)
|
fun notifyParentDataLoaded(semesterId: Int)
|
||||||
|
|
||||||
fun notifyParentRefresh()
|
fun notifyParentRefresh()
|
||||||
|
@ -103,9 +103,8 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyInitSymbolFragment(loginData: Triple<String, String, String>) {
|
override fun notifyInitSymbolFragment(loginData: Triple<String, String, String>) {
|
||||||
(loginAdapter.getFragmentInstance(1) as? LoginSymbolFragment)?.onParentInitSymbolFragment(
|
(loginAdapter.getFragmentInstance(1) as? LoginSymbolFragment)
|
||||||
loginData
|
?.onParentInitSymbolFragment(loginData)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyInitStudentSelectFragment(studentsWithSemesters: List<StudentWithSemesters>) {
|
override fun notifyInitStudentSelectFragment(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||||
|
@ -13,7 +13,7 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
|
class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
|
||||||
|
|
||||||
var onBadCredentials: () -> Unit = {}
|
var onBadCredentials: (String?) -> Unit = {}
|
||||||
|
|
||||||
var onInvalidToken: (String) -> Unit = {}
|
var onInvalidToken: (String) -> Unit = {}
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler
|
|||||||
|
|
||||||
override fun proceed(error: Throwable) {
|
override fun proceed(error: Throwable) {
|
||||||
when (error) {
|
when (error) {
|
||||||
is BadCredentialsException -> onBadCredentials()
|
is BadCredentialsException -> onBadCredentials(error.message)
|
||||||
is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student))
|
is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student))
|
||||||
is TokenDeadException -> onInvalidToken(resources.getString(R.string.login_expired_token))
|
is TokenDeadException -> onInvalidToken(resources.getString(R.string.login_expired_token))
|
||||||
is InvalidTokenException -> onInvalidToken(resources.getString(R.string.login_invalid_token))
|
is InvalidTokenException -> onInvalidToken(resources.getString(R.string.login_invalid_token))
|
||||||
|
@ -51,10 +51,12 @@ class LoginAdvancedFragment :
|
|||||||
private lateinit var hostSymbols: Array<String>
|
private lateinit var hostSymbols: Array<String>
|
||||||
|
|
||||||
override val formHostValue: String
|
override val formHostValue: String
|
||||||
get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty()
|
get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
override val formHostSymbol: String
|
override val formHostSymbol: String
|
||||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty()
|
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
override val formPinValue: String
|
override val formPinValue: String
|
||||||
get() = binding.loginFormPin.text.toString().trim()
|
get() = binding.loginFormPin.text.toString().trim()
|
||||||
@ -92,39 +94,62 @@ class LoginAdvancedFragment :
|
|||||||
loginFormSignIn.setOnClickListener { presenter.onSignInClick() }
|
loginFormSignIn.setOnClickListener { presenter.onSignInClick() }
|
||||||
|
|
||||||
loginTypeSwitch.setOnCheckedChangeListener { _, checkedId ->
|
loginTypeSwitch.setOnCheckedChangeListener { _, checkedId ->
|
||||||
presenter.onLoginModeSelected(when (checkedId) {
|
presenter.onLoginModeSelected(
|
||||||
R.id.loginTypeApi -> Sdk.Mode.API
|
when (checkedId) {
|
||||||
R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER
|
R.id.loginTypeApi -> Sdk.Mode.API
|
||||||
else -> Sdk.Mode.HYBRID
|
R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER
|
||||||
})
|
else -> Sdk.Mode.HYBRID
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
loginFormPin.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() }
|
loginFormPin.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() }
|
||||||
loginFormPass.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() }
|
loginFormPass.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() }
|
||||||
|
|
||||||
loginFormSymbol.setAdapter(ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, resources.getStringArray(R.array.symbols_values)))
|
loginFormSymbol.setAdapter(
|
||||||
|
ArrayAdapter(
|
||||||
|
requireContext(),
|
||||||
|
android.R.layout.simple_list_item_1,
|
||||||
|
resources.getStringArray(R.array.symbols_values)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
with(binding.loginFormHost) {
|
with(binding.loginFormHost) {
|
||||||
setText(hostKeys.getOrNull(0).orEmpty())
|
setText(hostKeys.getOrNull(0).orEmpty())
|
||||||
setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys))
|
setAdapter(
|
||||||
|
LoginSymbolAdapter(
|
||||||
|
context,
|
||||||
|
R.layout.support_simple_spinner_dropdown_item,
|
||||||
|
hostKeys
|
||||||
|
)
|
||||||
|
)
|
||||||
setOnClickListener { if (binding.loginFormContainer.visibility == GONE) dismissDropDown() }
|
setOnClickListener { if (binding.loginFormContainer.visibility == GONE) dismissDropDown() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showMobileApiWarningMessage() {
|
override fun showMobileApiWarningMessage() {
|
||||||
binding.loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_mobile_api)
|
binding.loginFormAdvancedWarningInfo.text =
|
||||||
|
getString(R.string.login_advanced_warning_mobile_api)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showScraperWarningMessage() {
|
override fun showScraperWarningMessage() {
|
||||||
binding.loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_scraper)
|
binding.loginFormAdvancedWarningInfo.text =
|
||||||
|
getString(R.string.login_advanced_warning_scraper)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showHybridWarningMessage() {
|
override fun showHybridWarningMessage() {
|
||||||
binding.loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_hybrid)
|
binding.loginFormAdvancedWarningInfo.text =
|
||||||
|
getString(R.string.login_advanced_warning_hybrid)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setDefaultCredentials(username: String, pass: String, symbol: String, token: String, pin: String) {
|
override fun setDefaultCredentials(
|
||||||
|
username: String,
|
||||||
|
pass: String,
|
||||||
|
symbol: String,
|
||||||
|
token: String,
|
||||||
|
pin: String
|
||||||
|
) {
|
||||||
with(binding) {
|
with(binding) {
|
||||||
loginFormUsername.setText(username)
|
loginFormUsername.setText(username)
|
||||||
loginFormPass.setText(pass)
|
loginFormPass.setText(pass)
|
||||||
@ -177,10 +202,10 @@ class LoginAdvancedFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setErrorPassIncorrect() {
|
override fun setErrorPassIncorrect(message: String?) {
|
||||||
with(binding.loginFormPassLayout) {
|
with(binding.loginFormPassLayout) {
|
||||||
requestFocus()
|
requestFocus()
|
||||||
error = getString(R.string.login_incorrect_password)
|
error = message ?: getString(R.string.login_incorrect_password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,11 +321,13 @@ class LoginAdvancedFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyParentAccountLogged(studentsWithSemesters: List<StudentWithSemesters>) {
|
override fun notifyParentAccountLogged(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||||
(activity as? LoginActivity)?.onFormFragmentAccountLogged(studentsWithSemesters, Triple(
|
(activity as? LoginActivity)?.onFormFragmentAccountLogged(
|
||||||
binding.loginFormUsername.text.toString(),
|
studentsWithSemesters, Triple(
|
||||||
binding.loginFormPass.text.toString(),
|
binding.loginFormUsername.text.toString(),
|
||||||
resources.getStringArray(R.array.hosts_values)[1]
|
binding.loginFormPass.text.toString(),
|
||||||
))
|
resources.getStringArray(R.array.hosts_values)[1]
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
@ -34,9 +34,9 @@ class LoginAdvancedPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onBadCredentials() {
|
private fun onBadCredentials(message: String?) {
|
||||||
view?.run {
|
view?.run {
|
||||||
setErrorPassIncorrect()
|
setErrorPassIncorrect(message)
|
||||||
showSoftKeyboard()
|
showSoftKeyboard()
|
||||||
Timber.i("Entered wrong username or password")
|
Timber.i("Entered wrong username or password")
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ interface LoginAdvancedView : BaseView {
|
|||||||
|
|
||||||
fun setErrorPassInvalid(focus: Boolean)
|
fun setErrorPassInvalid(focus: Boolean)
|
||||||
|
|
||||||
fun setErrorPassIncorrect()
|
fun setErrorPassIncorrect(message: String?)
|
||||||
|
|
||||||
fun clearUsernameError()
|
fun clearUsernameError()
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
@ -41,10 +42,12 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
|||||||
get() = binding.loginFormPass.text.toString()
|
get() = binding.loginFormPass.text.toString()
|
||||||
|
|
||||||
override val formHostValue: String
|
override val formHostValue: String
|
||||||
get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty()
|
get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
override val formHostSymbol: String
|
override val formHostSymbol: String
|
||||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty()
|
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
override val nicknameLabel: String
|
override val nicknameLabel: String
|
||||||
get() = getString(R.string.login_nickname_hint)
|
get() = getString(R.string.login_nickname_hint)
|
||||||
@ -88,7 +91,13 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
|||||||
|
|
||||||
with(binding.loginFormHost) {
|
with(binding.loginFormHost) {
|
||||||
setText(hostKeys.getOrNull(0).orEmpty())
|
setText(hostKeys.getOrNull(0).orEmpty())
|
||||||
setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys))
|
setAdapter(
|
||||||
|
LoginSymbolAdapter(
|
||||||
|
context,
|
||||||
|
R.layout.support_simple_spinner_dropdown_item,
|
||||||
|
hostKeys
|
||||||
|
)
|
||||||
|
)
|
||||||
setOnClickListener { if (binding.loginFormContainer.visibility == GONE) dismissDropDown() }
|
setOnClickListener { if (binding.loginFormContainer.visibility == GONE) dismissDropDown() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,24 +151,31 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setErrorPassIncorrect() {
|
override fun setErrorPassIncorrect(message: String?) {
|
||||||
with(binding.loginFormPassLayout) {
|
val error = message ?: getString(R.string.login_incorrect_password_default)
|
||||||
error = getString(R.string.login_incorrect_password)
|
|
||||||
|
with(binding) {
|
||||||
|
loginFormUsernameLayout.error = " "
|
||||||
|
loginFormPassLayout.error = " "
|
||||||
|
loginFormErrorBox.text = getString(R.string.login_incorrect_password, error)
|
||||||
|
loginFormErrorBox.isVisible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setErrorEmailInvalid(domain: String) {
|
override fun setErrorEmailInvalid(domain: String) {
|
||||||
with(binding.loginFormUsernameLayout) {
|
with(binding.loginFormUsernameLayout) {
|
||||||
error = getString(R.string.login_invalid_custom_email,domain)
|
error = getString(R.string.login_invalid_custom_email, domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearUsernameError() {
|
override fun clearUsernameError() {
|
||||||
binding.loginFormUsernameLayout.error = null
|
binding.loginFormUsernameLayout.error = null
|
||||||
|
binding.loginFormErrorBox.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearPassError() {
|
override fun clearPassError() {
|
||||||
binding.loginFormPassLayout.error = null
|
binding.loginFormPassLayout.error = null
|
||||||
|
binding.loginFormErrorBox.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showSoftKeyboard() {
|
override fun showSoftKeyboard() {
|
||||||
@ -183,12 +199,18 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
|||||||
binding.loginFormVersion.text = "v${appInfo.versionName}"
|
binding.loginFormVersion.text = "v${appInfo.versionName}"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyParentAccountLogged(studentsWithSemesters: List<StudentWithSemesters>, loginData: Triple<String, String, String>) {
|
override fun notifyParentAccountLogged(
|
||||||
|
studentsWithSemesters: List<StudentWithSemesters>,
|
||||||
|
loginData: Triple<String, String, String>
|
||||||
|
) {
|
||||||
(activity as? LoginActivity)?.onFormFragmentAccountLogged(studentsWithSemesters, loginData)
|
(activity as? LoginActivity)?.onFormFragmentAccountLogged(studentsWithSemesters, loginData)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openPrivacyPolicyPage() {
|
override fun openPrivacyPolicyPage() {
|
||||||
context?.openInternetBrowser("https://wulkanowy.github.io/polityka-prywatnosci.html", ::showMessage)
|
context?.openInternetBrowser(
|
||||||
|
"https://wulkanowy.github.io/polityka-prywatnosci.html",
|
||||||
|
::showMessage
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showContact(show: Boolean) {
|
override fun showContact(show: Boolean) {
|
||||||
@ -210,7 +232,10 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun openFaqPage() {
|
override fun openFaqPage() {
|
||||||
context?.openInternetBrowser("https://wulkanowy.github.io/czesto-zadawane-pytania/dlaczego-nie-moge-sie-zalogowac", ::showMessage)
|
context?.openInternetBrowser(
|
||||||
|
"https://wulkanowy.github.io/czesto-zadawane-pytania/dlaczego-nie-moge-sie-zalogowac",
|
||||||
|
::showMessage
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@ -223,7 +248,8 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
|||||||
chooserTitle = requireContext().getString(R.string.login_email_intent_title),
|
chooserTitle = requireContext().getString(R.string.login_email_intent_title),
|
||||||
email = "wulkanowyinc@gmail.com",
|
email = "wulkanowyinc@gmail.com",
|
||||||
subject = requireContext().getString(R.string.login_email_subject),
|
subject = requireContext().getString(R.string.login_email_subject),
|
||||||
body = requireContext().getString(R.string.login_email_text,
|
body = requireContext().getString(
|
||||||
|
R.string.login_email_text,
|
||||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||||
appInfo.systemVersion.toString(),
|
appInfo.systemVersion.toString(),
|
||||||
appInfo.versionName,
|
appInfo.versionName,
|
||||||
|
@ -30,7 +30,7 @@ class LoginFormPresenter @Inject constructor(
|
|||||||
showVersion()
|
showVersion()
|
||||||
|
|
||||||
loginErrorHandler.onBadCredentials = {
|
loginErrorHandler.onBadCredentials = {
|
||||||
setErrorPassIncorrect()
|
setErrorPassIncorrect(it)
|
||||||
showSoftKeyboard()
|
showSoftKeyboard()
|
||||||
Timber.i("Entered wrong username or password")
|
Timber.i("Entered wrong username or password")
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ interface LoginFormView : BaseView {
|
|||||||
|
|
||||||
fun setErrorPassInvalid(focus: Boolean)
|
fun setErrorPassInvalid(focus: Boolean)
|
||||||
|
|
||||||
fun setErrorPassIncorrect()
|
fun setErrorPassIncorrect(message: String?)
|
||||||
|
|
||||||
fun setErrorEmailInvalid(domain: String)
|
fun setErrorEmailInvalid(domain: String)
|
||||||
|
|
||||||
|
@ -79,12 +79,7 @@ class MessagePreviewAdapter @Inject constructor() :
|
|||||||
|
|
||||||
val readText = when {
|
val readText = when {
|
||||||
recipientCount > 1 -> {
|
recipientCount > 1 -> {
|
||||||
context.resources.getQuantityString(
|
context.getString(R.string.message_read_by, message.readBy, recipientCount)
|
||||||
R.plurals.message_read_by,
|
|
||||||
message.readBy,
|
|
||||||
message.readBy,
|
|
||||||
recipientCount
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
message.readBy == 1 -> {
|
message.readBy == 1 -> {
|
||||||
context.getString(R.string.message_read, context.getString(R.string.all_yes))
|
context.getString(R.string.message_read, context.getString(R.string.all_yes))
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.notificationscenter
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import io.github.wulkanowy.databinding.ItemNotificationsCenterBinding
|
||||||
|
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||||
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class NotificationsCenterAdapter @Inject constructor() :
|
||||||
|
ListAdapter<Notification, NotificationsCenterAdapter.ViewHolder>(DiffUtilCallback()) {
|
||||||
|
|
||||||
|
var onItemClickListener: (NotificationType) -> Unit = {}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||||
|
ItemNotificationsCenterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val item = getItem(position)
|
||||||
|
|
||||||
|
with(holder.binding) {
|
||||||
|
notificationsCenterItemTitle.text = item.title
|
||||||
|
notificationsCenterItemContent.text = item.content
|
||||||
|
notificationsCenterItemDate.text = item.date.toFormattedString("HH:mm, d MMM")
|
||||||
|
notificationsCenterItemIcon.setImageResource(item.type.toDrawableResId())
|
||||||
|
|
||||||
|
root.setOnClickListener { onItemClickListener(item.type) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NotificationType.toDrawableResId() = when (this) {
|
||||||
|
NotificationType.NEW_CONFERENCE -> R.drawable.ic_more_conferences
|
||||||
|
NotificationType.NEW_EXAM -> R.drawable.ic_main_exam
|
||||||
|
NotificationType.NEW_GRADE_DETAILS -> R.drawable.ic_stat_grade
|
||||||
|
NotificationType.NEW_GRADE_PREDICTED -> R.drawable.ic_stat_grade
|
||||||
|
NotificationType.NEW_GRADE_FINAL -> R.drawable.ic_stat_grade
|
||||||
|
NotificationType.NEW_HOMEWORK -> R.drawable.ic_more_homework
|
||||||
|
NotificationType.NEW_LUCKY_NUMBER -> R.drawable.ic_stat_luckynumber
|
||||||
|
NotificationType.NEW_MESSAGE -> R.drawable.ic_stat_message
|
||||||
|
NotificationType.NEW_NOTE -> R.drawable.ic_stat_note
|
||||||
|
NotificationType.NEW_ANNOUNCEMENT -> R.drawable.ic_all_about
|
||||||
|
NotificationType.PUSH -> R.drawable.ic_stat_all
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewHolder(val binding: ItemNotificationsCenterBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
private class DiffUtilCallback : DiffUtil.ItemCallback<Notification>() {
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: Notification, newItem: Notification) =
|
||||||
|
oldItem == newItem
|
||||||
|
|
||||||
|
override fun areItemsTheSame(oldItem: Notification, newItem: Notification) =
|
||||||
|
oldItem.id == newItem.id
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.notificationscenter
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import io.github.wulkanowy.databinding.FragmentNotificationsCenterBinding
|
||||||
|
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||||
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
|
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.note.NoteFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class NotificationsCenterFragment :
|
||||||
|
BaseFragment<FragmentNotificationsCenterBinding>(R.layout.fragment_notifications_center),
|
||||||
|
NotificationsCenterView, MainView.TitledView {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var presenter: NotificationsCenterPresenter
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var notificationsCenterAdapter: NotificationsCenterAdapter
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance() = NotificationsCenterFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val titleStringId: Int
|
||||||
|
get() = R.string.notifications_center_title
|
||||||
|
|
||||||
|
override val isViewEmpty: Boolean
|
||||||
|
get() = notificationsCenterAdapter.itemCount == 0
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding = FragmentNotificationsCenterBinding.bind(view)
|
||||||
|
presenter.onAttachView(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initView() {
|
||||||
|
notificationsCenterAdapter.onItemClickListener = { notificationType ->
|
||||||
|
notificationType.toDestinationFragment()
|
||||||
|
?.let { (requireActivity() as MainActivity).pushView(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
with(binding.notificationsCenterRecycler) {
|
||||||
|
layoutManager = LinearLayoutManager(context)
|
||||||
|
adapter = notificationsCenterAdapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateData(data: List<Notification>) {
|
||||||
|
notificationsCenterAdapter.submitList(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showEmpty(show: Boolean) {
|
||||||
|
binding.notificationsCenterEmpty.isVisible = show
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showProgress(show: Boolean) {
|
||||||
|
binding.notificationsCenterProgress.isVisible = show
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showContent(show: Boolean) {
|
||||||
|
binding.notificationsCenterRecycler.isVisible = show
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showErrorView(show: Boolean) {
|
||||||
|
binding.notificationCenterError.isVisible = show
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setErrorDetails(message: String) {
|
||||||
|
binding.notificationCenterErrorMessage.text = message
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
presenter.onDetachView()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NotificationType.toDestinationFragment(): Fragment? = when (this) {
|
||||||
|
NotificationType.NEW_CONFERENCE -> ConferenceFragment.newInstance()
|
||||||
|
NotificationType.NEW_EXAM -> ExamFragment.newInstance()
|
||||||
|
NotificationType.NEW_GRADE_DETAILS -> GradeFragment.newInstance()
|
||||||
|
NotificationType.NEW_GRADE_PREDICTED -> GradeFragment.newInstance()
|
||||||
|
NotificationType.NEW_GRADE_FINAL -> GradeFragment.newInstance()
|
||||||
|
NotificationType.NEW_HOMEWORK -> HomeworkFragment.newInstance()
|
||||||
|
NotificationType.NEW_LUCKY_NUMBER -> LuckyNumberFragment.newInstance()
|
||||||
|
NotificationType.NEW_MESSAGE -> MessageFragment.newInstance()
|
||||||
|
NotificationType.NEW_NOTE -> NoteFragment.newInstance()
|
||||||
|
NotificationType.NEW_ANNOUNCEMENT -> SchoolAnnouncementFragment.newInstance()
|
||||||
|
NotificationType.PUSH -> null
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.notificationscenter
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.repositories.NotificationRepository
|
||||||
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.emitAll
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class NotificationsCenterPresenter @Inject constructor(
|
||||||
|
private val notificationRepository: NotificationRepository,
|
||||||
|
errorHandler: ErrorHandler,
|
||||||
|
studentRepository: StudentRepository
|
||||||
|
) : BasePresenter<NotificationsCenterView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
|
private lateinit var lastError: Throwable
|
||||||
|
|
||||||
|
override fun onAttachView(view: NotificationsCenterView) {
|
||||||
|
super.onAttachView(view)
|
||||||
|
view.initView()
|
||||||
|
Timber.i("Notifications centre view was initialized")
|
||||||
|
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onRetry() {
|
||||||
|
view?.run {
|
||||||
|
showErrorView(false)
|
||||||
|
showProgress(true)
|
||||||
|
}
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDetailsClick() {
|
||||||
|
view?.showErrorDetailsDialog(lastError)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadData() {
|
||||||
|
Timber.i("Loading notifications data started")
|
||||||
|
|
||||||
|
flow {
|
||||||
|
val studentId = studentRepository.getCurrentStudent(false).id
|
||||||
|
emitAll(notificationRepository.getNotifications(studentId))
|
||||||
|
}
|
||||||
|
.map { notificationList -> notificationList.sortedByDescending { it.date } }
|
||||||
|
.catch { Timber.i("Loading notifications result: An exception occurred") }
|
||||||
|
.onEach {
|
||||||
|
Timber.i("Loading notifications result: Success")
|
||||||
|
|
||||||
|
if (it.isEmpty()) {
|
||||||
|
view?.run {
|
||||||
|
showContent(false)
|
||||||
|
showProgress(false)
|
||||||
|
showEmpty(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
view?.run {
|
||||||
|
showContent(true)
|
||||||
|
showProgress(false)
|
||||||
|
showEmpty(false)
|
||||||
|
updateData(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launch()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showErrorViewOnError(message: String, error: Throwable) {
|
||||||
|
view?.run {
|
||||||
|
if (isViewEmpty) {
|
||||||
|
lastError = error
|
||||||
|
setErrorDetails(message)
|
||||||
|
showErrorView(true)
|
||||||
|
showEmpty(false)
|
||||||
|
} else showError(message, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.notificationscenter
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import io.github.wulkanowy.ui.base.BaseView
|
||||||
|
|
||||||
|
interface NotificationsCenterView : BaseView {
|
||||||
|
|
||||||
|
val isViewEmpty: Boolean
|
||||||
|
|
||||||
|
fun initView()
|
||||||
|
|
||||||
|
fun updateData(data: List<Notification>)
|
||||||
|
|
||||||
|
fun showProgress(show: Boolean)
|
||||||
|
|
||||||
|
fun showEmpty(show: Boolean)
|
||||||
|
|
||||||
|
fun showContent(show: Boolean)
|
||||||
|
|
||||||
|
fun showErrorView(show: Boolean)
|
||||||
|
|
||||||
|
fun setErrorDetails(message: String)
|
||||||
|
}
|
@ -38,7 +38,7 @@ class SchoolAnnouncementDialog : DialogFragment() {
|
|||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
) = DialogSchoolAnnouncementBinding.inflate(inflater).apply { binding = this }.root
|
) = DialogSchoolAnnouncementBinding.inflate(inflater).also { binding = it }.root
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
@ -10,9 +10,12 @@ import android.provider.Settings
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.thelittlefireman.appkillermanager.AppKillerManager
|
import com.thelittlefireman.appkillermanager.AppKillerManager
|
||||||
import com.thelittlefireman.appkillermanager.exceptions.NoActionFoundException
|
import com.thelittlefireman.appkillermanager.exceptions.NoActionFoundException
|
||||||
@ -43,6 +46,21 @@ class NotificationsFragment : PreferenceFragmentCompat(),
|
|||||||
|
|
||||||
override val titleStringId get() = R.string.pref_settings_notifications_title
|
override val titleStringId get() = R.string.pref_settings_notifications_title
|
||||||
|
|
||||||
|
override val isNotificationPermissionGranted: Boolean
|
||||||
|
get() {
|
||||||
|
val packageNameList =
|
||||||
|
NotificationManagerCompat.getEnabledListenerPackages(requireContext())
|
||||||
|
val appPackageName = requireContext().packageName
|
||||||
|
|
||||||
|
return appPackageName in packageNameList
|
||||||
|
}
|
||||||
|
|
||||||
|
private val notificationSettingsContract =
|
||||||
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
|
||||||
|
presenter.onNotificationPermissionResult()
|
||||||
|
}
|
||||||
|
|
||||||
override fun initView(showDebugNotificationSwitch: Boolean) {
|
override fun initView(showDebugNotificationSwitch: Boolean) {
|
||||||
findPreference<Preference>(getString(R.string.pref_key_notification_debug))?.isVisible =
|
findPreference<Preference>(getString(R.string.pref_key_notification_debug))?.isVisible =
|
||||||
showDebugNotificationSwitch
|
showDebugNotificationSwitch
|
||||||
@ -57,12 +75,11 @@ class NotificationsFragment : PreferenceFragmentCompat(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findPreference<Preference>(getString(R.string.pref_key_notifications_system_settings))?.run {
|
findPreference<Preference>(getString(R.string.pref_key_notifications_system_settings))
|
||||||
setOnPreferenceClickListener {
|
?.setOnPreferenceClickListener {
|
||||||
presenter.onOpenSystemSettingsClicked()
|
presenter.onOpenSystemSettingsClicked()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateRecyclerView(
|
override fun onCreateRecyclerView(
|
||||||
@ -157,6 +174,25 @@ class NotificationsFragment : PreferenceFragmentCompat(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openNotificationPermissionDialog() {
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(getString(R.string.pref_notification_piggyback_popup_title))
|
||||||
|
.setMessage(getString(R.string.pref_notification_piggyback_popup_description))
|
||||||
|
.setPositiveButton(getString(R.string.pref_notification_piggyback_popup_positive)) { _, _ ->
|
||||||
|
notificationSettingsContract.launch(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"))
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
|
setNotificationPiggybackPreferenceChecked(false)
|
||||||
|
}
|
||||||
|
.setOnDismissListener { setNotificationPiggybackPreferenceChecked(false) }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean) {
|
||||||
|
findPreference<SwitchPreferenceCompat>(getString(R.string.pref_key_notifications_piggyback))?.isChecked =
|
||||||
|
isChecked
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||||
|
@ -31,6 +31,9 @@ class NotificationsPresenter @Inject constructor(
|
|||||||
)
|
)
|
||||||
initView(appInfo.isDebug)
|
initView(appInfo.isDebug)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkNotificationPiggybackState()
|
||||||
|
|
||||||
Timber.i("Settings notifications view was initialized")
|
Timber.i("Settings notifications view was initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +42,7 @@ class NotificationsPresenter @Inject constructor(
|
|||||||
|
|
||||||
preferencesRepository.apply {
|
preferencesRepository.apply {
|
||||||
when (key) {
|
when (key) {
|
||||||
isUpcomingLessonsNotificationsEnableKey -> {
|
isUpcomingLessonsNotificationsEnableKey, isUpcomingLessonsNotificationsPersistentKey -> {
|
||||||
if (!isUpcomingLessonsNotificationsEnable) {
|
if (!isUpcomingLessonsNotificationsEnable) {
|
||||||
timetableNotificationHelper.cancelNotification()
|
timetableNotificationHelper.cancelNotification()
|
||||||
}
|
}
|
||||||
@ -47,6 +50,11 @@ class NotificationsPresenter @Inject constructor(
|
|||||||
isDebugNotificationEnableKey -> {
|
isDebugNotificationEnableKey -> {
|
||||||
chuckerCollector.showNotification = isDebugNotificationEnable
|
chuckerCollector.showNotification = isDebugNotificationEnable
|
||||||
}
|
}
|
||||||
|
isNotificationPiggybackEnabledKey -> {
|
||||||
|
if (isNotificationPiggybackEnabled && view?.isNotificationPermissionGranted == false) {
|
||||||
|
view?.openNotificationPermissionDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
analytics.logEvent("setting_changed", "name" to key)
|
analytics.logEvent("setting_changed", "name" to key)
|
||||||
@ -59,4 +67,18 @@ class NotificationsPresenter @Inject constructor(
|
|||||||
fun onOpenSystemSettingsClicked() {
|
fun onOpenSystemSettingsClicked() {
|
||||||
view?.openSystemSettings()
|
view?.openSystemSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onNotificationPermissionResult() {
|
||||||
|
view?.run {
|
||||||
|
setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkNotificationPiggybackState() {
|
||||||
|
if (preferencesRepository.isNotificationPiggybackEnabled) {
|
||||||
|
view?.run {
|
||||||
|
setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ import io.github.wulkanowy.ui.base.BaseView
|
|||||||
|
|
||||||
interface NotificationsView : BaseView {
|
interface NotificationsView : BaseView {
|
||||||
|
|
||||||
|
val isNotificationPermissionGranted: Boolean
|
||||||
|
|
||||||
fun initView(showDebugNotificationSwitch: Boolean)
|
fun initView(showDebugNotificationSwitch: Boolean)
|
||||||
|
|
||||||
fun showFixSyncDialog()
|
fun showFixSyncDialog()
|
||||||
@ -11,4 +13,8 @@ interface NotificationsView : BaseView {
|
|||||||
fun openSystemSettings()
|
fun openSystemSettings()
|
||||||
|
|
||||||
fun enableNotification(notificationKey: String, enable: Boolean)
|
fun enableNotification(notificationKey: String, enable: Boolean)
|
||||||
|
|
||||||
|
fun openNotificationPermissionDialog()
|
||||||
|
|
||||||
|
fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package io.github.wulkanowy.ui.modules.timetable
|
package io.github.wulkanowy.ui.modules.timetable
|
||||||
|
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
@ -151,8 +152,8 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
|
|
||||||
if (lesson.isStudentPlan && showTimers) {
|
if (lesson.isStudentPlan && showTimers) {
|
||||||
timers[position] = timer(period = 1000) {
|
timers[position] = timer(period = 1000) {
|
||||||
if (ViewCompat.isAttachedToWindow(root)) {
|
Handler(Looper.getMainLooper()).post {
|
||||||
root.post { updateTimeLeft(binding, lesson, position) }
|
updateTimeLeft(binding, lesson, position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -176,8 +177,8 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
|
|
||||||
private fun updateTimeLeft(binding: ItemTimetableBinding, lesson: Timetable, position: Int) {
|
private fun updateTimeLeft(binding: ItemTimetableBinding, lesson: Timetable, position: Int) {
|
||||||
val isShowTimeUntil = lesson.isShowTimeUntil(getPreviousLesson(position))
|
val isShowTimeUntil = lesson.isShowTimeUntil(getPreviousLesson(position))
|
||||||
val until = lesson.until
|
val until = lesson.until.plusMinutes(1)
|
||||||
val left = lesson.left
|
val left = lesson.left?.plusMinutes(1)
|
||||||
val isJustFinished = lesson.isJustFinished
|
val isJustFinished = lesson.isJustFinished
|
||||||
|
|
||||||
with(binding) {
|
with(binding) {
|
||||||
@ -190,17 +191,10 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
visibility = VISIBLE
|
visibility = VISIBLE
|
||||||
text = context.getString(
|
text = context.getString(
|
||||||
R.string.timetable_time_until,
|
R.string.timetable_time_until,
|
||||||
if (until.seconds <= 60) {
|
context.getString(
|
||||||
context.getString(
|
R.string.timetable_minutes,
|
||||||
R.string.timetable_seconds,
|
until.toMinutes().toString(10)
|
||||||
until.seconds.toString(10)
|
)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
context.getString(
|
|
||||||
R.string.timetable_minutes,
|
|
||||||
until.toMinutes().toString(10)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,17 +206,10 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
visibility = VISIBLE
|
visibility = VISIBLE
|
||||||
text = context.getString(
|
text = context.getString(
|
||||||
R.string.timetable_time_left,
|
R.string.timetable_time_left,
|
||||||
if (left.seconds < 60) {
|
context.getString(
|
||||||
context.getString(
|
R.string.timetable_minutes,
|
||||||
R.string.timetable_seconds,
|
left.toMinutes().toString()
|
||||||
left.seconds.toString(10)
|
)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
context.getString(
|
|
||||||
R.string.timetable_minutes,
|
|
||||||
left.toMinutes().toString(10)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -360,7 +347,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
|||||||
private fun updateTeacherColor(teacherTextView: TextView, lesson: Timetable) {
|
private fun updateTeacherColor(teacherTextView: TextView, lesson: Timetable) {
|
||||||
teacherTextView.setTextColor(
|
teacherTextView.setTextColor(
|
||||||
teacherTextView.context.getThemeAttrColor(
|
teacherTextView.context.getThemeAttrColor(
|
||||||
if (lesson.teacherOld.isNotBlank() && lesson.teacherOld != lesson.teacher) R.attr.colorTimetableChange
|
if (lesson.teacherOld.isNotBlank()) R.attr.colorTimetableChange
|
||||||
else android.R.attr.textColorSecondary
|
else android.R.attr.textColorSecondary
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -13,7 +13,6 @@ import io.github.wulkanowy.R
|
|||||||
import io.github.wulkanowy.data.db.entities.Timetable
|
import io.github.wulkanowy.data.db.entities.Timetable
|
||||||
import io.github.wulkanowy.databinding.DialogTimetableBinding
|
import io.github.wulkanowy.databinding.DialogTimetableBinding
|
||||||
import io.github.wulkanowy.utils.capitalise
|
import io.github.wulkanowy.utils.capitalise
|
||||||
import io.github.wulkanowy.utils.decapitalise
|
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
@ -52,7 +51,7 @@ class TimetableDialog : DialogFragment() {
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
with(lesson) {
|
with(lesson) {
|
||||||
setInfo(info, teacher, canceled, changes)
|
setInfo(info, canceled, changes)
|
||||||
setSubject(subject, subjectOld)
|
setSubject(subject, subjectOld)
|
||||||
setTeacher(teacher, teacherOld)
|
setTeacher(teacher, teacherOld)
|
||||||
setGroup(group)
|
setGroup(group)
|
||||||
@ -80,7 +79,7 @@ class TimetableDialog : DialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("DefaultLocale")
|
@SuppressLint("DefaultLocale")
|
||||||
private fun setInfo(info: String, teacher: String, canceled: Boolean, changes: Boolean) {
|
private fun setInfo(info: String, canceled: Boolean, changes: Boolean) {
|
||||||
with(binding) {
|
with(binding) {
|
||||||
when {
|
when {
|
||||||
info.isNotBlank() -> {
|
info.isNotBlank() -> {
|
||||||
@ -90,20 +89,26 @@ class TimetableDialog : DialogFragment() {
|
|||||||
R.attr.colorPrimary
|
R.attr.colorPrimary
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
timetableDialogChangesValue.setTextColor(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
timetableDialogChangesValue.setTextColor(
|
||||||
|
requireContext().getThemeAttrColor(
|
||||||
|
R.attr.colorPrimary
|
||||||
|
)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
timetableDialogChangesTitle.setTextColor(
|
timetableDialogChangesTitle.setTextColor(
|
||||||
requireContext().getThemeAttrColor(
|
requireContext().getThemeAttrColor(
|
||||||
R.attr.colorTimetableChange
|
R.attr.colorTimetableChange
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
timetableDialogChangesValue.setTextColor(requireContext().getThemeAttrColor(R.attr.colorTimetableChange))
|
timetableDialogChangesValue.setTextColor(
|
||||||
|
requireContext().getThemeAttrColor(
|
||||||
|
R.attr.colorTimetableChange
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
timetableDialogChangesValue.text = when {
|
timetableDialogChangesValue.text = when {
|
||||||
canceled && !changes -> "Lekcja odwołana: $info"
|
canceled && !changes -> "Lekcja odwołana: $info"
|
||||||
changes && teacher.isNotBlank() -> "Zastępstwo: $teacher"
|
|
||||||
changes && teacher.isBlank() -> "Zastępstwo, ${info.decapitalise()}"
|
|
||||||
else -> info.capitalise()
|
else -> info.capitalise()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,6 +136,15 @@ class TimetableDialog : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
teacherOld.isNotBlank() && teacherOld == teacher -> {
|
||||||
|
timetableDialogTeacherValue.run {
|
||||||
|
visibility = GONE
|
||||||
|
}
|
||||||
|
timetableDialogTeacherNewValue.run {
|
||||||
|
visibility = VISIBLE
|
||||||
|
text = teacher
|
||||||
|
}
|
||||||
|
}
|
||||||
teacher.isNotBlank() -> timetableDialogTeacherValue.text = teacher
|
teacher.isNotBlank() -> timetableDialogTeacherValue.text = teacher
|
||||||
else -> {
|
else -> {
|
||||||
timetableDialogTeacherTitle.visibility = GONE
|
timetableDialogTeacherTitle.visibility = GONE
|
||||||
|
@ -44,7 +44,13 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
|
|||||||
companion object {
|
companion object {
|
||||||
private const val SAVED_DATE_KEY = "CURRENT_DATE"
|
private const val SAVED_DATE_KEY = "CURRENT_DATE"
|
||||||
|
|
||||||
fun newInstance() = TimetableFragment()
|
private const val ARGUMENT_DATE_KEY = "ARGUMENT_DATE"
|
||||||
|
|
||||||
|
fun newInstance(date: LocalDate? = null) = TimetableFragment().apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
date?.let { putLong(ARGUMENT_DATE_KEY, it.toEpochDay()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val titleStringId get() = R.string.timetable_title
|
override val titleStringId get() = R.string.timetable_title
|
||||||
@ -62,7 +68,11 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding = FragmentTimetableBinding.bind(view)
|
binding = FragmentTimetableBinding.bind(view)
|
||||||
messageContainer = binding.timetableRecycler
|
messageContainer = binding.timetableRecycler
|
||||||
presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY))
|
|
||||||
|
val initDate = savedInstanceState?.getLong(SAVED_DATE_KEY)
|
||||||
|
?: arguments?.getLong(ARGUMENT_DATE_KEY)?.takeUnless { it == 0L }
|
||||||
|
|
||||||
|
presenter.onAttachView(this, initDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
|
@ -173,7 +173,7 @@ class TimetableWidgetFactory(
|
|||||||
updateNotCanceledLessonNumberColor(this, lesson)
|
updateNotCanceledLessonNumberColor(this, lesson)
|
||||||
updateNotCanceledSubjectColor(this, lesson)
|
updateNotCanceledSubjectColor(this, lesson)
|
||||||
|
|
||||||
val teacherChange = lesson.teacherOld.isNotBlank() && lesson.teacher != lesson.teacherOld
|
val teacherChange = lesson.teacherOld.isNotBlank()
|
||||||
updateNotCanceledRoom(this, lesson, teacherChange)
|
updateNotCanceledRoom(this, lesson, teacherChange)
|
||||||
updateNotCanceledTeacher(this, lesson, teacherChange)
|
updateNotCanceledTeacher(this, lesson, teacherChange)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ package io.github.wulkanowy.utils
|
|||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.sdk.scrapper.grades.*
|
import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid
|
||||||
|
|
||||||
fun List<Grade>.calcAverage(isOptionalArithmeticAverage: Boolean): Double {
|
fun List<Grade>.calcAverage(isOptionalArithmeticAverage: Boolean): Double {
|
||||||
val isArithmeticAverage = isOptionalArithmeticAverage && !any { it.weightValue != .0 }
|
val isArithmeticAverage = isOptionalArithmeticAverage && !any { it.weightValue != .0 }
|
||||||
@ -18,8 +18,7 @@ fun List<Grade>.calcAverage(isOptionalArithmeticAverage: Boolean): Double {
|
|||||||
return if (denominator != 0.0) counter / denominator else 0.0
|
return if (denominator != 0.0) counter / denominator else 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmName("calcSummaryAverage")
|
fun List<GradeSummary>.calcFinalAverage(plusModifier: Double, minusModifier: Double) = asSequence()
|
||||||
fun List<GradeSummary>.calcAverage(plusModifier: Double, minusModifier: Double) = asSequence()
|
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
if (it.finalGrade.matches("[0-6][+-]?".toRegex())) {
|
if (it.finalGrade.matches("[0-6][+-]?".toRegex())) {
|
||||||
when {
|
when {
|
||||||
|
@ -33,7 +33,7 @@ class AutoRefreshHelper @Inject constructor(
|
|||||||
private val sharedPref: SharedPrefProvider
|
private val sharedPref: SharedPrefProvider
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun isShouldBeRefreshed(key: String): Boolean {
|
fun shouldBeRefreshed(key: String): Boolean {
|
||||||
val timestamp = sharedPref.getLong(key, 0).toLocalDateTime()
|
val timestamp = sharedPref.getLong(key, 0).toLocalDateTime()
|
||||||
val servicesInterval = sharedPref.getString(context.getString(R.string.pref_key_services_interval), context.getString(R.string.pref_default_services_interval)).toLong()
|
val servicesInterval = sharedPref.getString(context.getString(R.string.pref_key_services_interval), context.getString(R.string.pref_default_services_interval)).toLong()
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
Wersja 1.2.2
|
Wersja 1.3.0
|
||||||
|
|
||||||
- naprawiliśmy problem z widocznością zadań w aplikacji gdy widoczne są one na stronie www dziennika (nadal pozostaje błąd z zadaniami widocznymi tylko w oficjalnej aplikacji - czekamy na poprawkę po stronie VULCANa)
|
- naprawiliśmy logowanie na platformę Opolskiej eSzkoły
|
||||||
- odblokowaliśmy niedzielę w wyborze daty w planie lekcji i innych zakładkach
|
- dodaliśmy centrum powiadomień i opcję odbierania pushy z oficjalnej aplikacji (dla zaawansowanych)
|
||||||
- przywróciliśmy odnośnik do szczęśliwego numerka w menu Więcej
|
- dodaliśmy objaśnienie do informacji o obliczonych średnich w podsumowaniu
|
||||||
- naprawiliśmy drobne błędy ze stabilnością i wyglądem
|
- poprawiliśmy wyświetlanie zmian w planie lekcji
|
||||||
|
- dokonaliśmy też kilka innych zmian i kosmetycznych poprawek poprawiających komfort używania aplikacji
|
||||||
|
|
||||||
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
|
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
<solid android:color="@android:color/white" />
|
<solid android:color="#bbffffff" />
|
||||||
<corners android:radius="5dp" />
|
<corners android:radius="8dp" />
|
||||||
</shape>
|
</shape>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
<solid android:color="@color/colorWidgetBackground" />
|
<solid android:color="#BB191919" />
|
||||||
<corners android:radius="5dp" />
|
<corners android:radius="8dp" />
|
||||||
</shape>
|
</shape>
|
||||||
|
10
app/src/main/res/drawable/ic_help.xml
Normal file
10
app/src/main/res/drawable/ic_help.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="#000000">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M11,18h2v-2h-2v2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12,6c-2.21,0 -4,1.79 -4,4h2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,2 -3,1.75 -3,5h2c0,-2.25 3,-2.5 3,-5 0,-2.21 -1.79,-4 -4,-4z"/>
|
||||||
|
</vector>
|
211
app/src/main/res/layout/dialog_conference.xml
Normal file
211
app/src/main/res/layout/dialog_conference.xml
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="24dp"
|
||||||
|
android:paddingEnd="8dp">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="280dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/allDetailsHeader"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:text="@string/all_details"
|
||||||
|
android:textSize="21sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/conferenceDialogHeaderTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginTop="28dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:text="@string/all_title"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/allDetailsHeader" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/conferenceDialogHeaderValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:paddingStart="0dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:text="@string/all_no_data"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/conferenceDialogHeaderTitle" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/conferenceDialogSubjectTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:text="@string/all_subject"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/conferenceDialogHeaderValue" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/conferenceDialogSubjectValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:paddingStart="0dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:text="@string/all_no_data"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/conferenceDialogSubjectTitle" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/conferenceDialogDateTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:text="@string/all_date"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/conferenceDialogSubjectValue" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/conferenceDialogDateValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:paddingStart="0dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:text="@string/all_no_data"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/conferenceDialogDateTitle" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/conferenceDialogPresentTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:text="@string/conferences_present"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/conferenceDialogDateValue" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/conferenceDialogPresentValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:paddingStart="0dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:text="@string/all_no_data"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/conferenceDialogPresentTitle" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/conferenceDialogAgendaTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:text="@string/conference_agenda"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/conferenceDialogPresentValue" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/conferenceDialogAgendaValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:paddingStart="0dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:text="@string/all_no_data"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/conferenceDialogAgendaTitle"
|
||||||
|
tools:maxLines="5"
|
||||||
|
tools:text="@tools:sample/lorem/random" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/conferenceDialogClose"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_marginTop="36dp"
|
||||||
|
android:layout_marginEnd="0dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:insetLeft="0dp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetRight="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
android:minWidth="88dp"
|
||||||
|
android:text="@string/all_close"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/conferenceDialogAgendaValue" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
@ -118,6 +118,7 @@
|
|||||||
android:layout_marginEnd="24dp"
|
android:layout_marginEnd="24dp"
|
||||||
android:paddingStart="0dp"
|
android:paddingStart="0dp"
|
||||||
android:paddingEnd="16dp"
|
android:paddingEnd="16dp"
|
||||||
|
tools:maxLines="5"
|
||||||
android:text="@string/all_no_data"
|
android:text="@string/all_no_data"
|
||||||
android:textIsSelectable="true"
|
android:textIsSelectable="true"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
|
@ -110,10 +110,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="32dp"
|
android:layout_marginStart="32dp"
|
||||||
android:layout_marginLeft="32dp"
|
|
||||||
android:layout_marginTop="32dp"
|
android:layout_marginTop="32dp"
|
||||||
android:layout_marginEnd="32dp"
|
android:layout_marginEnd="32dp"
|
||||||
android:layout_marginRight="32dp"
|
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:text="@string/login_header_default"
|
android:text="@string/login_header_default"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
@ -126,6 +124,20 @@
|
|||||||
app:layout_constraintVertical_chainStyle="packed"
|
app:layout_constraintVertical_chainStyle="packed"
|
||||||
app:layout_goneMarginTop="64dp" />
|
app:layout_goneMarginTop="64dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loginFormErrorBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:layout_marginEnd="32dp"
|
||||||
|
android:textAppearance="?attr/textAppearanceCaption"
|
||||||
|
android:textColor="@color/mtrl_error"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/loginFormHeader"
|
||||||
|
tools:text="Nazwa użytkownika lub hasło są niepoprawne albo hasło do konta wygasło"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/loginFormUsernameLayout"
|
android:id="@+id/loginFormUsernameLayout"
|
||||||
@ -134,15 +146,15 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="24dp"
|
android:layout_marginStart="24dp"
|
||||||
android:layout_marginLeft="24dp"
|
android:layout_marginLeft="24dp"
|
||||||
android:layout_marginTop="48dp"
|
android:layout_marginTop="28dp"
|
||||||
android:layout_marginEnd="24dp"
|
android:layout_marginEnd="24dp"
|
||||||
android:layout_marginRight="24dp"
|
android:layout_marginRight="24dp"
|
||||||
android:hint="@string/login_nickname_hint"
|
android:hint="@string/login_nickname_hint"
|
||||||
app:errorEnabled="true"
|
app:errorEnabled="true"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/loginFormPassLayout"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/loginFormHeader">
|
app:layout_constraintTop_toBottomOf="@+id/loginFormErrorBox"
|
||||||
|
app:layout_goneMarginTop="48dp">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/loginFormUsername"
|
android:id="@+id/loginFormUsername"
|
||||||
@ -170,7 +182,6 @@
|
|||||||
android:hint="@string/login_password_hint"
|
android:hint="@string/login_password_hint"
|
||||||
app:errorEnabled="true"
|
app:errorEnabled="true"
|
||||||
app:errorIconDrawable="@null"
|
app:errorIconDrawable="@null"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/loginFormRecoverLink"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/loginFormUsernameLayout"
|
app:layout_constraintTop_toBottomOf="@+id/loginFormUsernameLayout"
|
||||||
@ -200,7 +211,6 @@
|
|||||||
android:textAppearance="?android:textAppearance"
|
android:textAppearance="?android:textAppearance"
|
||||||
app:backgroundTint="?android:windowBackground"
|
app:backgroundTint="?android:windowBackground"
|
||||||
app:fontFamily="sans-serif-medium"
|
app:fontFamily="sans-serif-medium"
|
||||||
app:layout_constraintBottom_toTopOf="@id/loginFormHostLayout"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/loginFormPassLayout"
|
app:layout_constraintTop_toBottomOf="@+id/loginFormPassLayout"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
@ -217,7 +227,6 @@
|
|||||||
android:layout_marginRight="24dp"
|
android:layout_marginRight="24dp"
|
||||||
android:hint="@string/login_host_hint"
|
android:hint="@string/login_host_hint"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/loginFormAdvancedButton"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/loginFormRecoverLink">
|
app:layout_constraintTop_toBottomOf="@+id/loginFormRecoverLink">
|
||||||
@ -262,14 +271,13 @@
|
|||||||
android:id="@+id/loginFormPrivacyLink"
|
android:id="@+id/loginFormPrivacyLink"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="24dp"
|
|
||||||
android:gravity="start|center_vertical"
|
android:gravity="start|center_vertical"
|
||||||
android:text="@string/login_privacy_policy"
|
android:text="@string/login_privacy_policy"
|
||||||
android:textColor="?android:textColorSecondary"
|
android:textColor="?android:textColorSecondary"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
app:fontFamily="sans-serif-medium"
|
app:fontFamily="sans-serif-medium"
|
||||||
app:layout_constraintStart_toStartOf="@id/loginFormAdvancedButton"
|
app:layout_constraintStart_toStartOf="@id/loginFormAdvancedButton"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/loginFormAdvancedButton"
|
app:layout_constraintTop_toTopOf="@+id/loginFormVersion"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
android:id="@+id/messageTabRecycler"
|
android:id="@+id/messageTabRecycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingBottom="64dp"
|
||||||
tools:listitem="@layout/item_message" />
|
tools:listitem="@layout/item_message" />
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
|
106
app/src/main/res/layout/fragment_notifications_center.xml
Normal file
106
app/src/main/res/layout/fragment_notifications_center.xml
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/notifications_center_recycler"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:itemCount="4"
|
||||||
|
tools:listitem="@layout/item_notifications_center"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
android:id="@+id/notifications_center_progress"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/notifications_center_empty"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
app:srcCompat="@drawable/ic_settings_notifications"
|
||||||
|
app:tint="?colorOnBackground"
|
||||||
|
tools:ignore="contentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/all_no_data"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/notification_center_error"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="invisible"
|
||||||
|
tools:ignore="UseCompoundDrawables"
|
||||||
|
tools:visibility="invisible">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
app:srcCompat="@drawable/ic_error"
|
||||||
|
app:tint="?colorOnBackground"
|
||||||
|
tools:ignore="contentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notification_center_error_message"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/error_unknown"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/notification_center_error_details"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:text="@string/all_details" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/notification_center_error_retry"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/all_retry" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user