forked from github/wulkanowy-mirror
Merge branch 'release/1.3.0'
This commit is contained in:
commit
689012131f
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 96
|
versionCode 97
|
||||||
versionName "1.2.3"
|
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.3"
|
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"
|
||||||
@ -215,7 +224,7 @@ dependencies {
|
|||||||
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
@ -120,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,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
|
||||||
|
@ -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)
|
||||||
|
}
|
@ -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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -89,14 +89,22 @@ 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 {
|
||||||
@ -128,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
|
||||||
|
@ -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.3
|
Wersja 1.3.0
|
||||||
|
|
||||||
- naprawiliśmy pomieszane imiona nauczycieli z salami w planie lekcji
|
- naprawiliśmy logowanie na platformę Opolskiej eSzkoły
|
||||||
- dodaliśmy brakujące okienka ze szczegółami na ekranie zebrań
|
- dodaliśmy centrum powiadomień i opcję odbierania pushy z oficjalnej aplikacji (dla zaawansowanych)
|
||||||
- klikając w kafelek z lekcjami na jutro aplikacja teraz przekierowuje na ekran z planem na jutro
|
- dodaliśmy objaśnienie do informacji o obliczonych średnich w podsumowaniu
|
||||||
- naprawiliśmy błąd przy wylogowywaniu innego niż bieżący uczeń
|
- 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>
|
@ -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>
|
70
app/src/main/res/layout/item_notifications_center.xml
Normal file
70
app/src/main/res/layout/item_notifications_center.xml
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.card.MaterialCardView 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="wrap_content"
|
||||||
|
android:layout_marginHorizontal="8dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
app:cardElevation="4dp">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notifications_center_item_date"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/notifications_center_item_icon"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="@tools:sample/date/ddmmyy" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notifications_center_item_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/notifications_center_item_icon"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/notifications_center_item_date"
|
||||||
|
tools:text="@tools:sample/lorem" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notifications_center_item_content"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="5"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/notifications_center_item_icon"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/notifications_center_item_title"
|
||||||
|
tools:text="@tools:sample/lorem/random" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/notifications_center_item_icon"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:tint="?colorPrimary"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout 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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -12,7 +13,7 @@
|
|||||||
android:layout_width="150dp"
|
android:layout_width="150dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="20dp"
|
android:layout_marginTop="20dp"
|
||||||
android:layout_marginEnd="5dp"
|
android:layout_marginEnd="15dp"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -24,13 +25,30 @@
|
|||||||
android:text="@string/grade_summary_calculated_average"
|
android:text="@string/grade_summary_calculated_average"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/gradeSummaryScrollableHeaderCalculated"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_horizontal"
|
android:layout_gravity="center"
|
||||||
android:textSize="21sp"
|
android:orientation="horizontal">
|
||||||
tools:text="6,00" />
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/gradeSummaryScrollableHeaderCalculated"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="21sp"
|
||||||
|
tools:text="6,00" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/gradeSummaryCalculatedAverageHelp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@+string/grade_summary_calculated_average_help_dialog_title"
|
||||||
|
app:srcCompat="@drawable/ic_help"
|
||||||
|
app:tint="?colorOnBackground" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/gradeSummaryScrollableHeaderCalculatedSubjectCount"
|
android:id="@+id/gradeSummaryScrollableHeaderCalculatedSubjectCount"
|
||||||
@ -57,13 +75,30 @@
|
|||||||
android:text="@string/grade_summary_final_average"
|
android:text="@string/grade_summary_final_average"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/gradeSummaryScrollableHeaderFinal"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_horizontal"
|
android:layout_gravity="center"
|
||||||
android:textSize="21sp"
|
android:orientation="horizontal">
|
||||||
tools:text="6,00" />
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/gradeSummaryScrollableHeaderFinal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:textSize="21sp"
|
||||||
|
tools:text="6,00" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/gradeSummaryFinalAverageHelp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@+string/grade_summary_calculated_average_help_dialog_title"
|
||||||
|
app:srcCompat="@drawable/ic_help"
|
||||||
|
app:tint="?colorOnBackground" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/gradeSummaryScrollableHeaderFinalSubjectCount"
|
android:id="@+id/gradeSummaryScrollableHeaderFinalSubjectCount"
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/dashboard_menu_notifaction_list"
|
||||||
|
android:icon="@drawable/ic_settings_notifications"
|
||||||
|
android:orderInCategory="1"
|
||||||
|
android:title="@string/notifications_center_title"
|
||||||
|
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/dashboard_menu_tiles"
|
android:id="@+id/dashboard_menu_tiles"
|
||||||
android:icon="@drawable/ic_more_settings"
|
android:icon="@drawable/ic_more_settings"
|
||||||
android:orderInCategory="1"
|
android:orderInCategory="2"
|
||||||
android:title="@string/pref_dashboard_appearance_tiles_title"
|
android:title="@string/pref_dashboard_appearance_tiles_title"
|
||||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/mainMenuAccount"
|
android:id="@+id/mainMenuAccount"
|
||||||
android:orderInCategory="2"
|
android:orderInCategory="10"
|
||||||
app:showAsAction="always"
|
app:showAsAction="always"
|
||||||
tools:ignore="MenuTitle" />
|
tools:ignore="MenuTitle" />
|
||||||
</menu>
|
</menu>
|
||||||
|
@ -41,8 +41,8 @@
|
|||||||
<item>Barvy známek v deníku</item>
|
<item>Barvy známek v deníku</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="grade_average_mode_entries">
|
<string-array name="grade_average_mode_entries">
|
||||||
<item>Průměrná známka od druhého semestru</item>
|
<item>Průměr známek pouze z vybraného semestru</item>
|
||||||
<item>Průměr známek z obou semestrů</item>
|
<item>Průměr z průměrů z obou semestrů</item>
|
||||||
<item>Průměr známek z celého roku</item>
|
<item>Průměr známek z celého roku</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="dashboard_tile_entries">
|
<string-array name="dashboard_tile_entries">
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<string name="about_title">O aplikaci</string>
|
<string name="about_title">O aplikaci</string>
|
||||||
<string name="logviewer_title">Prohlížeč protokolů</string>
|
<string name="logviewer_title">Prohlížeč protokolů</string>
|
||||||
<string name="debug_title">Ladění</string>
|
<string name="debug_title">Ladění</string>
|
||||||
<string name="notification_debug_title">Ladění oznámení</string>
|
<string name="notification_debug_title">Ladění upozornění</string>
|
||||||
<string name="contributors_title">Tvůrci</string>
|
<string name="contributors_title">Tvůrci</string>
|
||||||
<string name="license_title">Licence</string>
|
<string name="license_title">Licence</string>
|
||||||
<string name="message_title">Zprávy</string>
|
<string name="message_title">Zprávy</string>
|
||||||
@ -24,6 +24,7 @@
|
|||||||
<string name="account_details_title">Podrobnosti účtu</string>
|
<string name="account_details_title">Podrobnosti účtu</string>
|
||||||
<string name="student_info_title">Informace o žáku</string>
|
<string name="student_info_title">Informace o žáku</string>
|
||||||
<string name="dashboard_title">Domů</string>
|
<string name="dashboard_title">Domů</string>
|
||||||
|
<string name="notifications_center_title">Centrum upozornění</string>
|
||||||
<!--Subtitles-->
|
<!--Subtitles-->
|
||||||
<string name="grade_subtitle">Semestr %1$d, %2$d/%3$d</string>
|
<string name="grade_subtitle">Semestr %1$d, %2$d/%3$d</string>
|
||||||
<!--Login-->
|
<!--Login-->
|
||||||
@ -42,7 +43,8 @@
|
|||||||
<string name="login_symbol_hint">Symbol</string>
|
<string name="login_symbol_hint">Symbol</string>
|
||||||
<string name="login_sign_in">Přihlásit</string>
|
<string name="login_sign_in">Přihlásit</string>
|
||||||
<string name="login_invalid_password">Toto heslo je příliš krátké</string>
|
<string name="login_invalid_password">Toto heslo je příliš krátké</string>
|
||||||
<string name="login_incorrect_password">Přihlašovací údaje jsou nesprávné. Ujistěte se, že je v poli níže vybrána správná variace deníku UONET+</string>
|
<string name="login_incorrect_password_default">Přihlašovací údaje jsou nesprávné</string>
|
||||||
|
<string name="login_incorrect_password">%1$s. Zkontrolujte, zda je níže vybrána správná variace deníku UONET+</string>
|
||||||
<string name="login_invalid_pin">Neplatný PIN</string>
|
<string name="login_invalid_pin">Neplatný PIN</string>
|
||||||
<string name="login_invalid_token">Neplatný token</string>
|
<string name="login_invalid_token">Neplatný token</string>
|
||||||
<string name="login_expired_token">Token vypršel</string>
|
<string name="login_expired_token">Token vypršel</string>
|
||||||
@ -90,6 +92,10 @@
|
|||||||
<string name="grade_summary_final_grade">Konečná známka</string>
|
<string name="grade_summary_final_grade">Konečná známka</string>
|
||||||
<string name="grade_summary_predicted_grade">Předpokládaná známka</string>
|
<string name="grade_summary_predicted_grade">Předpokládaná známka</string>
|
||||||
<string name="grade_summary_calculated_average">Vypočítaný průměr</string>
|
<string name="grade_summary_calculated_average">Vypočítaný průměr</string>
|
||||||
|
<string name="grade_summary_calculated_average_help_dialog_title">Jak funguje vypočítaný průměr?</string>
|
||||||
|
<string name="grade_summary_calculated_average_help_dialog_message">Vypočítaný průměr je aritmetický průměr vypočítaný z průměrů předmětů. Umožňuje vám to znát přibližný konečný průměr. Vypočítává se způsobem zvoleným uživatelem v nastavení aplikaci. Doporučuje se vybrat příslušnou možnost. Důvodem je rozdílný výpočet školních průměrů. Pokud vaše škola navíc uvádí průměr předmětů na stránce deníku Vulcan, aplikace si je stáhne a tyto průměry nepočítá. To lze změnit vynucením výpočtu průměru v nastavení aplikaci.\n\n<b>Průměr známek pouze z vybraného semestru</b>:\n1. Výpočet váženého průměru pro každý předmět v daném semestru\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů\n\n<b>Průměr průměrů z obou semestrů</b>:\n1. Výpočet váženého průměru pro každý předmět v semestru 1 a 2\n2. Výpočet aritmetického průměru vypočítaných průměrů za semestry 1 a 2 pro každý předmět.\n3. Sčítání vypočítaných průměrů\n4. Výpočet aritmetického průměru součtených průměrů\n\n<b>Průměr známek z celého roku:</b>\n1. Výpočet váženého průměru za rok pro každý předmět. Konečný průměr v 1. semestru je nepodstatný.\n3. Sčítání vypočítaných průměrů\n4. Výpočet aritmetického průměru součtených průměrů</string>
|
||||||
|
<string name="grade_summary_final_average_help_dialog_title">Jak funguje konečný průměr?</string>
|
||||||
|
<string name="grade_summary_final_average_help_dialog_message">Konečný průměr je aritmetický průměr vypočítaný ze všech aktuálně dostupných konečných známek v daném semestru.\n\nSchéma výpočtu se skládá z následujících kroků:\n1. Sčítání konečných známek zadaných učiteli\n2. Děleno počtem předmětů, pro které už byly vydány známky</string>
|
||||||
<string name="grade_summary_final_average">Konečný průměr</string>
|
<string name="grade_summary_final_average">Konečný průměr</string>
|
||||||
<string name="grade_summary_from_subjects">z %1$d z %2$d předmětů</string>
|
<string name="grade_summary_from_subjects">z %1$d z %2$d předmětů</string>
|
||||||
<string name="grade_menu_summary">Shrnutí</string>
|
<string name="grade_menu_summary">Shrnutí</string>
|
||||||
@ -238,12 +244,7 @@
|
|||||||
<string name="message_chip_only_unread">Pouze nepřečtené</string>
|
<string name="message_chip_only_unread">Pouze nepřečtené</string>
|
||||||
<string name="message_chip_only_with_attachments">Pouze s přílohami</string>
|
<string name="message_chip_only_with_attachments">Pouze s přílohami</string>
|
||||||
<string name="message_read">Přečtena: %s</string>
|
<string name="message_read">Přečtena: %s</string>
|
||||||
<plurals name="message_read_by">
|
<string name="message_read_by">Přečtena přes: %1$d z %2$d osob</string>
|
||||||
<item quantity="one">Přečtena přes: %1$d z %2$d osob</item>
|
|
||||||
<item quantity="few">Přečtena přes: %1$d z %2$d osob</item>
|
|
||||||
<item quantity="many">Přečtena přes: %1$d z %2$d osob</item>
|
|
||||||
<item quantity="other">Přečtena přes: %1$d z %2$d osob</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="message_number_item">
|
<plurals name="message_number_item">
|
||||||
<item quantity="one">%d zpráva</item>
|
<item quantity="one">%d zpráva</item>
|
||||||
<item quantity="few">%d zprávy</item>
|
<item quantity="few">%d zprávy</item>
|
||||||
@ -402,7 +403,7 @@
|
|||||||
<item quantity="many">Máte %1$d nových setkání</item>
|
<item quantity="many">Máte %1$d nových setkání</item>
|
||||||
<item quantity="other">Máte %1$d nových setkání</item>
|
<item quantity="other">Máte %1$d nových setkání</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="conferences_present">Present at conference</string>
|
<string name="conferences_present">Přítomnost na setkání</string>
|
||||||
<string name="conference_agenda">Agenda</string>
|
<string name="conference_agenda">Agenda</string>
|
||||||
<!--Director information-->
|
<!--Director information-->
|
||||||
<string name="school_announcement_title">Školní oznámení</string>
|
<string name="school_announcement_title">Školní oznámení</string>
|
||||||
@ -592,7 +593,7 @@
|
|||||||
<string name="all_yes">Ano</string>
|
<string name="all_yes">Ano</string>
|
||||||
<string name="all_no">Ne</string>
|
<string name="all_no">Ne</string>
|
||||||
<string name="all_save">Uložit</string>
|
<string name="all_save">Uložit</string>
|
||||||
<string name="all_title">Title</string>
|
<string name="all_title">Titul</string>
|
||||||
<!--Timetable Widget-->
|
<!--Timetable Widget-->
|
||||||
<string name="widget_timetable_no_items">Žádné lekce</string>
|
<string name="widget_timetable_no_items">Žádné lekce</string>
|
||||||
<string name="widget_timetable_theme_title">Vybrat motiv</string>
|
<string name="widget_timetable_theme_title">Vybrat motiv</string>
|
||||||
@ -602,7 +603,7 @@
|
|||||||
<!--Preferences-->
|
<!--Preferences-->
|
||||||
<string name="pref_view_header">Vzhled a chování aplikací</string>
|
<string name="pref_view_header">Vzhled a chování aplikací</string>
|
||||||
<string name="pref_view_list">Výchozí zobrazení</string>
|
<string name="pref_view_list">Výchozí zobrazení</string>
|
||||||
<string name="pref_view_grade_average_mode">Výpočet koncoročního průměru</string>
|
<string name="pref_view_grade_average_mode">Možnosti vypočítaného průměru</string>
|
||||||
<string name="pref_view_grade_average_force_calc">Vynutit průměrný výpočet podle aplikace</string>
|
<string name="pref_view_grade_average_force_calc">Vynutit průměrný výpočet podle aplikace</string>
|
||||||
<string name="pref_view_present">Zobrazit přítomnost</string>
|
<string name="pref_view_present">Zobrazit přítomnost</string>
|
||||||
<string name="pref_view_app_theme">Motiv</string>
|
<string name="pref_view_app_theme">Motiv</string>
|
||||||
@ -617,12 +618,18 @@
|
|||||||
<string name="pref_notify_header">Upozornění</string>
|
<string name="pref_notify_header">Upozornění</string>
|
||||||
<string name="pref_notify_switch">Zobrazit upozornění</string>
|
<string name="pref_notify_switch">Zobrazit upozornění</string>
|
||||||
<string name="pref_notify_upcoming_lessons_switch">Zobrazit upozornění o nadcházející lekci</string>
|
<string name="pref_notify_upcoming_lessons_switch">Zobrazit upozornění o nadcházející lekci</string>
|
||||||
|
<string name="pref_notify_upcoming_lessons_persistent_switch">Nastavit upozornění o nadcházející lekci jako trvalé</string>
|
||||||
|
<string name="pref_notify_upcoming_lessons_persistent_summary">Vypnout, když upozornění není ve vašem hodinkách/náramku viditelné</string>
|
||||||
<string name="pref_notify_open_system_settings">Otevřít systémová nastavení upozornění</string>
|
<string name="pref_notify_open_system_settings">Otevřít systémová nastavení upozornění</string>
|
||||||
<string name="pref_notify_fix_sync_issues">Opravte problémy se synchronizací a upozorněním</string>
|
<string name="pref_notify_fix_sync_issues">Opravte problémy se synchronizací a upozorněním</string>
|
||||||
<string name="pref_notify_fix_sync_issues_message">Vaše zařízení může mít problémy se synchronizací dat as upozorněními.\n\nChcete-li je opravit, přidejte Wulkanového do funkce Autostart a vypněte optimalizaci/úsporu baterie v nastavení systému telefonu.</string>
|
<string name="pref_notify_fix_sync_issues_message">Vaše zařízení může mít problémy se synchronizací dat as upozorněními.\n\nChcete-li je opravit, přidejte Wulkanového do funkce Autostart a vypněte optimalizaci/úsporu baterie v nastavení systému telefonu.</string>
|
||||||
<string name="pref_notify_fix_sync_issues_settings_button">Přejít do nastavení</string>
|
<string name="pref_notify_fix_sync_issues_settings_button">Přejít do nastavení</string>
|
||||||
<string name="pref_notify_debug_switch">Zobrazit upozornění o ladění</string>
|
<string name="pref_notify_debug_switch">Zobrazit upozornění o ladění</string>
|
||||||
<string name="pref_notify_disabled_summary">Synchronizace je vypnutá</string>
|
<string name="pref_notify_disabled_summary">Synchronizace je vypnutá</string>
|
||||||
|
<string name="pref_notify_notifications_piggyback">Zachytit upozornění oficiální aplikací</string>
|
||||||
|
<string name="pref_notification_piggyback_popup_title">Zachytit upozornění</string>
|
||||||
|
<string name="pref_notification_piggyback_popup_description">S touto funkcí můžete získat náhradu push upozornění jako v oficiální aplikaci. Vše, co musíte udělat, je povolit Wulkanowému číst všechna vaše upozornění v nastaveních systému.\n\nJak to funguje?\nKdyž obdržíte oznámení v Deníčku VULCAN, Wulkanowy bude o tom informován (k tomu je to dodatečné povolení) a spustí synchronizaci, aby mohl zaslat vlastní upozornění.\n\nPOUZE PRO POKROČILÉ UŽIVATELE</string>
|
||||||
|
<string name="pref_notification_piggyback_popup_positive">Přejít do nastavení</string>
|
||||||
<string name="pref_services_header">Synchronizace</string>
|
<string name="pref_services_header">Synchronizace</string>
|
||||||
<string name="pref_services_switch">Automatická aktualizace</string>
|
<string name="pref_services_switch">Automatická aktualizace</string>
|
||||||
<string name="pref_services_suspended">Pozastaveno na dovolené</string>
|
<string name="pref_services_suspended">Pozastaveno na dovolené</string>
|
||||||
|
@ -41,8 +41,8 @@
|
|||||||
<item>Farben der Bewertungen im Logbuch</item>
|
<item>Farben der Bewertungen im Logbuch</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="grade_average_mode_entries">
|
<string-array name="grade_average_mode_entries">
|
||||||
<item>Durchschnittsnote für das 2. Semester</item>
|
|
||||||
<item>Durchschnitt der Noten aus beiden Semestern</item>
|
<item>Durchschnitt der Noten aus beiden Semestern</item>
|
||||||
|
<item>Durchschnittswert der Durchschnittswerte beider Semester</item>
|
||||||
<item>Durchschnitt der Noten aus dem ganzen Jahr</item>
|
<item>Durchschnitt der Noten aus dem ganzen Jahr</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="dashboard_tile_entries">
|
<string-array name="dashboard_tile_entries">
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
<string name="account_details_title">Kontodetails</string>
|
<string name="account_details_title">Kontodetails</string>
|
||||||
<string name="student_info_title">Schülerinfo</string>
|
<string name="student_info_title">Schülerinfo</string>
|
||||||
<string name="dashboard_title">Übersicht</string>
|
<string name="dashboard_title">Übersicht</string>
|
||||||
|
<string name="notifications_center_title">Benachrichtigungszentrum</string>
|
||||||
<!--Subtitles-->
|
<!--Subtitles-->
|
||||||
<string name="grade_subtitle">Semester %1$d, %2$d/%3$d</string>
|
<string name="grade_subtitle">Semester %1$d, %2$d/%3$d</string>
|
||||||
<!--Login-->
|
<!--Login-->
|
||||||
@ -42,7 +43,8 @@
|
|||||||
<string name="login_symbol_hint">Symbol</string>
|
<string name="login_symbol_hint">Symbol</string>
|
||||||
<string name="login_sign_in">Anmelden</string>
|
<string name="login_sign_in">Anmelden</string>
|
||||||
<string name="login_invalid_password">Passwort ist zu kurz</string>
|
<string name="login_invalid_password">Passwort ist zu kurz</string>
|
||||||
<string name="login_incorrect_password">Anmeldedaten sind falsch. Stellen Sie sicher, dass die richtige UONET+ Registervariation im unteren Feld ausgewählt ist</string>
|
<string name="login_incorrect_password_default">Anmeldedaten sind falsch</string>
|
||||||
|
<string name="login_incorrect_password">%1$s. Stellen Sie sicher, dass die korrekte UONET+ Registervariation unten ausgewählt ist</string>
|
||||||
<string name="login_invalid_pin">Ungültige PIN</string>
|
<string name="login_invalid_pin">Ungültige PIN</string>
|
||||||
<string name="login_invalid_token">Ungültige token</string>
|
<string name="login_invalid_token">Ungültige token</string>
|
||||||
<string name="login_expired_token">Token ist nicht mehr gültig</string>
|
<string name="login_expired_token">Token ist nicht mehr gültig</string>
|
||||||
@ -90,6 +92,10 @@
|
|||||||
<string name="grade_summary_final_grade">Finaler Note</string>
|
<string name="grade_summary_final_grade">Finaler Note</string>
|
||||||
<string name="grade_summary_predicted_grade">Vorhergesagte Note</string>
|
<string name="grade_summary_predicted_grade">Vorhergesagte Note</string>
|
||||||
<string name="grade_summary_calculated_average">Berechnender Durchschnitt</string>
|
<string name="grade_summary_calculated_average">Berechnender Durchschnitt</string>
|
||||||
|
<string name="grade_summary_calculated_average_help_dialog_title">Wie funktioniert der berechnete Durchschnitt?</string>
|
||||||
|
<string name="grade_summary_calculated_average_help_dialog_message">The Calculated Average is the arithmetic average calculated from the subjects averages. It allows you to know the approximate final average. It is calculated in a way selected by the user in the application settings. It is recommended that you choose the appropriate option. This is because the calculation of school averages differs. Additionally, if your school reports the average of the subjects on the Vulcan page, the application downloads them and does not calculate these averages. This can be changed by forcing the calculation of the average in the application settings.\n\n<b>Average of grades only from selected semester</b>:\n1. Calculating the weighted average for each subject in a given semester\n2.Adding calculated averages\n3. Calculation of the arithmetic average of the summed averages\n\n<b>Average of averages from both semesters</b>:\n1.Calculating the weighted average for each subject in semester 1 and 2\n2. Calculating the arithmetic average of the calculated averages for semesters 1 and 2 for each subject.\n3. Adding calculated averages\n4. Calculation of the arithmetic average of the summed averages\n\n<b>Average of grades from the whole year:</b>\n1. Calculating weighted average over the year for each subject. The final average in the 1st semester is irrelevant.\n3. Adding calculated averages\n4. Calculating the arithmetic average of summed averages</string>
|
||||||
|
<string name="grade_summary_final_average_help_dialog_title">Wie funktioniert der endgültige Durchschnitt?</string>
|
||||||
|
<string name="grade_summary_final_average_help_dialog_message">The Final Average is the arithmetic average calculated from all currently available final grades in the given semester.\n\nThe calculation scheme consists of the following steps:\n1. Summing up the final grades given by teachers\n2. Divide by the number of subjects that have already been graded</string>
|
||||||
<string name="grade_summary_final_average">Finaler Durchschnitt</string>
|
<string name="grade_summary_final_average">Finaler Durchschnitt</string>
|
||||||
<string name="grade_summary_from_subjects">aus %1$d von %2$d Schulfächern</string>
|
<string name="grade_summary_from_subjects">aus %1$d von %2$d Schulfächern</string>
|
||||||
<string name="grade_menu_summary">Zusammenfassung</string>
|
<string name="grade_menu_summary">Zusammenfassung</string>
|
||||||
@ -155,7 +161,7 @@
|
|||||||
<!--Additional lessons-->
|
<!--Additional lessons-->
|
||||||
<string name="additional_lessons_title">Zusätzliche Lektionen</string>
|
<string name="additional_lessons_title">Zusätzliche Lektionen</string>
|
||||||
<string name="additional_lessons_button">Zusätzliche Lektionen anzeigen</string>
|
<string name="additional_lessons_button">Zusätzliche Lektionen anzeigen</string>
|
||||||
<string name="additional_lessons_no_items">Keine Infos zu zusätzlichen Lektionen</string>
|
<string name="additional_lessons_no_items">Keine Informationen über zusätzlichen Lektionen</string>
|
||||||
<!--Attendance-->
|
<!--Attendance-->
|
||||||
<string name="attendance_summary_button">Übersicht über die Schulbesuch</string>
|
<string name="attendance_summary_button">Übersicht über die Schulbesuch</string>
|
||||||
<string name="attendance_absence_school">Aus schulischen Gründen abwesend</string>
|
<string name="attendance_absence_school">Aus schulischen Gründen abwesend</string>
|
||||||
@ -185,8 +191,8 @@
|
|||||||
<item quantity="other">Neue prüfungen</item>
|
<item quantity="other">Neue prüfungen</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="exam_notify_new_item_content">
|
<plurals name="exam_notify_new_item_content">
|
||||||
<item quantity="one">Du hast %d neue Prüfung erhalten</item>
|
<item quantity="one">Du hast %d neue Prüfung</item>
|
||||||
<item quantity="other">Sie haben %d neue Prüfungen erhalten</item>
|
<item quantity="other">Du hast %d neue Prüfungen</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="exam_number_item">
|
<plurals name="exam_number_item">
|
||||||
<item quantity="one">%d prüfung</item>
|
<item quantity="one">%d prüfung</item>
|
||||||
@ -212,16 +218,13 @@
|
|||||||
<string name="message_subject">Thema</string>
|
<string name="message_subject">Thema</string>
|
||||||
<string name="message_content">Inhalt</string>
|
<string name="message_content">Inhalt</string>
|
||||||
<string name="message_send_successful">Nachricht erfolgreich gesendet</string>
|
<string name="message_send_successful">Nachricht erfolgreich gesendet</string>
|
||||||
<string name="message_not_exists">Nachricht existiert nicht</string>
|
<string name="message_not_exists">Nachricht nicht vorhanden</string>
|
||||||
<string name="message_required_recipients">Sie müssen mindestens 1 Empfänger auswählen.</string>
|
<string name="message_required_recipients">Sie müssen mindestens 1 Empfänger auswählen.</string>
|
||||||
<string name="message_content_min_length">Der Inhalt der Nachricht muss mindestens 3 Zeichen lang sein.</string>
|
<string name="message_content_min_length">Der Inhalt der Nachricht muss mindestens 3 Zeichen lang sein.</string>
|
||||||
<string name="message_chip_only_unread">Nur ungelesen</string>
|
<string name="message_chip_only_unread">Nur ungelesen</string>
|
||||||
<string name="message_chip_only_with_attachments">Nur mit Anhängen</string>
|
<string name="message_chip_only_with_attachments">Nur mit Anhängen</string>
|
||||||
<string name="message_read">Lesen: %s</string>
|
<string name="message_read">Lesen: %s</string>
|
||||||
<plurals name="message_read_by">
|
<string name="message_read_by">Lesen von: %1$d von %2$d Personen</string>
|
||||||
<item quantity="one">Lesen von: %1$d von %2$d Personen</item>
|
|
||||||
<item quantity="other">Lesen von: %1$d von %2$d Personen</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="message_number_item">
|
<plurals name="message_number_item">
|
||||||
<item quantity="one">%d nachricht</item>
|
<item quantity="one">%d nachricht</item>
|
||||||
<item quantity="other">%d nachrichten</item>
|
<item quantity="other">%d nachrichten</item>
|
||||||
@ -344,7 +347,7 @@
|
|||||||
<item quantity="one">Sie haben %1$d neue konferenz</item>
|
<item quantity="one">Sie haben %1$d neue konferenz</item>
|
||||||
<item quantity="other">Sie haben %1$d neue konferenzen</item>
|
<item quantity="other">Sie haben %1$d neue konferenzen</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="conferences_present">Present at conference</string>
|
<string name="conferences_present">Teilnahme an einem Meeting</string>
|
||||||
<string name="conference_agenda">Agenda</string>
|
<string name="conference_agenda">Agenda</string>
|
||||||
<!--Director information-->
|
<!--Director information-->
|
||||||
<string name="school_announcement_title">Schulankündigungen</string>
|
<string name="school_announcement_title">Schulankündigungen</string>
|
||||||
@ -514,7 +517,7 @@
|
|||||||
<string name="all_yes">Ja</string>
|
<string name="all_yes">Ja</string>
|
||||||
<string name="all_no">Nein</string>
|
<string name="all_no">Nein</string>
|
||||||
<string name="all_save">Speichern</string>
|
<string name="all_save">Speichern</string>
|
||||||
<string name="all_title">Title</string>
|
<string name="all_title">Titel</string>
|
||||||
<!--Timetable Widget-->
|
<!--Timetable Widget-->
|
||||||
<string name="widget_timetable_no_items">Keine Lektionen</string>
|
<string name="widget_timetable_no_items">Keine Lektionen</string>
|
||||||
<string name="widget_timetable_theme_title">Thema wählen</string>
|
<string name="widget_timetable_theme_title">Thema wählen</string>
|
||||||
@ -524,7 +527,7 @@
|
|||||||
<!--Preferences-->
|
<!--Preferences-->
|
||||||
<string name="pref_view_header">Aussehen & Verhalten</string>
|
<string name="pref_view_header">Aussehen & Verhalten</string>
|
||||||
<string name="pref_view_list">Standard Ansicht</string>
|
<string name="pref_view_list">Standard Ansicht</string>
|
||||||
<string name="pref_view_grade_average_mode">Berechnung des Jahresenddurchschnitts</string>
|
<string name="pref_view_grade_average_mode">Berechnete Durchschnittsoptionen</string>
|
||||||
<string name="pref_view_grade_average_force_calc">Mittelwertberechnung durch App erzwingen</string>
|
<string name="pref_view_grade_average_force_calc">Mittelwertberechnung durch App erzwingen</string>
|
||||||
<string name="pref_view_present">Anwesendheit zeigen</string>
|
<string name="pref_view_present">Anwesendheit zeigen</string>
|
||||||
<string name="pref_view_app_theme">Thema</string>
|
<string name="pref_view_app_theme">Thema</string>
|
||||||
@ -539,12 +542,18 @@
|
|||||||
<string name="pref_notify_header">Benachrichtigungen</string>
|
<string name="pref_notify_header">Benachrichtigungen</string>
|
||||||
<string name="pref_notify_switch">Benachrichtigungen anzeigen</string>
|
<string name="pref_notify_switch">Benachrichtigungen anzeigen</string>
|
||||||
<string name="pref_notify_upcoming_lessons_switch">Benachrichtigungen über bevorstehende Lektionen anzeigen</string>
|
<string name="pref_notify_upcoming_lessons_switch">Benachrichtigungen über bevorstehende Lektionen anzeigen</string>
|
||||||
|
<string name="pref_notify_upcoming_lessons_persistent_switch">Festlegen einer Benachrichtigung über die bevorstehende Lektion dauerhaft</string>
|
||||||
|
<string name="pref_notify_upcoming_lessons_persistent_summary">Deaktivieren wenn die Benachrichtigung nicht in deiner Uhr/Band angezeigt wird</string>
|
||||||
<string name="pref_notify_open_system_settings">Systembenachrichtigungseinstellungen öffnen</string>
|
<string name="pref_notify_open_system_settings">Systembenachrichtigungseinstellungen öffnen</string>
|
||||||
<string name="pref_notify_fix_sync_issues">Synchronisierungs- und Benachrichtigungsprobleme reparieren</string>
|
<string name="pref_notify_fix_sync_issues">Synchronisierungs- und Benachrichtigungsprobleme reparieren</string>
|
||||||
<string name="pref_notify_fix_sync_issues_message">Ihr Gerät hat möglicherweise Probleme mit der Datensynchronisierung und Benachrichtigungen.\n\nUm diese zu reparieren, fügen Sie Wulkanowy zum Autostart hinzu und deaktivieren Sie die Batterieoptimierung in den Systemeinstellungen des Geräts.</string>
|
<string name="pref_notify_fix_sync_issues_message">Ihr Gerät hat möglicherweise Probleme mit der Datensynchronisierung und Benachrichtigungen.\n\nUm diese zu reparieren, fügen Sie Wulkanowy zum Autostart hinzu und deaktivieren Sie die Batterieoptimierung in den Systemeinstellungen des Geräts.</string>
|
||||||
<string name="pref_notify_fix_sync_issues_settings_button">Gehe zu den Einstellungen</string>
|
<string name="pref_notify_fix_sync_issues_settings_button">Gehe zu den Einstellungen</string>
|
||||||
<string name="pref_notify_debug_switch">Debug-Benachrichtigungen anzeigen</string>
|
<string name="pref_notify_debug_switch">Debug-Benachrichtigungen anzeigen</string>
|
||||||
<string name="pref_notify_disabled_summary">Synchronisierung ist deaktiviert</string>
|
<string name="pref_notify_disabled_summary">Synchronisierung ist deaktiviert</string>
|
||||||
|
<string name="pref_notify_notifications_piggyback">Offizielle App-Benachrichtigungen erfassen</string>
|
||||||
|
<string name="pref_notification_piggyback_popup_title">Benachrichtigungen erfassen</string>
|
||||||
|
<string name="pref_notification_piggyback_popup_description">With this feature you can gain a substitute of push notifications like in the official app. All you need to do is allow Wulkanowy to receive all notifications in your system settings.\n\nHow it works?\nWhen you get a notification in Dziennik VULCAN, Wulkanowy will be notified (that\'s what these extra permissions are for) and will trigger a sync so that can send its own notification.\n\nFOR ADVANCED USERS ONLY</string>
|
||||||
|
<string name="pref_notification_piggyback_popup_positive">Gehe zu Einstellungen</string>
|
||||||
<string name="pref_services_header">Synchronisierung</string>
|
<string name="pref_services_header">Synchronisierung</string>
|
||||||
<string name="pref_services_switch">Automatische Aktualisierung</string>
|
<string name="pref_services_switch">Automatische Aktualisierung</string>
|
||||||
<string name="pref_services_suspended">An Feiertagen suspendiert</string>
|
<string name="pref_services_suspended">An Feiertagen suspendiert</string>
|
||||||
|
@ -41,8 +41,8 @@
|
|||||||
<item>Kolory ocen w dzienniku</item>
|
<item>Kolory ocen w dzienniku</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="grade_average_mode_entries">
|
<string-array name="grade_average_mode_entries">
|
||||||
<item>Średnia ocen z drugiego semestru</item>
|
<item>Średnia ocen tylko z wybranego semestru</item>
|
||||||
<item>Średnia średnich z obu semestrów</item>
|
<item>Średnia ze średnich z obu semestrów</item>
|
||||||
<item>Średnia wszystkich ocen z całego roku</item>
|
<item>Średnia wszystkich ocen z całego roku</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="dashboard_tile_entries">
|
<string-array name="dashboard_tile_entries">
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
<string name="account_details_title">Szczegóły konta</string>
|
<string name="account_details_title">Szczegóły konta</string>
|
||||||
<string name="student_info_title">Informacje o uczniu</string>
|
<string name="student_info_title">Informacje o uczniu</string>
|
||||||
<string name="dashboard_title">Start</string>
|
<string name="dashboard_title">Start</string>
|
||||||
|
<string name="notifications_center_title">Centrum powiadomień</string>
|
||||||
<!--Subtitles-->
|
<!--Subtitles-->
|
||||||
<string name="grade_subtitle">Semestr %1$d, %2$d/%3$d</string>
|
<string name="grade_subtitle">Semestr %1$d, %2$d/%3$d</string>
|
||||||
<!--Login-->
|
<!--Login-->
|
||||||
@ -42,7 +43,8 @@
|
|||||||
<string name="login_symbol_hint">Symbol</string>
|
<string name="login_symbol_hint">Symbol</string>
|
||||||
<string name="login_sign_in">Zaloguj</string>
|
<string name="login_sign_in">Zaloguj</string>
|
||||||
<string name="login_invalid_password">To hasło jest za krótkie</string>
|
<string name="login_invalid_password">To hasło jest za krótkie</string>
|
||||||
<string name="login_incorrect_password">Dane logowania są niepoprawne. Upewnij się, że została wybrana odpowiednia odmiana dziennika UONET+ w polu poniżej</string>
|
<string name="login_incorrect_password_default">Dane logowania są niepoprawne</string>
|
||||||
|
<string name="login_incorrect_password">%1$s. Upewnij się, że wybrano poprawną odmianę dziennika UONET+ poniżej</string>
|
||||||
<string name="login_invalid_pin">Nieprawidłowy PIN</string>
|
<string name="login_invalid_pin">Nieprawidłowy PIN</string>
|
||||||
<string name="login_invalid_token">Nieprawidłowy token</string>
|
<string name="login_invalid_token">Nieprawidłowy token</string>
|
||||||
<string name="login_expired_token">Token stracił ważność</string>
|
<string name="login_expired_token">Token stracił ważność</string>
|
||||||
@ -90,6 +92,10 @@
|
|||||||
<string name="grade_summary_final_grade">Ocena końcowa</string>
|
<string name="grade_summary_final_grade">Ocena końcowa</string>
|
||||||
<string name="grade_summary_predicted_grade">Przewidywana ocena</string>
|
<string name="grade_summary_predicted_grade">Przewidywana ocena</string>
|
||||||
<string name="grade_summary_calculated_average">Obliczona średnia</string>
|
<string name="grade_summary_calculated_average">Obliczona średnia</string>
|
||||||
|
<string name="grade_summary_calculated_average_help_dialog_title">Jak działa obliczona średnia?</string>
|
||||||
|
<string name="grade_summary_calculated_average_help_dialog_message">Obliczona średnia jest średnią arytmetyczną obliczoną ze średnich przedmiotów. Pozwala ona na poznanie przybliżonej średniej końcowej. Jest obliczana w sposób wybrany przez użytkownika w ustawieniach aplikacji. Zaleca się wybranie odpowiedniej opcji. Dzieje się tak dlatego, że obliczanie średnich w szkołach różni się. Dodatkowo, jeśli twoja szkoła ma włączone średnie przedmiotów na stronie dziennika Vulcan, aplikacja pobiera je i ich nie oblicza. Można to zmienić, wymuszając obliczanie średniej w ustawieniach aplikacji.\n\n<b>Średnia ocen tylko z wybranego semestru</b>:\n1. Obliczanie średniej arytmetycznej każdego przedmiotu w danym semestrze\n2. Zsumowanie obliczonych średnich\n3. Obliczanie średniej arytmetycznej zsumowanych średnich\n\n<b>Średnia ze średnich z obu semestrów</b>:\n1.Obliczanie średniej arytmetycznej każdego przedmiotu w semestrze 1 i 2\n2. Obliczanie średniej arytmetycznej obliczonych średnich w semetrze 1 i 2 każdego przedmiotu.\n3. Zsumowanie obliczonych średnich\n4. Obliczanie średniej arytmetycznej zsumowanych średnich\n\n<b>Średnia wszystkich ocen z całego roku:</b>\n1. Obliczanie średniej arytmetycznej z każdego przedmiotu w ciągu całego roku. Końcowa ocena w 1 semestrze jest bez znaczenia.\n3. Zsumowanie obliczonych średnich\n4. Obliczanie średniej arytmetycznej z zsumowanych średnich</string>
|
||||||
|
<string name="grade_summary_final_average_help_dialog_title">Jak działa końcowa średnia?</string>
|
||||||
|
<string name="grade_summary_final_average_help_dialog_message">Średnią końcową jest średnia arytmetyczna obliczona na podstawie wszystkich obecnie dostępnych ocen końcowych w danym semestrze.\n\nSchemat obliczeń składa się z następujących kroków:\n1. Sumowanie końcowych ocen wpisanych przez nauczycieli\n2. Dzielenie przez liczbę przedmiotów, z których oceny zostały już wystawione</string>
|
||||||
<string name="grade_summary_final_average">Końcowa średnia</string>
|
<string name="grade_summary_final_average">Końcowa średnia</string>
|
||||||
<string name="grade_summary_from_subjects">z %1$d na %2$d przedmiotów</string>
|
<string name="grade_summary_from_subjects">z %1$d na %2$d przedmiotów</string>
|
||||||
<string name="grade_menu_summary">Podsumowanie</string>
|
<string name="grade_menu_summary">Podsumowanie</string>
|
||||||
@ -238,12 +244,7 @@
|
|||||||
<string name="message_chip_only_unread">Tylko nieprzeczytane</string>
|
<string name="message_chip_only_unread">Tylko nieprzeczytane</string>
|
||||||
<string name="message_chip_only_with_attachments">Tylko z załącznikami</string>
|
<string name="message_chip_only_with_attachments">Tylko z załącznikami</string>
|
||||||
<string name="message_read">Przeczytana: %s</string>
|
<string name="message_read">Przeczytana: %s</string>
|
||||||
<plurals name="message_read_by">
|
<string name="message_read_by">Przeczytana przez: %1$d z %2$d osób</string>
|
||||||
<item quantity="one">Przeczytana przez: %1$d z %2$d osób</item>
|
|
||||||
<item quantity="few">Przeczytana przez: %1$d z %2$d osób</item>
|
|
||||||
<item quantity="many">Przeczytana przez: %1$d z %2$d osób</item>
|
|
||||||
<item quantity="other">Przeczytana przez: %1$d z %2$d osób</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="message_number_item">
|
<plurals name="message_number_item">
|
||||||
<item quantity="one">%d wiadomość</item>
|
<item quantity="one">%d wiadomość</item>
|
||||||
<item quantity="few">%d wiadomości</item>
|
<item quantity="few">%d wiadomości</item>
|
||||||
@ -602,7 +603,7 @@
|
|||||||
<!--Preferences-->
|
<!--Preferences-->
|
||||||
<string name="pref_view_header">Wygląd i zachowanie aplikacji</string>
|
<string name="pref_view_header">Wygląd i zachowanie aplikacji</string>
|
||||||
<string name="pref_view_list">Domyślny widok</string>
|
<string name="pref_view_list">Domyślny widok</string>
|
||||||
<string name="pref_view_grade_average_mode">Obliczanie średniej końcoworocznej</string>
|
<string name="pref_view_grade_average_mode">Opcje obliczonej średniej</string>
|
||||||
<string name="pref_view_grade_average_force_calc">Wymuś obliczanie średniej przez aplikację</string>
|
<string name="pref_view_grade_average_force_calc">Wymuś obliczanie średniej przez aplikację</string>
|
||||||
<string name="pref_view_present">Pokazuj obecność</string>
|
<string name="pref_view_present">Pokazuj obecność</string>
|
||||||
<string name="pref_view_app_theme">Motyw</string>
|
<string name="pref_view_app_theme">Motyw</string>
|
||||||
@ -617,12 +618,18 @@
|
|||||||
<string name="pref_notify_header">Powiadomienia</string>
|
<string name="pref_notify_header">Powiadomienia</string>
|
||||||
<string name="pref_notify_switch">Pokazuj powiadomienia</string>
|
<string name="pref_notify_switch">Pokazuj powiadomienia</string>
|
||||||
<string name="pref_notify_upcoming_lessons_switch">Pokazuj powiadomienia o nadchodzących lekcjach</string>
|
<string name="pref_notify_upcoming_lessons_switch">Pokazuj powiadomienia o nadchodzących lekcjach</string>
|
||||||
|
<string name="pref_notify_upcoming_lessons_persistent_switch">Ustaw powiadomienie o nadchodzącej lekcji jako trwałe</string>
|
||||||
|
<string name="pref_notify_upcoming_lessons_persistent_summary">Wyłącz, gdy powiadomienie nie jest widoczne na zegarku/opasce</string>
|
||||||
<string name="pref_notify_open_system_settings">Otwórz systemowe ustawienia powiadomień</string>
|
<string name="pref_notify_open_system_settings">Otwórz systemowe ustawienia powiadomień</string>
|
||||||
<string name="pref_notify_fix_sync_issues">Napraw problemy z synchronizacją i powiadomieniami</string>
|
<string name="pref_notify_fix_sync_issues">Napraw problemy z synchronizacją i powiadomieniami</string>
|
||||||
<string name="pref_notify_fix_sync_issues_message">Na twoim urządzeniu mogą występować problemy z synchronizacją danych i powiadomieniami.\n\nBy je naprawić, dodaj Wulkanowego do autostartu i wyłącz optymalizację/oszczędzanie baterii w ustawieniach systemowych telefonu.</string>
|
<string name="pref_notify_fix_sync_issues_message">Na twoim urządzeniu mogą występować problemy z synchronizacją danych i powiadomieniami.\n\nBy je naprawić, dodaj Wulkanowego do autostartu i wyłącz optymalizację/oszczędzanie baterii w ustawieniach systemowych telefonu.</string>
|
||||||
<string name="pref_notify_fix_sync_issues_settings_button">Przejdź do ustawień</string>
|
<string name="pref_notify_fix_sync_issues_settings_button">Przejdź do ustawień</string>
|
||||||
<string name="pref_notify_debug_switch">Pokazuj powiadomienia debugowania</string>
|
<string name="pref_notify_debug_switch">Pokazuj powiadomienia debugowania</string>
|
||||||
<string name="pref_notify_disabled_summary">Synchronizacja jest wyłączona</string>
|
<string name="pref_notify_disabled_summary">Synchronizacja jest wyłączona</string>
|
||||||
|
<string name="pref_notify_notifications_piggyback">Przechwytywanie powiadomień oficjalnej aplikacji</string>
|
||||||
|
<string name="pref_notification_piggyback_popup_title">Przechwytywanie powiadomień</string>
|
||||||
|
<string name="pref_notification_piggyback_popup_description">Dzięki tej funkcji możesz uzyskać namiastkę powiadomień push, takich jak w oficjalnej aplikacji. Wszystko, co musisz zrobić, to zezwolić Wulkanowemu na odczytywanie wszystkich powiadomień w ustawieniach systemowych.\n\nJak to działa?\nKiedy otrzymasz powiadomienie w Dzienniczku VULCAN, Wulkanowy zostanie o tym powiadomiony (do tego jest to dodatkowe uprawnienie) i uruchomi synchronizację, aby mógł wysłać własne powiadomienie.\n\nWYŁĄCZNIE DLA ZAAWANSOWANYCH UŻYTKOWNIKÓW</string>
|
||||||
|
<string name="pref_notification_piggyback_popup_positive">Przejdź do ustawień</string>
|
||||||
<string name="pref_services_header">Synchronizacja</string>
|
<string name="pref_services_header">Synchronizacja</string>
|
||||||
<string name="pref_services_switch">Automatyczna aktualizacja</string>
|
<string name="pref_services_switch">Automatyczna aktualizacja</string>
|
||||||
<string name="pref_services_suspended">Zawieszona na wakacjach</string>
|
<string name="pref_services_suspended">Zawieszona na wakacjach</string>
|
||||||
|
@ -41,8 +41,8 @@
|
|||||||
<item>Цвета оценок в дневнике</item>
|
<item>Цвета оценок в дневнике</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="grade_average_mode_entries">
|
<string-array name="grade_average_mode_entries">
|
||||||
<item>Средняя оценка со 2 семестра</item>
|
<item>Средние оценки только с выбранного семестра</item>
|
||||||
<item>Средняя оценка с двух семестров</item>
|
<item>Средние значения для обоих семестров</item>
|
||||||
<item>Средняя оценок со всего года</item>
|
<item>Средняя оценок со всего года</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="dashboard_tile_entries">
|
<string-array name="dashboard_tile_entries">
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
<string name="account_details_title">Данные аккаунта</string>
|
<string name="account_details_title">Данные аккаунта</string>
|
||||||
<string name="student_info_title">Информация о студенте</string>
|
<string name="student_info_title">Информация о студенте</string>
|
||||||
<string name="dashboard_title">Панель</string>
|
<string name="dashboard_title">Панель</string>
|
||||||
|
<string name="notifications_center_title">Центр уведомлений</string>
|
||||||
<!--Subtitles-->
|
<!--Subtitles-->
|
||||||
<string name="grade_subtitle">%1$d семестр, %2$d/%3$d</string>
|
<string name="grade_subtitle">%1$d семестр, %2$d/%3$d</string>
|
||||||
<!--Login-->
|
<!--Login-->
|
||||||
@ -42,7 +43,8 @@
|
|||||||
<string name="login_symbol_hint">Symbol</string>
|
<string name="login_symbol_hint">Symbol</string>
|
||||||
<string name="login_sign_in">Войти</string>
|
<string name="login_sign_in">Войти</string>
|
||||||
<string name="login_invalid_password">Слишком короткий пароль</string>
|
<string name="login_invalid_password">Слишком короткий пароль</string>
|
||||||
<string name="login_incorrect_password">Данные для входа неверны. Убедитесь, что в поле ниже выбран правильный вариант регистра UONET+</string>
|
<string name="login_incorrect_password_default">Данные для входа указаны неверно</string>
|
||||||
|
<string name="login_incorrect_password">%1$s. Убедитесь, что ниже выбран правильный UONET+ вариант регистра</string>
|
||||||
<string name="login_invalid_pin">Неправильный PIN</string>
|
<string name="login_invalid_pin">Неправильный PIN</string>
|
||||||
<string name="login_invalid_token">Неверный token</string>
|
<string name="login_invalid_token">Неверный token</string>
|
||||||
<string name="login_expired_token">Token просрочен</string>
|
<string name="login_expired_token">Token просрочен</string>
|
||||||
@ -90,6 +92,10 @@
|
|||||||
<string name="grade_summary_final_grade">Итоговая оценка</string>
|
<string name="grade_summary_final_grade">Итоговая оценка</string>
|
||||||
<string name="grade_summary_predicted_grade">Ожидаемая оценка</string>
|
<string name="grade_summary_predicted_grade">Ожидаемая оценка</string>
|
||||||
<string name="grade_summary_calculated_average">Рассчитанная средняя оценка</string>
|
<string name="grade_summary_calculated_average">Рассчитанная средняя оценка</string>
|
||||||
|
<string name="grade_summary_calculated_average_help_dialog_title">Как рассчитывается средняя работа?</string>
|
||||||
|
<string name="grade_summary_calculated_average_help_dialog_message">Расчетное среднее - это среднее арифметическое, рассчитанное на основе средних значений испытуемых. Это позволяет узнать приблизительное итоговое среднее значение. Он рассчитывается способом, выбранным пользователем в настройках приложения. Рекомендуется выбрать подходящий вариант. Это потому, что расчет средних показателей школы отличается. Кроме того, если ваша школа сообщает среднее значение по предметам на странице Vulcan, приложение загружает их и не вычисляет эти средние значения. Это можно изменить, принудительно вычисляя среднее значение в настройках приложения.\n\n<b>Среднее значение только за выбранный семестр </b>:\n1. Вычисление средневзвешенного значения по каждому предмету за семестр\n2.Добавление вычисленных средних\n3. Вычисление среднего арифметического суммарных средних\n\n<b>Среднее из средних значений за оба семестра</b>:\n1.Расчет средневзвешенного значения для каждого предмета в семестрах 1 и 2\n2. Вычисление среднего арифметического рассчитанных средних значений для семестров 1 и 2 по каждому предмету.\n3. Добавление вычисленных средних\n4. Расчет среднего арифметического суммированных средних\n\n<b>Среднее значение оценок за весь год: </b>\n1. Расчет средневзвешенного значения за год по каждому предмету. Итоговое среднее значение за 1 семестр не имеет значения.\n3. Добавление вычисленных средних\n4. Расчет среднего арифметического</string>
|
||||||
|
<string name="grade_summary_final_average_help_dialog_title">Как работает окончательный средний показатель?</string>
|
||||||
|
<string name="grade_summary_final_average_help_dialog_message">Среднее арифметическое - это среднее арифметическое, рассчитанное по всем имеющимся на данный момент итоговым классам данного семестра.\n\nСхема расчета состоит из следующих шагов:\n1. Суммирование итоговых классов преподавателей\n2. Деление по количеству уже оцененных предметов</string>
|
||||||
<string name="grade_summary_final_average">Итоговая средняя оценка</string>
|
<string name="grade_summary_final_average">Итоговая средняя оценка</string>
|
||||||
<string name="grade_summary_from_subjects">от %1$d из %2$d субъектов</string>
|
<string name="grade_summary_from_subjects">от %1$d из %2$d субъектов</string>
|
||||||
<string name="grade_menu_summary">Итоги</string>
|
<string name="grade_menu_summary">Итоги</string>
|
||||||
@ -238,12 +244,7 @@
|
|||||||
<string name="message_chip_only_unread">Только непрочитанные</string>
|
<string name="message_chip_only_unread">Только непрочитанные</string>
|
||||||
<string name="message_chip_only_with_attachments">Только с вложениями</string>
|
<string name="message_chip_only_with_attachments">Только с вложениями</string>
|
||||||
<string name="message_read">Чтение: %s</string>
|
<string name="message_read">Чтение: %s</string>
|
||||||
<plurals name="message_read_by">
|
<string name="message_read_by">Прочитано: %1$d из %2$d человек</string>
|
||||||
<item quantity="one">Прочитано: %1$d из %2$d человек</item>
|
|
||||||
<item quantity="few">Прочитано: %1$d из %2$d человек</item>
|
|
||||||
<item quantity="many">Прочитано: %1$d из %2$d человек</item>
|
|
||||||
<item quantity="other">Прочитано: %1$d из %2$d человек</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="message_number_item">
|
<plurals name="message_number_item">
|
||||||
<item quantity="one">%d сообщение</item>
|
<item quantity="one">%d сообщение</item>
|
||||||
<item quantity="few">%d сообщения</item>
|
<item quantity="few">%d сообщения</item>
|
||||||
@ -402,8 +403,8 @@
|
|||||||
<item quantity="many">У вас %1$d новая конференция</item>
|
<item quantity="many">У вас %1$d новая конференция</item>
|
||||||
<item quantity="other">У вас %1$d новых конференций</item>
|
<item quantity="other">У вас %1$d новых конференций</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="conferences_present">Present at conference</string>
|
<string name="conferences_present">Присутствует на конференции</string>
|
||||||
<string name="conference_agenda">Agenda</string>
|
<string name="conference_agenda">Повестка дня</string>
|
||||||
<!--Director information-->
|
<!--Director information-->
|
||||||
<string name="school_announcement_title">Объявления школ</string>
|
<string name="school_announcement_title">Объявления школ</string>
|
||||||
<string name="school_announcement_no_items">Нет объявлений о школе</string>
|
<string name="school_announcement_no_items">Нет объявлений о школе</string>
|
||||||
@ -592,7 +593,7 @@
|
|||||||
<string name="all_yes">Да</string>
|
<string name="all_yes">Да</string>
|
||||||
<string name="all_no">Нет</string>
|
<string name="all_no">Нет</string>
|
||||||
<string name="all_save">Сохранить</string>
|
<string name="all_save">Сохранить</string>
|
||||||
<string name="all_title">Title</string>
|
<string name="all_title">Тема</string>
|
||||||
<!--Timetable Widget-->
|
<!--Timetable Widget-->
|
||||||
<string name="widget_timetable_no_items">Нет уроков</string>
|
<string name="widget_timetable_no_items">Нет уроков</string>
|
||||||
<string name="widget_timetable_theme_title">Выбрать тему</string>
|
<string name="widget_timetable_theme_title">Выбрать тему</string>
|
||||||
@ -602,7 +603,7 @@
|
|||||||
<!--Preferences-->
|
<!--Preferences-->
|
||||||
<string name="pref_view_header">Внешний вид приложения & поведение</string>
|
<string name="pref_view_header">Внешний вид приложения & поведение</string>
|
||||||
<string name="pref_view_list">Окно по умолчанию</string>
|
<string name="pref_view_list">Окно по умолчанию</string>
|
||||||
<string name="pref_view_grade_average_mode">Способ определения средней годовой оценки</string>
|
<string name="pref_view_grade_average_mode">Рассчитанные средние параметры</string>
|
||||||
<string name="pref_view_grade_average_force_calc">Принудительно высчитать среднюю оценку через приложение</string>
|
<string name="pref_view_grade_average_force_calc">Принудительно высчитать среднюю оценку через приложение</string>
|
||||||
<string name="pref_view_present">Показать присутствие</string>
|
<string name="pref_view_present">Показать присутствие</string>
|
||||||
<string name="pref_view_app_theme">Тема</string>
|
<string name="pref_view_app_theme">Тема</string>
|
||||||
@ -617,12 +618,18 @@
|
|||||||
<string name="pref_notify_header">Уведомления</string>
|
<string name="pref_notify_header">Уведомления</string>
|
||||||
<string name="pref_notify_switch">Показывать уведомления</string>
|
<string name="pref_notify_switch">Показывать уведомления</string>
|
||||||
<string name="pref_notify_upcoming_lessons_switch">Показывать уведомления о будущих уроках</string>
|
<string name="pref_notify_upcoming_lessons_switch">Показывать уведомления о будущих уроках</string>
|
||||||
|
<string name="pref_notify_upcoming_lessons_persistent_switch">Сделать уведомления о предстоящем уроке постоянным</string>
|
||||||
|
<string name="pref_notify_upcoming_lessons_persistent_summary">Выключить, когда уведомление не отображается в чата/полосе</string>
|
||||||
<string name="pref_notify_open_system_settings">Открыть настройки уведомлений системы</string>
|
<string name="pref_notify_open_system_settings">Открыть настройки уведомлений системы</string>
|
||||||
<string name="pref_notify_fix_sync_issues">Исправить проблемы с синхронизацией и уведомлениями</string>
|
<string name="pref_notify_fix_sync_issues">Исправить проблемы с синхронизацией и уведомлениями</string>
|
||||||
<string name="pref_notify_fix_sync_issues_message">На вашем устройстве могут быть проблемы с синхронизацией данных и уведомлениями.\n\nЧтобы их исправить, вам необходимо добавить Wulkanowy в авто-старт и выключить оптимизацию/экономию батареи в настройках устройства.</string>
|
<string name="pref_notify_fix_sync_issues_message">На вашем устройстве могут быть проблемы с синхронизацией данных и уведомлениями.\n\nЧтобы их исправить, вам необходимо добавить Wulkanowy в авто-старт и выключить оптимизацию/экономию батареи в настройках устройства.</string>
|
||||||
<string name="pref_notify_fix_sync_issues_settings_button">Перейти в настройски</string>
|
<string name="pref_notify_fix_sync_issues_settings_button">Перейти в настройски</string>
|
||||||
<string name="pref_notify_debug_switch">Показывать дебаг-уведомления</string>
|
<string name="pref_notify_debug_switch">Показывать дебаг-уведомления</string>
|
||||||
<string name="pref_notify_disabled_summary">Синхронизация отключена</string>
|
<string name="pref_notify_disabled_summary">Синхронизация отключена</string>
|
||||||
|
<string name="pref_notify_notifications_piggyback">Записывать официальные уведомления</string>
|
||||||
|
<string name="pref_notification_piggyback_popup_title">Показывать push-уведомления</string>
|
||||||
|
<string name="pref_notification_piggyback_popup_description">С помощью этой функции вы можете получить замену push-уведомлений, как в официальном приложении. Все, что вам нужно сделать, это разрешить Wulkanowy получать все уведомления в настройках системы.\n\nКак это работает?\nКогда вы получаете уведомление в Dziennik VULCAN, Wulkanowy будет уведомлен (это требует дополнительных прав) и запустит синхронизацию, чтобы отправить свое уведомление.\n\nТОЛЬКО ДЛЯ ПОЛЬЗОВАТЕЛЯ</string>
|
||||||
|
<string name="pref_notification_piggyback_popup_positive">Перейти к настройкам</string>
|
||||||
<string name="pref_services_header">Синхронизация</string>
|
<string name="pref_services_header">Синхронизация</string>
|
||||||
<string name="pref_services_switch">Автоматическая синхронизация</string>
|
<string name="pref_services_switch">Автоматическая синхронизация</string>
|
||||||
<string name="pref_services_suspended">Приостановить синхронизации во время каникул</string>
|
<string name="pref_services_suspended">Приостановить синхронизации во время каникул</string>
|
||||||
|
@ -41,8 +41,8 @@
|
|||||||
<item>Farby známok v denníku</item>
|
<item>Farby známok v denníku</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="grade_average_mode_entries">
|
<string-array name="grade_average_mode_entries">
|
||||||
<item>Priemer známok až od druhého semestra</item>
|
<item>Priemer známok iba z vybraného semestra</item>
|
||||||
<item>Priemer známok z oboch semestrov</item>
|
<item>Priemer z priemerov z oboch semestrov</item>
|
||||||
<item>Priemer známok z celého roka</item>
|
<item>Priemer známok z celého roka</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="dashboard_tile_entries">
|
<string-array name="dashboard_tile_entries">
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user