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 }}
|
||||
run: |
|
||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg
|
||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg
|
||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
||||
- name: Upload apk to google play
|
||||
env:
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||
PLAY_SERVICE_ACCOUNT_EMAIL: ${{ secrets.PLAY_SERVICE_ACCOUNT_EMAIL }}
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
run: ./gradlew publishPlayRelease -PenableFirebase --stacktrace;
|
||||
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
|
||||
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
|
||||
|
||||
deploy-app-gallery:
|
||||
name: Deploy to AppGallery
|
||||
@ -60,7 +59,6 @@ jobs:
|
||||
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
||||
run: |
|
||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg
|
||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg
|
||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
||||
- name: Prepare credentials
|
||||
env:
|
||||
@ -68,7 +66,7 @@ jobs:
|
||||
run: echo $AGC_CREDENTIALS > ./app/src/release/agconnect-credentials.json
|
||||
- name: Build and publish HMS version
|
||||
env:
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
run: ./gradlew bundleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace
|
||||
|
2
LICENSE
2
LICENSE
@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2019 Wulkanowy
|
||||
Copyright 2021 Wulkanowy
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -21,8 +21,8 @@ android {
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode 96
|
||||
versionName "1.2.3"
|
||||
versionCode 97
|
||||
versionName "1.3.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
@ -96,10 +96,20 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
playConfigs {
|
||||
play { enabled.set(true) }
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
bundle {
|
||||
language {
|
||||
enableSplit = false
|
||||
}
|
||||
}
|
||||
|
||||
testOptions.unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
@ -130,11 +140,10 @@ kapt {
|
||||
}
|
||||
|
||||
play {
|
||||
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
|
||||
serviceAccountCredentials = file('key.p12')
|
||||
defaultToAppBundles = false
|
||||
track = 'production'
|
||||
updatePriority = 3
|
||||
enabled.set(false)
|
||||
}
|
||||
|
||||
huaweiPublish {
|
||||
@ -157,7 +166,7 @@ ext {
|
||||
}
|
||||
|
||||
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'
|
||||
|
||||
@ -174,7 +183,7 @@ dependencies {
|
||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||
implementation "androidx.viewpager:viewpager:1.0.0"
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.1.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.1.1"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
|
||||
implementation "com.google.android.material:material:1.4.0"
|
||||
implementation "com.github.wulkanowy:material-chips-input:2.3.1"
|
||||
@ -215,7 +224,7 @@ dependencies {
|
||||
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
||||
playImplementation 'com.google.firebase:firebase-messaging:'
|
||||
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
||||
playImplementation 'com.google.android.play:core:1.10.1'
|
||||
playImplementation 'com.google.android.play:core:1.10.2'
|
||||
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
||||
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:6.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">
|
||||
<activity
|
||||
android:name=".ui.modules.splash.SplashActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/WulkanowyTheme.SplashScreen"
|
||||
tools:ignore="LockedOrientationActivity">
|
||||
@ -74,6 +75,7 @@
|
||||
<activity
|
||||
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||
<intent-filter>
|
||||
@ -83,6 +85,7 @@
|
||||
<activity
|
||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||
<intent-filter>
|
||||
@ -93,6 +96,22 @@
|
||||
<service
|
||||
android:name=".services.widgets.TimetableWidgetService"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||
<service
|
||||
android:name=".services.piggyback.VulcanNotificationListenerService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name=".services.messaging.AppMessagingService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
|
||||
<receiver
|
||||
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
|
||||
@ -107,6 +126,7 @@
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
|
||||
android:exported="true"
|
||||
android:label="@string/lucky_number_title">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
|
@ -8,8 +8,8 @@ import androidx.preference.PreferenceManager
|
||||
import com.chuckerteam.chucker.api.ChuckerCollector
|
||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||
import com.chuckerteam.chucker.api.RetentionManager
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||
import com.squareup.moshi.Moshi
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@ -202,4 +202,8 @@ internal class RepositoryModule {
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideSchoolAnnouncementDao(database: AppDatabase) = database.schoolAnnouncementDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideNotificationDao(database: AppDatabase) = database.notificationDao
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import io.github.wulkanowy.data.db.dao.AttendanceDao
|
||||
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||
import io.github.wulkanowy.data.db.dao.ConferenceDao
|
||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||
import io.github.wulkanowy.data.db.dao.ExamDao
|
||||
import io.github.wulkanowy.data.db.dao.GradeDao
|
||||
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
|
||||
@ -23,8 +22,10 @@ import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
||||
import io.github.wulkanowy.data.db.dao.MessagesDao
|
||||
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
||||
import io.github.wulkanowy.data.db.dao.NoteDao
|
||||
import io.github.wulkanowy.data.db.dao.NotificationDao
|
||||
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||
import io.github.wulkanowy.data.db.dao.SchoolDao
|
||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||
@ -38,7 +39,6 @@ import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
||||
import io.github.wulkanowy.data.db.entities.Conference
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
|
||||
@ -51,9 +51,11 @@ import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||
import io.github.wulkanowy.data.db.entities.Note
|
||||
import io.github.wulkanowy.data.db.entities.Notification
|
||||
import io.github.wulkanowy.data.db.entities.Recipient
|
||||
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
||||
import io.github.wulkanowy.data.db.entities.School
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||
@ -95,6 +97,7 @@ import io.github.wulkanowy.data.db.migrations.Migration37
|
||||
import io.github.wulkanowy.data.db.migrations.Migration38
|
||||
import io.github.wulkanowy.data.db.migrations.Migration39
|
||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||
import io.github.wulkanowy.data.db.migrations.Migration40
|
||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||
@ -134,6 +137,7 @@ import javax.inject.Singleton
|
||||
StudentInfo::class,
|
||||
TimetableHeader::class,
|
||||
SchoolAnnouncement::class,
|
||||
Notification::class
|
||||
],
|
||||
version = AppDatabase.VERSION_SCHEMA,
|
||||
exportSchema = true
|
||||
@ -142,7 +146,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 39
|
||||
const val VERSION_SCHEMA = 40
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||
Migration2(),
|
||||
@ -183,6 +187,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration37(),
|
||||
Migration38(),
|
||||
Migration39(),
|
||||
Migration40()
|
||||
)
|
||||
|
||||
fun newInstance(
|
||||
@ -252,4 +257,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract val timetableHeaderDao: TimetableHeaderDao
|
||||
|
||||
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
|
||||
|
||||
abstract val notificationDao: NotificationDao
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
package io.github.wulkanowy.data.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import io.github.wulkanowy.data.db.entities.Notification
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Dao
|
||||
interface NotificationDao : BaseDao<Notification> {
|
||||
|
||||
@Query("SELECT * FROM Notifications WHERE student_id = :studentId OR student_id = -1")
|
||||
fun loadAll(studentId: Long): Flow<List<Notification>>
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Entity(tableName = "Notifications")
|
||||
data class Notification(
|
||||
|
||||
@ColumnInfo(name = "student_id")
|
||||
val studentId: Long,
|
||||
|
||||
val title: String,
|
||||
|
||||
val content: String,
|
||||
|
||||
val type: NotificationType,
|
||||
|
||||
val date: LocalDateTime,
|
||||
|
||||
val data: String? = null
|
||||
) {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration40 : Migration(39, 40) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS `Notifications` (
|
||||
`student_id` INTEGER NOT NULL,
|
||||
`title` TEXT NOT NULL,
|
||||
`content` TEXT NOT NULL,
|
||||
`type` TEXT NOT NULL,
|
||||
`date` INTEGER NOT NULL,
|
||||
`data` TEXT,
|
||||
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
|
||||
)
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import androidx.annotation.StringRes
|
||||
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
|
||||
sealed interface Notification {
|
||||
sealed interface NotificationData {
|
||||
val type: NotificationType
|
||||
val startMenu: MainView.Section
|
||||
val icon: Int
|
||||
@ -14,7 +14,7 @@ sealed interface Notification {
|
||||
val contentStringRes: Int
|
||||
}
|
||||
|
||||
data class MultipleNotifications(
|
||||
data class MultipleNotificationsData(
|
||||
override val type: NotificationType,
|
||||
override val startMenu: MainView.Section,
|
||||
@DrawableRes override val icon: Int,
|
||||
@ -23,9 +23,9 @@ data class MultipleNotifications(
|
||||
|
||||
@PluralsRes val summaryStringRes: Int,
|
||||
val lines: List<String>,
|
||||
) : Notification
|
||||
) : NotificationData
|
||||
|
||||
data class OneNotification(
|
||||
data class OneNotificationData(
|
||||
override val type: NotificationType,
|
||||
override val startMenu: MainView.Section,
|
||||
@DrawableRes override val icon: Int,
|
||||
@ -33,4 +33,4 @@ data class OneNotification(
|
||||
@StringRes override val contentStringRes: Int,
|
||||
|
||||
val contentValues: List<String>,
|
||||
) : Notification
|
||||
) : NotificationData
|
@ -32,10 +32,23 @@ class AttendanceRepository @Inject constructor(
|
||||
|
||||
private val cacheKey = "attendance"
|
||||
|
||||
fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
fun getAttendance(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) },
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, semester, start, end)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getAttendance(start.monday, end.sunday, semester.semesterId)
|
||||
@ -50,12 +63,17 @@ class AttendanceRepository @Inject constructor(
|
||||
filterResult = { it.filter { item -> item.date in start..end } }
|
||||
)
|
||||
|
||||
suspend fun excuseForAbsence(student: Student, semester: Semester, absenceList: List<Attendance>, reason: String? = null) {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).excuseForAbsence(absenceList.map { attendance ->
|
||||
suspend fun excuseForAbsence(
|
||||
student: Student, semester: Semester,
|
||||
absenceList: List<Attendance>, reason: String? = null
|
||||
) {
|
||||
val items = absenceList.map { attendance ->
|
||||
Absent(
|
||||
date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)),
|
||||
timeId = attendance.timeId
|
||||
)
|
||||
}, reason)
|
||||
}
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.excuseForAbsence(items, reason)
|
||||
}
|
||||
}
|
||||
|
@ -29,12 +29,12 @@ class AttendanceSummaryRepository @Inject constructor(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
subjectId: Int,
|
||||
forceRefresh: Boolean
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
it.isEmpty() || forceRefresh
|
||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
||||
fetch = {
|
||||
|
@ -28,10 +28,28 @@ class CompletedLessonsRepository @Inject constructor(
|
||||
|
||||
private val cacheKey = "completed"
|
||||
|
||||
fun getCompletedLessons(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
fun getCompletedLessons(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { completedLessonsDb.loadAll(semester.studentId, semester.diaryId, start.monday, end.sunday) },
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, semester, start, end)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
completedLessonsDb.loadAll(
|
||||
studentId = semester.studentId,
|
||||
diaryId = semester.diaryId,
|
||||
from = start.monday,
|
||||
end = end.sunday
|
||||
)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getCompletedLessons(start.monday, end.sunday)
|
||||
|
@ -35,12 +35,12 @@ class ConferenceRepository @Inject constructor(
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)
|
||||
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
it.isEmpty() || forceRefresh
|
||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
|
||||
|
@ -36,14 +36,14 @@ class ExamRepository @Inject constructor(
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false
|
||||
notify: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, semester, start, end)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
examDb.loadAll(
|
||||
|
@ -37,13 +37,12 @@ class GradeRepository @Inject constructor(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false
|
||||
notify: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { (details, summaries) ->
|
||||
val isShouldBeRefreshed =
|
||||
refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
||||
@ -71,8 +70,8 @@ class GradeRepository @Inject constructor(
|
||||
newDetails: List<Grade>,
|
||||
notify: Boolean
|
||||
) {
|
||||
val notifyBreakDate =
|
||||
oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate()
|
||||
val notifyBreakDate = oldGrades.maxByOrNull {it.date }
|
||||
?.date ?: student.registrationDate.toLocalDate()
|
||||
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
||||
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
||||
if (it.date >= notifyBreakDate) it.apply {
|
||||
@ -89,8 +88,7 @@ class GradeRepository @Inject constructor(
|
||||
) {
|
||||
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
||||
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
||||
val oldSummary =
|
||||
oldSummaries.find { oldSummary -> oldSummary.subject == summary.subject }
|
||||
val oldSummary = oldSummaries.find { old -> old.subject == summary.subject }
|
||||
summary.isPredictedGradeNotified = when {
|
||||
summary.predictedGrade.isEmpty() -> true
|
||||
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
||||
|
@ -39,9 +39,19 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
private val semesterCacheKey = "grade_stats_semester"
|
||||
private val pointsCacheKey = "grade_stats_points"
|
||||
|
||||
fun getGradesPartialStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||
fun getGradesPartialStatistics(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
subjectName: String,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = partialMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(partialCacheKey, semester)) },
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(partialCacheKey, semester)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
@ -76,9 +86,19 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
fun getGradesSemesterStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||
fun getGradesSemesterStatistics(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
subjectName: String,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = semesterMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(semesterCacheKey, semester)) },
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(semesterCacheKey, semester)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
@ -94,10 +114,12 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
val itemsWithAverage = items.map { item ->
|
||||
item.copy().apply {
|
||||
val denominator = item.amounts.sum()
|
||||
average = if (denominator == 0) "" else (item.amounts.mapIndexed { gradeValue, amount ->
|
||||
(gradeValue + 1) * amount
|
||||
}.sum().toDouble() / denominator).let {
|
||||
"%.2f".format(Locale.FRANCE, it)
|
||||
average = if (denominator == 0) "" else {
|
||||
(item.amounts.mapIndexed { gradeValue, amount ->
|
||||
(gradeValue + 1) * amount
|
||||
}.sum().toDouble() / denominator).let {
|
||||
"%.2f".format(Locale.FRANCE, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -109,7 +131,9 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
|
||||
studentGrade = 0
|
||||
).apply {
|
||||
average = itemsWithAverage.mapNotNull { it.average.replace(",", ".").toDoubleOrNull() }.average().let {
|
||||
average = itemsWithAverage.mapNotNull {
|
||||
it.average.replace(",", ".").toDoubleOrNull()
|
||||
}.average().let {
|
||||
"%.2f".format(Locale.FRANCE, it)
|
||||
}
|
||||
}).reversed()
|
||||
@ -118,9 +142,17 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
||||
fun getGradesPointsStatistics(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
subjectName: String,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = pointsMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(pointsCacheKey, semester)) },
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
|
@ -30,16 +30,19 @@ class HomeworkRepository @Inject constructor(
|
||||
private val cacheKey = "homework"
|
||||
|
||||
fun getHomework(
|
||||
student: Student, semester: Semester,
|
||||
start: LocalDate, end: LocalDate,
|
||||
forceRefresh: Boolean, notify: Boolean = false
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, semester, start, end)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
homeworkDb.loadAll(
|
||||
|
@ -23,11 +23,17 @@ class LuckyNumberRepository @Inject constructor(
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getLuckyNumber(student: Student, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
fun getLuckyNumber(
|
||||
student: Student,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { luckyNumberDb.load(student.studentId, now()) },
|
||||
fetch = { sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student) },
|
||||
fetch = {
|
||||
sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (new != old) {
|
||||
old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
||||
@ -41,9 +47,11 @@ class LuckyNumberRepository @Inject constructor(
|
||||
fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) =
|
||||
luckyNumberDb.getAll(student.studentId, start, end)
|
||||
|
||||
suspend fun getNotNotifiedLuckyNumber(student: Student) = luckyNumberDb.load(student.studentId, now()).map {
|
||||
if (it?.isNotified == false) it else null
|
||||
}.first()
|
||||
suspend fun getNotNotifiedLuckyNumber(student: Student) =
|
||||
luckyNumberDb.load(student.studentId, now()).map {
|
||||
if (it?.isNotified == false) it else null
|
||||
}.first()
|
||||
|
||||
suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) = luckyNumberDb.updateAll(listOfNotNull(luckyNumber))
|
||||
suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) =
|
||||
luckyNumberDb.updateAll(listOfNotNull(luckyNumber))
|
||||
}
|
||||
|
@ -51,14 +51,18 @@ class MessageRepository @Inject constructor(
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun getMessages(
|
||||
student: Student, semester: Semester,
|
||||
folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
folder: MessageFolder,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
): Flow<Resource<List<Message>>> = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(
|
||||
getRefreshKey(cacheKey, student, folder)
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, student, folder)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
||||
fetch = {
|
||||
@ -77,7 +81,8 @@ class MessageRepository @Inject constructor(
|
||||
)
|
||||
|
||||
private fun getMessagesWithReadByChange(
|
||||
old: List<Message>, new: List<Message>,
|
||||
old: List<Message>,
|
||||
new: List<Message>,
|
||||
setNotified: Boolean
|
||||
): List<Message> {
|
||||
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
|
||||
@ -96,7 +101,9 @@ class MessageRepository @Inject constructor(
|
||||
}
|
||||
|
||||
fun getMessage(
|
||||
student: Student, message: Message, markAsRead: Boolean = false
|
||||
student: Student,
|
||||
message: Message,
|
||||
markAsRead: Boolean = false,
|
||||
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
|
||||
shouldFetch = {
|
||||
checkNotNull(it, { "This message no longer exist!" })
|
||||
@ -135,8 +142,10 @@ class MessageRepository @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun sendMessage(
|
||||
student: Student, subject: String, content: String,
|
||||
recipients: List<Recipient>
|
||||
student: Student,
|
||||
subject: String,
|
||||
content: String,
|
||||
recipients: List<Recipient>,
|
||||
): SentMessage = sdk.init(student).sendMessage(
|
||||
subject = subject,
|
||||
content = content,
|
||||
|
@ -28,9 +28,16 @@ class MobileDeviceRepository @Inject constructor(
|
||||
|
||||
private val cacheKey = "devices"
|
||||
|
||||
fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
fun getDevices(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student)) },
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
|
@ -12,7 +12,6 @@ import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -28,9 +27,19 @@ class NoteRepository @Inject constructor(
|
||||
|
||||
private val cacheKey = "note"
|
||||
|
||||
fun getNotes(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
fun getNotes(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
getRefreshKey(cacheKey, semester)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { noteDb.loadAll(student.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
|
@ -0,0 +1,17 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.db.dao.NotificationDao
|
||||
import io.github.wulkanowy.data.db.entities.Notification
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class NotificationRepository @Inject constructor(
|
||||
private val notificationDao: NotificationDao,
|
||||
) {
|
||||
|
||||
fun getNotifications(studentId: Long) = notificationDao.loadAll(studentId)
|
||||
|
||||
suspend fun saveNotification(notification: Notification) =
|
||||
notificationDao.insertAll(listOf(notification))
|
||||
}
|
@ -14,7 +14,6 @@ import io.github.wulkanowy.sdk.toLocalDate
|
||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
|
||||
import io.github.wulkanowy.utils.toTimestamp
|
||||
import io.github.wulkanowy.utils.toLocalDateTime
|
||||
import io.github.wulkanowy.utils.toTimestamp
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@ -108,6 +107,22 @@ class PreferencesRepository @Inject constructor(
|
||||
R.bool.pref_default_notification_upcoming_lessons_enable
|
||||
)
|
||||
|
||||
val isUpcomingLessonsNotificationsPersistentKey =
|
||||
context.getString(R.string.pref_key_notifications_upcoming_lessons_persistent)
|
||||
val isUpcomingLessonsNotificationsPersistent: Boolean
|
||||
get() = getBoolean(
|
||||
isUpcomingLessonsNotificationsPersistentKey,
|
||||
R.bool.pref_default_notification_upcoming_lessons_persistent
|
||||
)
|
||||
|
||||
val isNotificationPiggybackEnabledKey =
|
||||
context.getString(R.string.pref_key_notifications_piggyback)
|
||||
val isNotificationPiggybackEnabled: Boolean
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_notifications_piggyback,
|
||||
R.bool.pref_default_notification_piggyback
|
||||
)
|
||||
|
||||
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
|
||||
val isDebugNotificationEnable: Boolean
|
||||
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
||||
@ -176,10 +191,8 @@ class PreferencesRepository @Inject constructor(
|
||||
)
|
||||
|
||||
var lasSyncDate: LocalDateTime
|
||||
get() = getLong(
|
||||
R.string.pref_key_last_sync_date,
|
||||
R.string.pref_default_last_sync_date
|
||||
).toLocalDateTime()
|
||||
get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date)
|
||||
.toLocalDateTime()
|
||||
set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply()
|
||||
|
||||
var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
|
||||
@ -230,8 +243,10 @@ class PreferencesRepository @Inject constructor(
|
||||
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
|
||||
|
||||
var inAppReviewDate: LocalDate?
|
||||
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }?.toLocalDate()
|
||||
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp()).apply()
|
||||
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }
|
||||
?.toLocalDate()
|
||||
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp())
|
||||
.apply()
|
||||
|
||||
var isAppReviewDone: Boolean
|
||||
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)
|
||||
|
@ -7,6 +7,8 @@ import io.github.wulkanowy.data.db.entities.ReportingUnit
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import javax.inject.Inject
|
||||
@ -15,26 +17,34 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class RecipientRepository @Inject constructor(
|
||||
private val recipientDb: RecipientDao,
|
||||
private val sdk: Sdk
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val cacheKey = "recipient"
|
||||
|
||||
suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) {
|
||||
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
|
||||
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||
|
||||
recipientDb.deleteAll(old uniqueSubtract new)
|
||||
recipientDb.insertAll(new uniqueSubtract old)
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
|
||||
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> {
|
||||
return recipientDb.loadAll(unit.studentId, unit.unitId, role).ifEmpty {
|
||||
refreshRecipients(student, unit, role)
|
||||
val cached = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
return if (cached.isEmpty() || isExpired) {
|
||||
refreshRecipients(student, unit, role)
|
||||
recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||
}
|
||||
} else cached
|
||||
}
|
||||
|
||||
suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> {
|
||||
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId).mapToEntities(student.studentId)
|
||||
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId)
|
||||
.mapToEntities(student.studentId)
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ class RecoverRepository @Inject constructor(private val sdk: Sdk) {
|
||||
return sdk.getPasswordResetCaptchaCode(host, symbol)
|
||||
}
|
||||
|
||||
suspend fun sendRecoverRequest(url: String, symbol: String, email: String, reCaptchaResponse: String): String {
|
||||
return sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
||||
}
|
||||
suspend fun sendRecoverRequest(
|
||||
url: String, symbol: String, email: String, reCaptchaResponse: String
|
||||
): String = sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
@ -12,7 +11,6 @@ import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -30,17 +28,15 @@ class SchoolAnnouncementRepository @Inject constructor(
|
||||
|
||||
fun getSchoolAnnouncements(
|
||||
student: Student,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false
|
||||
forceRefresh: Boolean, notify: Boolean = false
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
it.isEmpty() || forceRefresh
|
||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
schoolAnnouncementDb.loadAll(
|
||||
student.studentId)
|
||||
schoolAnnouncementDb.loadAll(student.studentId)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
@ -57,9 +53,11 @@ class SchoolAnnouncementRepository @Inject constructor(
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
)
|
||||
|
||||
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
||||
return schoolAnnouncementDb.loadAll(student.studentId)
|
||||
}
|
||||
|
||||
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) = schoolAnnouncementDb.updateAll(schoolAnnouncement)
|
||||
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =
|
||||
schoolAnnouncementDb.updateAll(schoolAnnouncement)
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntity
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@ -14,29 +16,41 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class SchoolRepository @Inject constructor(
|
||||
private val schoolDb: SchoolDao,
|
||||
private val sdk: Sdk
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
|
||||
.mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(schoolDb) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
} else if (old == null) {
|
||||
schoolDb.insertAll(listOf(new))
|
||||
private val cacheKey = "school_info"
|
||||
|
||||
fun getSchoolInfo(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, student)
|
||||
)
|
||||
it == null || forceRefresh || isExpired
|
||||
},
|
||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
|
||||
.mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(schoolDb) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
} else if (old == null) {
|
||||
schoolDb.insertAll(listOf(new))
|
||||
}
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -19,24 +19,27 @@ class StudentInfoRepository @Inject constructor(
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getStudentInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getStudentInfo().mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(studentInfoDao) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
} else if (old == null) {
|
||||
studentInfoDao.insertAll(listOf(new))
|
||||
fun getStudentInfo(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getStudentInfo().mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(studentInfoDao) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
} else if (old == null) {
|
||||
studentInfoDao.insertAll(listOf(new))
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
@ -15,14 +17,24 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class SubjectRepository @Inject constructor(
|
||||
private val subjectDao: SubjectDao,
|
||||
private val sdk: Sdk
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getSubjects(student: Student, semester: Semester, forceRefresh: Boolean = false) = networkBoundResource(
|
||||
private val cacheKey = "subjects"
|
||||
|
||||
fun getSubjects(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
@ -31,6 +43,8 @@ class SubjectRepository @Inject constructor(
|
||||
saveFetchResult = { old, new ->
|
||||
subjectDao.deleteAll(old uniqueSubtract new)
|
||||
subjectDao.insertAll(new uniqueSubtract old)
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
@ -15,14 +17,24 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class TeacherRepository @Inject constructor(
|
||||
private val teacherDb: TeacherDao,
|
||||
private val sdk: Sdk
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getTeachers(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
private val cacheKey = "teachers"
|
||||
|
||||
fun getTeachers(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
@ -32,6 +44,8 @@ class TeacherRepository @Inject constructor(
|
||||
saveFetchResult = { old, new ->
|
||||
teacherDb.deleteAll(old uniqueSubtract new)
|
||||
teacherDb.insertAll(new uniqueSubtract old)
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -41,18 +41,22 @@ class TimetableRepository @Inject constructor(
|
||||
private val cacheKey = "timetable"
|
||||
|
||||
fun getTimetable(
|
||||
student: Student, semester: Semester, start: LocalDate, end: LocalDate,
|
||||
forceRefresh: Boolean, refreshAdditional: Boolean = false
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
refreshAdditional: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { (timetable, additional, headers) ->
|
||||
val refreshKey = getRefreshKey(cacheKey, semester, start, end)
|
||||
val isShouldRefresh = refreshHelper.isShouldBeRefreshed(refreshKey)
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(refreshKey)
|
||||
val isRefreshAdditional = additional.isEmpty() && refreshAdditional
|
||||
|
||||
val isNoData = timetable.isEmpty() || isRefreshAdditional || headers.isEmpty()
|
||||
|
||||
isNoData || forceRefresh || isShouldRefresh
|
||||
isNoData || forceRefresh || isExpired
|
||||
},
|
||||
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
||||
fetch = {
|
||||
@ -79,8 +83,10 @@ class TimetableRepository @Inject constructor(
|
||||
)
|
||||
|
||||
private fun getFullTimetableFromDatabase(
|
||||
student: Student, semester: Semester,
|
||||
start: LocalDate, end: LocalDate
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
): Flow<TimetableFull> {
|
||||
val timetableFlow = timetableDb.loadAll(
|
||||
diaryId = semester.diaryId,
|
||||
@ -113,20 +119,11 @@ class TimetableRepository @Inject constructor(
|
||||
|
||||
private suspend fun refreshTimetable(
|
||||
student: Student,
|
||||
lessonsOld: List<Timetable>, lessonsNew: List<Timetable>
|
||||
lessonsOld: List<Timetable>,
|
||||
lessonsNew: List<Timetable>,
|
||||
) {
|
||||
val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew
|
||||
val lessonsToAdd = (lessonsNew uniqueSubtract lessonsOld).map { new ->
|
||||
val matchingOld = lessonsOld.singleOrNull { new.start == it.start }
|
||||
if (matchingOld != null) {
|
||||
val useOldTeacher = new.teacher.isEmpty() && !new.changes && !matchingOld.changes
|
||||
new.copy(
|
||||
room = if (new.room.isEmpty()) matchingOld.room else new.room,
|
||||
teacher = if (useOldTeacher) matchingOld.teacher
|
||||
else new.teacher
|
||||
)
|
||||
} else new
|
||||
}
|
||||
val lessonsToAdd = lessonsNew uniqueSubtract lessonsOld
|
||||
|
||||
timetableDb.deleteAll(lessonsToRemove)
|
||||
timetableDb.insertAll(lessonsToAdd)
|
||||
|
@ -11,6 +11,7 @@ import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.services.HiltBroadcastReceiver
|
||||
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
|
||||
@ -32,6 +33,9 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||
@Inject
|
||||
lateinit var studentRepository: StudentRepository
|
||||
|
||||
@Inject
|
||||
lateinit var preferencesRepository: PreferencesRepository
|
||||
|
||||
companion object {
|
||||
const val NOTIFICATION_TYPE_CURRENT = 1
|
||||
const val NOTIFICATION_TYPE_UPCOMING = 2
|
||||
@ -68,6 +72,7 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||
private fun prepareNotification(context: Context, intent: Intent) {
|
||||
val type = intent.getIntExtra(LESSON_TYPE, 0)
|
||||
val notificationId = intent.getIntExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||
val isPersistent = preferencesRepository.isUpcomingLessonsNotificationsPersistent
|
||||
|
||||
if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) {
|
||||
return NotificationManagerCompat.from(context).cancel(notificationId)
|
||||
@ -87,33 +92,57 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||
|
||||
Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId")
|
||||
|
||||
showNotification(context, notificationId, studentName,
|
||||
showNotification(context, notificationId, isPersistent, studentName,
|
||||
if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start,
|
||||
context.getString(if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next, "($room) $subject".removePrefix("()")),
|
||||
nextSubject?.let { context.getString(R.string.timetable_later, "($nextRoom) $nextSubject".removePrefix("()")) }
|
||||
context.getString(
|
||||
if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next,
|
||||
"($room) $subject".removePrefix("()")
|
||||
),
|
||||
nextSubject?.let {
|
||||
context.getString(
|
||||
R.string.timetable_later,
|
||||
"($nextRoom) $nextSubject".removePrefix("()")
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun showNotification(context: Context, notificationId: Int, studentName: String?, countDown: Long, timeout: Long, title: String, next: String?) {
|
||||
NotificationManagerCompat.from(context).notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setContentTitle(title)
|
||||
.setContentText(next)
|
||||
.setAutoCancel(false)
|
||||
.setOngoing(true)
|
||||
.setWhen(countDown)
|
||||
.apply {
|
||||
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
||||
}
|
||||
.setTimeoutAfter(timeout)
|
||||
.setSmallIcon(R.drawable.ic_stat_timetable)
|
||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||
.setStyle(NotificationCompat.InboxStyle().also {
|
||||
it.setSummaryText(studentName)
|
||||
it.addLine(next)
|
||||
})
|
||||
.setContentIntent(PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id,
|
||||
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT))
|
||||
.build()
|
||||
)
|
||||
private fun showNotification(
|
||||
context: Context,
|
||||
notificationId: Int,
|
||||
isPersistent: Boolean,
|
||||
studentName: String?,
|
||||
countDown: Long,
|
||||
timeout: Long,
|
||||
title: String,
|
||||
next: String?
|
||||
) {
|
||||
NotificationManagerCompat.from(context)
|
||||
.notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setContentTitle(title)
|
||||
.setContentText(next)
|
||||
.setAutoCancel(false)
|
||||
.setWhen(countDown)
|
||||
.setOngoing(isPersistent)
|
||||
.apply {
|
||||
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
||||
}
|
||||
.setTimeoutAfter(timeout)
|
||||
.setSmallIcon(R.drawable.ic_stat_timetable)
|
||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||
.setStyle(NotificationCompat.InboxStyle().also {
|
||||
it.setSummaryText(studentName)
|
||||
it.addLine(next)
|
||||
})
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
MainView.Section.TIMETABLE.id,
|
||||
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true),
|
||||
FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import io.github.wulkanowy.utils.nickOrName
|
||||
import io.github.wulkanowy.utils.toTimestamp
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalDateTime.now
|
||||
import javax.inject.Inject
|
||||
@ -57,10 +58,13 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
|
||||
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
|
||||
cancelScheduledTo(
|
||||
upcomingTime..lesson.start,
|
||||
getRequestCode(upcomingTime, studentId)
|
||||
range = upcomingTime..lesson.start,
|
||||
requestCode = getRequestCode(upcomingTime, studentId)
|
||||
)
|
||||
cancelScheduledTo(
|
||||
range = lesson.start..lesson.end,
|
||||
requestCode = getRequestCode(lesson.start, studentId)
|
||||
)
|
||||
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId))
|
||||
|
||||
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
|
||||
}
|
||||
@ -82,6 +86,11 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
return cancelScheduled(lessons, student)
|
||||
}
|
||||
|
||||
if (lessons.firstOrNull()?.date?.isAfter(LocalDate.now().plusDays(2)) == true) {
|
||||
Timber.d("Timetable notification scheduling skipped - lessons are too far")
|
||||
return
|
||||
}
|
||||
|
||||
withContext(dispatchersProvider.backgroundThread) {
|
||||
lessons.groupBy { it.date }
|
||||
.map { it.value.sortedBy { lesson -> lesson.start } }
|
||||
@ -96,26 +105,26 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
|
||||
if (lesson.start > now()) {
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_UPCOMING,
|
||||
getUpcomingLessonTime(index, active, lesson)
|
||||
intent = intent,
|
||||
studentId = student.studentId,
|
||||
notificationType = NOTIFICATION_TYPE_UPCOMING,
|
||||
time = getUpcomingLessonTime(index, active, lesson)
|
||||
)
|
||||
}
|
||||
|
||||
if (lesson.end > now()) {
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_CURRENT,
|
||||
lesson.start
|
||||
intent = intent,
|
||||
studentId = student.studentId,
|
||||
notificationType = NOTIFICATION_TYPE_CURRENT,
|
||||
time = lesson.start
|
||||
)
|
||||
if (active.lastIndex == index) {
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
||||
lesson.end
|
||||
intent = intent,
|
||||
studentId = student.studentId,
|
||||
notificationType = NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
||||
time = lesson.end
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -143,17 +152,21 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
notificationType: Int,
|
||||
time: LocalDateTime
|
||||
) {
|
||||
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
||||
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||
it.putExtra(LESSON_TYPE, notificationType)
|
||||
}, FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
Timber.d(
|
||||
"TimetableNotification scheduled: type: $notificationType, subject: ${
|
||||
intent.getStringExtra(LESSON_TITLE)
|
||||
}, start: $time, student: $studentId"
|
||||
)
|
||||
try {
|
||||
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
||||
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||
it.putExtra(LESSON_TYPE, notificationType)
|
||||
}, FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
Timber.d(
|
||||
"TimetableNotification scheduled: type: $notificationType, subject: ${
|
||||
intent.getStringExtra(LESSON_TITLE)
|
||||
}, start: $time, student: $studentId"
|
||||
)
|
||||
} catch (e: IllegalStateException) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
package io.github.wulkanowy.services.piggyback
|
||||
|
||||
import android.service.notification.NotificationListenerService
|
||||
import android.service.notification.StatusBarNotification
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.services.sync.SyncManager
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class VulcanNotificationListenerService : NotificationListenerService() {
|
||||
|
||||
@Inject
|
||||
lateinit var syncManager: SyncManager
|
||||
|
||||
@Inject
|
||||
lateinit var preferenceRepository: PreferencesRepository
|
||||
|
||||
override fun onNotificationPosted(statusBarNotification: StatusBarNotification?) {
|
||||
if (statusBarNotification?.packageName == "pl.edu.vulcan.hebe" && preferenceRepository.isNotificationPiggybackEnabled) {
|
||||
syncManager.startOneTimeSyncWorker()
|
||||
}
|
||||
}
|
||||
}
|
@ -57,14 +57,20 @@ class SyncManager @Inject constructor(
|
||||
|
||||
fun startPeriodicSyncWorker(restart: Boolean = false) {
|
||||
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
|
||||
workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
||||
PeriodicWorkRequestBuilder<SyncWorker>(preferencesRepository.servicesInterval, MINUTES)
|
||||
val serviceInterval = preferencesRepository.servicesInterval
|
||||
|
||||
workManager.enqueueUniquePeriodicWork(
|
||||
SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
||||
PeriodicWorkRequestBuilder<SyncWorker>(serviceInterval, MINUTES)
|
||||
.setInitialDelay(10, MINUTES)
|
||||
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
|
||||
.setConstraints(Constraints.Builder()
|
||||
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
||||
.build())
|
||||
.build())
|
||||
.setConstraints(
|
||||
Constraints.Builder()
|
||||
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,7 +83,11 @@ class SyncManager @Inject constructor(
|
||||
)
|
||||
.build()
|
||||
|
||||
workManager.enqueueUniqueWork("${SyncWorker::class.java.simpleName}_one_time", ExistingWorkPolicy.REPLACE, work)
|
||||
workManager.enqueueUniqueWork(
|
||||
"${SyncWorker::class.java.simpleName}_one_time",
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
work
|
||||
)
|
||||
|
||||
return workManager.getWorkInfoByIdLiveData(work.id).asFlow()
|
||||
}
|
||||
|
@ -0,0 +1,145 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Notification
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.data.pojos.NotificationData
|
||||
import io.github.wulkanowy.data.pojos.OneNotificationData
|
||||
import io.github.wulkanowy.data.repositories.NotificationRepository
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.getCompatBitmap
|
||||
import io.github.wulkanowy.utils.getCompatColor
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import java.time.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
import kotlin.random.Random
|
||||
|
||||
class AppNotificationManager @Inject constructor(
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val appInfo: AppInfo,
|
||||
private val notificationRepository: NotificationRepository
|
||||
) {
|
||||
|
||||
suspend fun sendNotification(notificationData: NotificationData, student: Student) =
|
||||
when (notificationData) {
|
||||
is OneNotificationData -> sendOneNotification(notificationData, student)
|
||||
is MultipleNotificationsData -> sendMultipleNotifications(notificationData, student)
|
||||
}
|
||||
|
||||
private suspend fun sendOneNotification(
|
||||
notificationData: OneNotificationData,
|
||||
student: Student
|
||||
) {
|
||||
val content = context.getString(
|
||||
notificationData.contentStringRes,
|
||||
*notificationData.contentValues.toTypedArray()
|
||||
)
|
||||
|
||||
val title = context.getString(notificationData.titleStringRes)
|
||||
|
||||
val notification = getDefaultNotificationBuilder(notificationData)
|
||||
.setContentTitle(title)
|
||||
.setContentText(content)
|
||||
.setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.setSummaryText(student.nickOrName)
|
||||
.bigText(content)
|
||||
)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), notification)
|
||||
|
||||
saveNotification(title, content, notificationData, student)
|
||||
}
|
||||
|
||||
private suspend fun sendMultipleNotifications(
|
||||
notificationData: MultipleNotificationsData,
|
||||
student: Student
|
||||
) {
|
||||
val groupType = notificationData.type.group ?: return
|
||||
val group = "${groupType}_${student.id}"
|
||||
val groupId = student.id * 100 + notificationData.type.ordinal
|
||||
|
||||
notificationData.lines.forEach { item ->
|
||||
val title = context.resources.getQuantityString(notificationData.titleStringRes, 1)
|
||||
|
||||
val notification = getDefaultNotificationBuilder(notificationData)
|
||||
.setContentTitle(title)
|
||||
.setContentText(item)
|
||||
.setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.setSummaryText(student.nickOrName)
|
||||
.bigText(item)
|
||||
)
|
||||
.setGroup(group)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), notification)
|
||||
|
||||
saveNotification(title, item, notificationData, student)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
|
||||
|
||||
val summaryNotification = getDefaultNotificationBuilder(notificationData)
|
||||
.setSmallIcon(notificationData.icon)
|
||||
.setGroup(group)
|
||||
.setStyle(NotificationCompat.InboxStyle().setSummaryText(student.nickOrName))
|
||||
.setGroupSummary(true)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(groupId.toInt(), summaryNotification)
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private fun getDefaultNotificationBuilder(notificationData: NotificationData): NotificationCompat.Builder {
|
||||
val pendingIntentsFlags = if (appInfo.systemVersion >= Build.VERSION_CODES.M) {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
}
|
||||
|
||||
return NotificationCompat.Builder(context, notificationData.type.channel)
|
||||
.setLargeIcon(context.getCompatBitmap(notificationData.icon, R.color.colorPrimary))
|
||||
.setSmallIcon(R.drawable.ic_stat_all)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
notificationData.startMenu.id,
|
||||
MainActivity.getStartIntent(context, notificationData.startMenu, true),
|
||||
pendingIntentsFlags
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun saveNotification(
|
||||
title: String,
|
||||
content: String,
|
||||
notificationData: NotificationData,
|
||||
student: Student
|
||||
) {
|
||||
val notificationEntity = Notification(
|
||||
studentId = student.id,
|
||||
title = title,
|
||||
content = content,
|
||||
type = notificationData.type,
|
||||
date = LocalDateTime.now()
|
||||
)
|
||||
|
||||
notificationRepository.saveNotification(notificationEntity)
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.Notification
|
||||
import io.github.wulkanowy.data.pojos.OneNotification
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.utils.getCompatBitmap
|
||||
import io.github.wulkanowy.utils.getCompatColor
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import kotlin.random.Random
|
||||
|
||||
abstract class BaseNotification(
|
||||
private val context: Context,
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
) {
|
||||
|
||||
protected fun sendNotification(notification: Notification, student: Student) =
|
||||
when (notification) {
|
||||
is OneNotification -> sendOneNotification(notification, student)
|
||||
is MultipleNotifications -> sendMultipleNotifications(notification, student)
|
||||
}
|
||||
|
||||
private fun sendOneNotification(notification: OneNotification, student: Student?) {
|
||||
notificationManager.notify(
|
||||
Random.nextInt(Int.MAX_VALUE),
|
||||
getNotificationBuilder(notification).apply {
|
||||
val content = context.getString(
|
||||
notification.contentStringRes,
|
||||
*notification.contentValues.toTypedArray()
|
||||
)
|
||||
setContentTitle(context.getString(notification.titleStringRes))
|
||||
setContentText(content)
|
||||
setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.setSummaryText(student?.nickOrName)
|
||||
.bigText(content)
|
||||
)
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendMultipleNotifications(notification: MultipleNotifications, student: Student) {
|
||||
val group = notification.type.group + student.id
|
||||
val groupId = student.id * 100 + notification.type.ordinal
|
||||
|
||||
notification.lines.forEach { item ->
|
||||
notificationManager.notify(
|
||||
Random.nextInt(Int.MAX_VALUE),
|
||||
getNotificationBuilder(notification).apply {
|
||||
setContentTitle(getQuantityString(notification.titleStringRes, 1))
|
||||
setContentText(item)
|
||||
setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.setSummaryText(student.nickOrName)
|
||||
.bigText(item)
|
||||
)
|
||||
setGroup(group)
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
|
||||
|
||||
notificationManager.notify(
|
||||
groupId.toInt(),
|
||||
getNotificationBuilder(notification).apply {
|
||||
setSmallIcon(notification.icon)
|
||||
setGroup(group)
|
||||
setStyle(NotificationCompat.InboxStyle().setSummaryText(student.nickOrName))
|
||||
setGroupSummary(true)
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun getNotificationBuilder(notification: Notification) = NotificationCompat
|
||||
.Builder(context, notification.type.channel)
|
||||
.setLargeIcon(context.getCompatBitmap(notification.icon, R.color.colorPrimary))
|
||||
.setSmallIcon(R.drawable.ic_stat_all)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context, notification.startMenu.id,
|
||||
MainActivity.getStartIntent(context, notification.startMenu, true),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
|
||||
private fun getQuantityString(@PluralsRes id: Int, value: Int): String {
|
||||
return context.resources.getQuantityString(id, value, value)
|
||||
}
|
||||
}
|
@ -1,29 +1,25 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Conference
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import java.time.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewConferenceNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<Conference>, student: Student) {
|
||||
suspend fun notify(items: List<Conference>, student: Student) {
|
||||
val today = LocalDateTime.now()
|
||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
|
||||
}.ifEmpty { return }
|
||||
|
||||
val notification = MultipleNotifications(
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_CONFERENCE,
|
||||
icon = R.drawable.ic_more_conferences,
|
||||
titleStringRes = R.plurals.conference_notify_new_item_title,
|
||||
@ -33,6 +29,6 @@ class NewConferenceNotification @Inject constructor(
|
||||
lines = lines
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,25 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewExamNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<Exam>, student: Student) {
|
||||
suspend fun notify(items: List<Exam>, student: Student) {
|
||||
val today = LocalDate.now()
|
||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
|
||||
}.ifEmpty { return }
|
||||
|
||||
val notification = MultipleNotifications(
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_EXAM,
|
||||
icon = R.drawable.ic_main_exam,
|
||||
titleStringRes = R.plurals.exam_notify_new_item_title,
|
||||
@ -33,6 +29,6 @@ class NewExamNotification @Inject constructor(
|
||||
lines = lines
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,19 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewGradeNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notifyDetails(items: List<Grade>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notifyDetails(items: List<Grade>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_GRADE_DETAILS,
|
||||
icon = R.drawable.ic_stat_grade,
|
||||
titleStringRes = R.plurals.grade_new_items,
|
||||
@ -29,11 +25,11 @@ class NewGradeNotification @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
|
||||
fun notifyPredicted(items: List<GradeSummary>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notifyPredicted(items: List<GradeSummary>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_GRADE_PREDICTED,
|
||||
icon = R.drawable.ic_stat_grade,
|
||||
titleStringRes = R.plurals.grade_new_items_predicted,
|
||||
@ -45,11 +41,11 @@ class NewGradeNotification @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
|
||||
fun notifyFinal(items: List<GradeSummary>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notifyFinal(items: List<GradeSummary>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_GRADE_FINAL,
|
||||
icon = R.drawable.ic_stat_grade,
|
||||
titleStringRes = R.plurals.grade_new_items_final,
|
||||
@ -61,6 +57,6 @@ class NewGradeNotification @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,25 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Homework
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewHomeworkNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<Homework>, student: Student) {
|
||||
suspend fun notify(items: List<Homework>, student: Student) {
|
||||
val today = LocalDate.now()
|
||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
|
||||
}.ifEmpty { return }
|
||||
|
||||
val notification = MultipleNotifications(
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_HOMEWORK,
|
||||
icon = R.drawable.ic_more_homework,
|
||||
titleStringRes = R.plurals.homework_notify_new_item_title,
|
||||
@ -33,6 +29,6 @@ class NewHomeworkNotification @Inject constructor(
|
||||
lines = lines
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,26 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.OneNotification
|
||||
import io.github.wulkanowy.data.pojos.OneNotificationData
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewLuckyNumberNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(item: LuckyNumber, student: Student) {
|
||||
val notification = OneNotification(
|
||||
type = NotificationType.NEW_LUCKY_NUMBER,
|
||||
icon = R.drawable.ic_stat_luckynumber,
|
||||
titleStringRes = R.string.lucky_number_notify_new_item_title,
|
||||
contentStringRes = R.string.lucky_number_notify_new_item,
|
||||
startMenu = MainView.Section.LUCKY_NUMBER,
|
||||
contentValues = listOf(item.luckyNumber.toString())
|
||||
)
|
||||
suspend fun notify(item: LuckyNumber, student: Student) {
|
||||
val notification = OneNotificationData(
|
||||
type = NotificationType.NEW_LUCKY_NUMBER,
|
||||
icon = R.drawable.ic_stat_luckynumber,
|
||||
titleStringRes = R.string.lucky_number_notify_new_item_title,
|
||||
contentStringRes = R.string.lucky_number_notify_new_item,
|
||||
startMenu = MainView.Section.LUCKY_NUMBER,
|
||||
contentValues = listOf(item.luckyNumber.toString())
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,18 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewMessageNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<Message>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notify(items: List<Message>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_MESSAGE,
|
||||
icon = R.drawable.ic_stat_message,
|
||||
titleStringRes = R.plurals.message_new_items,
|
||||
@ -28,6 +24,6 @@ class NewMessageNotification @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,19 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Note
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewNoteNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<Note>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notify(items: List<Note>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_NOTE,
|
||||
icon = R.drawable.ic_stat_note,
|
||||
titleStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
||||
@ -41,6 +37,6 @@ class NewNoteNotification @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,29 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewSchoolAnnouncementNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_ANNOUNCEMENT,
|
||||
icon = R.drawable.ic_all_about,
|
||||
titleStringRes = R.plurals.school_announcement_notify_new_item_title,
|
||||
contentStringRes = R.plurals.school_announcement_notify_new_items,
|
||||
summaryStringRes = R.plurals.school_announcement_number_item,
|
||||
startMenu = MainView.Section.SCHOOL_ANNOUNCEMENT,
|
||||
lines = items.map {
|
||||
"${it.subject}: ${it.content}"
|
||||
}
|
||||
suspend fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_ANNOUNCEMENT,
|
||||
icon = R.drawable.ic_all_about,
|
||||
titleStringRes = R.plurals.school_announcement_notify_new_item_title,
|
||||
contentStringRes = R.plurals.school_announcement_notify_new_items,
|
||||
summaryStringRes = R.plurals.school_announcement_number_item,
|
||||
startMenu = MainView.Section.SCHOOL_ANNOUNCEMENT,
|
||||
lines = items.map {
|
||||
"${it.subject}: ${it.content}"
|
||||
}
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,9 @@ import io.github.wulkanowy.services.sync.channels.NewHomeworkChannel
|
||||
import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
|
||||
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
||||
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
|
||||
import io.github.wulkanowy.services.sync.channels.PushChannel
|
||||
|
||||
enum class NotificationType(val group: String, val channel: String) {
|
||||
enum class NotificationType(val group: String?, val channel: String) {
|
||||
NEW_CONFERENCE("new_conferences_group", NewConferencesChannel.CHANNEL_ID),
|
||||
NEW_EXAM("new_exam_group", NewExamChannel.CHANNEL_ID),
|
||||
NEW_GRADE_DETAILS("new_grade_details_group", NewGradesChannel.CHANNEL_ID),
|
||||
@ -20,4 +21,5 @@ enum class NotificationType(val group: String, val channel: String) {
|
||||
NEW_MESSAGE("new_message_group", NewMessagesChannel.CHANNEL_ID),
|
||||
NEW_NOTE("new_notes_group", NewNotesChannel.CHANNEL_ID),
|
||||
NEW_ANNOUNCEMENT("new_school_announcements_group", NewSchoolAnnouncementsChannel.CHANNEL_ID),
|
||||
PUSH(null, PushChannel.CHANNEL_ID)
|
||||
}
|
||||
|
@ -9,9 +9,12 @@ import io.github.wulkanowy.utils.waitForResult
|
||||
import java.time.LocalDate.now
|
||||
import javax.inject.Inject
|
||||
|
||||
class AttendanceWork @Inject constructor(private val attendanceRepository: AttendanceRepository) : Work {
|
||||
class AttendanceWork @Inject constructor(
|
||||
private val attendanceRepository: AttendanceRepository
|
||||
) : Work {
|
||||
|
||||
override suspend fun doWork(student: Student, semester: Semester) {
|
||||
attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true).waitForResult()
|
||||
attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true)
|
||||
.waitForResult()
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||
import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment
|
||||
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
||||
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
||||
import io.github.wulkanowy.utils.capitalise
|
||||
@ -120,6 +121,7 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.dashboard_menu_tiles -> presenter.onDashboardTileSettingsSelected()
|
||||
R.id.dashboard_menu_notifaction_list -> presenter.onNotificationsCenterSelected()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@ -182,6 +184,10 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||
if (::presenter.isInitialized) presenter.onViewReselected()
|
||||
}
|
||||
|
||||
override fun openNotificationsCenterView() {
|
||||
(requireActivity() as MainActivity).pushView(NotificationsCenterFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
dashboardAdapter.clearTimers()
|
||||
presenter.onDetachView()
|
||||
|
@ -209,6 +209,11 @@ class DashboardPresenter @Inject constructor(
|
||||
view?.showErrorDetailsDialog(lastError)
|
||||
}
|
||||
|
||||
fun onNotificationsCenterSelected(): Boolean {
|
||||
view?.openNotificationsCenterView()
|
||||
return true
|
||||
}
|
||||
|
||||
fun onDashboardTileSettingsSelected(): Boolean {
|
||||
view?.showDashboardTileSettings(preferencesRepository.selectedDashboardTiles.toList())
|
||||
return true
|
||||
|
@ -23,4 +23,6 @@ interface DashboardView : BaseView {
|
||||
fun resetView()
|
||||
|
||||
fun popViewToRoot()
|
||||
|
||||
fun openNotificationsCenterView()
|
||||
}
|
@ -87,7 +87,7 @@ class NotificationDebugPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun withStudent(block: (Student) -> Unit) {
|
||||
private fun withStudent(block: suspend (Student) -> Unit) {
|
||||
launch {
|
||||
block(studentRepository.getCurrentStudent(false))
|
||||
}
|
||||
|
@ -131,7 +131,9 @@ class GradeAverageProvider @Inject constructor(
|
||||
val updatedFirstSemesterGrades =
|
||||
firstSemesterSubject?.grades?.updateModifiers(student).orEmpty()
|
||||
|
||||
(updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage(isOptionalArithmeticAverage)
|
||||
(updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage(
|
||||
isOptionalArithmeticAverage
|
||||
)
|
||||
} else {
|
||||
secondSemesterSubject.average
|
||||
}
|
||||
@ -147,7 +149,8 @@ class GradeAverageProvider @Inject constructor(
|
||||
|
||||
return if (!isAnyVulcanAverage || isGradeAverageForceCalc) {
|
||||
val secondSemesterAverage =
|
||||
secondSemesterSubject.grades.updateModifiers(student).calcAverage(isOptionalArithmeticAverage)
|
||||
secondSemesterSubject.grades.updateModifiers(student)
|
||||
.calcAverage(isOptionalArithmeticAverage)
|
||||
val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student)
|
||||
?.calcAverage(isOptionalArithmeticAverage) ?: secondSemesterAverage
|
||||
|
||||
@ -213,7 +216,8 @@ class GradeAverageProvider @Inject constructor(
|
||||
proposedPoints = "",
|
||||
finalPoints = "",
|
||||
pointsSum = "",
|
||||
average = if (calcAverage) details.updateModifiers(student).calcAverage(isOptionalArithmeticAverage) else .0
|
||||
average = if (calcAverage) details.updateModifiers(student)
|
||||
.calcAverage(isOptionalArithmeticAverage) else .0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.databinding.ItemGradeSummaryBinding
|
||||
import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding
|
||||
import io.github.wulkanowy.utils.calcAverage
|
||||
import io.github.wulkanowy.utils.calcFinalAverage
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -25,6 +25,10 @@ class GradeSummaryAdapter @Inject constructor(
|
||||
|
||||
var items = emptyList<GradeSummary>()
|
||||
|
||||
var onCalculatedHelpClickListener: () -> Unit = {}
|
||||
|
||||
var onFinalHelpClickListener: () -> Unit = {}
|
||||
|
||||
override fun getItemCount() = items.size + if (items.isNotEmpty()) 1 else 0
|
||||
|
||||
override fun getItemViewType(position: Int) = when (position) {
|
||||
@ -60,7 +64,7 @@ class GradeSummaryAdapter @Inject constructor(
|
||||
val finalItemsCount = items.count { it.finalGrade.matches("[0-6][+-]?".toRegex()) }
|
||||
val calculatedItemsCount = items.count { value -> value.average != 0.0 }
|
||||
val allItemsCount = items.count { !it.subject.equals("zachowanie", true) }
|
||||
val finalAverage = items.calcAverage(
|
||||
val finalAverage = items.calcFinalAverage(
|
||||
preferencesRepository.gradePlusModifier,
|
||||
preferencesRepository.gradeMinusModifier
|
||||
)
|
||||
@ -83,6 +87,9 @@ class GradeSummaryAdapter @Inject constructor(
|
||||
calculatedItemsCount,
|
||||
allItemsCount
|
||||
)
|
||||
|
||||
gradeSummaryCalculatedAverageHelp.setOnClickListener { onCalculatedHelpClickListener() }
|
||||
gradeSummaryFinalAverageHelp.setOnClickListener { onFinalHelpClickListener() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
@ -48,6 +49,11 @@ class GradeSummaryFragment :
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
with(gradeSummaryAdapter) {
|
||||
onCalculatedHelpClickListener = presenter::onCalculatedAverageHelpClick
|
||||
onFinalHelpClickListener = presenter::onFinalAverageHelpClick
|
||||
}
|
||||
|
||||
with(binding.gradeSummaryRecycler) {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = gradeSummaryAdapter
|
||||
@ -55,7 +61,11 @@ class GradeSummaryFragment :
|
||||
with(binding) {
|
||||
gradeSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
gradeSummarySwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
gradeSummarySwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
||||
gradeSummarySwipe.setProgressBackgroundColorSchemeColor(
|
||||
requireContext().getThemeAttrColor(
|
||||
R.attr.colorSwipeRefresh
|
||||
)
|
||||
)
|
||||
gradeSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
}
|
||||
@ -107,6 +117,22 @@ class GradeSummaryFragment :
|
||||
binding.gradeSummarySwipe.isRefreshing = show
|
||||
}
|
||||
|
||||
override fun showCalculatedAverageHelpDialog() {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.grade_summary_calculated_average_help_dialog_title)
|
||||
.setMessage(R.string.grade_summary_calculated_average_help_dialog_message)
|
||||
.setPositiveButton(R.string.all_close) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun showFinalAverageHelpDialog() {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.grade_summary_final_average_help_dialog_title)
|
||||
.setMessage(R.string.grade_summary_final_average_help_dialog_message)
|
||||
.setPositiveButton(R.string.all_close) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) {
|
||||
presenter.onParentViewLoadData(semesterId, forceRefresh)
|
||||
}
|
||||
|
@ -135,6 +135,14 @@ class GradeSummaryPresenter @Inject constructor(
|
||||
cancelJobs("load")
|
||||
}
|
||||
|
||||
fun onCalculatedAverageHelpClick() {
|
||||
view?.showCalculatedAverageHelpDialog()
|
||||
}
|
||||
|
||||
fun onFinalAverageHelpClick() {
|
||||
view?.showFinalAverageHelpDialog()
|
||||
}
|
||||
|
||||
private fun createGradeSummaryItems(items: List<GradeSubject>): List<GradeSummary> {
|
||||
return items
|
||||
.filter { !checkEmpty(it) }
|
||||
|
@ -33,6 +33,10 @@ interface GradeSummaryView : BaseView {
|
||||
|
||||
fun showEmpty(show: Boolean)
|
||||
|
||||
fun showCalculatedAverageHelpDialog()
|
||||
|
||||
fun showFinalAverageHelpDialog()
|
||||
|
||||
fun notifyParentDataLoaded(semesterId: Int)
|
||||
|
||||
fun notifyParentRefresh()
|
||||
|
@ -103,9 +103,8 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
|
||||
}
|
||||
|
||||
override fun notifyInitSymbolFragment(loginData: Triple<String, String, String>) {
|
||||
(loginAdapter.getFragmentInstance(1) as? LoginSymbolFragment)?.onParentInitSymbolFragment(
|
||||
loginData
|
||||
)
|
||||
(loginAdapter.getFragmentInstance(1) as? LoginSymbolFragment)
|
||||
?.onParentInitSymbolFragment(loginData)
|
||||
}
|
||||
|
||||
override fun notifyInitStudentSelectFragment(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||
|
@ -13,7 +13,7 @@ import javax.inject.Inject
|
||||
|
||||
class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
|
||||
|
||||
var onBadCredentials: () -> Unit = {}
|
||||
var onBadCredentials: (String?) -> Unit = {}
|
||||
|
||||
var onInvalidToken: (String) -> Unit = {}
|
||||
|
||||
@ -25,7 +25,7 @@ class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler
|
||||
|
||||
override fun proceed(error: Throwable) {
|
||||
when (error) {
|
||||
is BadCredentialsException -> onBadCredentials()
|
||||
is BadCredentialsException -> onBadCredentials(error.message)
|
||||
is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student))
|
||||
is TokenDeadException -> onInvalidToken(resources.getString(R.string.login_expired_token))
|
||||
is InvalidTokenException -> onInvalidToken(resources.getString(R.string.login_invalid_token))
|
||||
|
@ -51,10 +51,12 @@ class LoginAdvancedFragment :
|
||||
private lateinit var hostSymbols: Array<String>
|
||||
|
||||
override val formHostValue: String
|
||||
get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty()
|
||||
get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||
.orEmpty()
|
||||
|
||||
override val formHostSymbol: String
|
||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty()
|
||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||
.orEmpty()
|
||||
|
||||
override val formPinValue: String
|
||||
get() = binding.loginFormPin.text.toString().trim()
|
||||
@ -92,39 +94,62 @@ class LoginAdvancedFragment :
|
||||
loginFormSignIn.setOnClickListener { presenter.onSignInClick() }
|
||||
|
||||
loginTypeSwitch.setOnCheckedChangeListener { _, checkedId ->
|
||||
presenter.onLoginModeSelected(when (checkedId) {
|
||||
R.id.loginTypeApi -> Sdk.Mode.API
|
||||
R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER
|
||||
else -> Sdk.Mode.HYBRID
|
||||
})
|
||||
presenter.onLoginModeSelected(
|
||||
when (checkedId) {
|
||||
R.id.loginTypeApi -> Sdk.Mode.API
|
||||
R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER
|
||||
else -> Sdk.Mode.HYBRID
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
loginFormPin.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() }
|
||||
loginFormPass.setOnEditorDoneSignIn { loginFormSignIn.callOnClick() }
|
||||
|
||||
loginFormSymbol.setAdapter(ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, resources.getStringArray(R.array.symbols_values)))
|
||||
loginFormSymbol.setAdapter(
|
||||
ArrayAdapter(
|
||||
requireContext(),
|
||||
android.R.layout.simple_list_item_1,
|
||||
resources.getStringArray(R.array.symbols_values)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
with(binding.loginFormHost) {
|
||||
setText(hostKeys.getOrNull(0).orEmpty())
|
||||
setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys))
|
||||
setAdapter(
|
||||
LoginSymbolAdapter(
|
||||
context,
|
||||
R.layout.support_simple_spinner_dropdown_item,
|
||||
hostKeys
|
||||
)
|
||||
)
|
||||
setOnClickListener { if (binding.loginFormContainer.visibility == GONE) dismissDropDown() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun showMobileApiWarningMessage() {
|
||||
binding.loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_mobile_api)
|
||||
binding.loginFormAdvancedWarningInfo.text =
|
||||
getString(R.string.login_advanced_warning_mobile_api)
|
||||
}
|
||||
|
||||
override fun showScraperWarningMessage() {
|
||||
binding.loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_scraper)
|
||||
binding.loginFormAdvancedWarningInfo.text =
|
||||
getString(R.string.login_advanced_warning_scraper)
|
||||
}
|
||||
|
||||
override fun showHybridWarningMessage() {
|
||||
binding.loginFormAdvancedWarningInfo.text = getString(R.string.login_advanced_warning_hybrid)
|
||||
binding.loginFormAdvancedWarningInfo.text =
|
||||
getString(R.string.login_advanced_warning_hybrid)
|
||||
}
|
||||
|
||||
override fun setDefaultCredentials(username: String, pass: String, symbol: String, token: String, pin: String) {
|
||||
override fun setDefaultCredentials(
|
||||
username: String,
|
||||
pass: String,
|
||||
symbol: String,
|
||||
token: String,
|
||||
pin: String
|
||||
) {
|
||||
with(binding) {
|
||||
loginFormUsername.setText(username)
|
||||
loginFormPass.setText(pass)
|
||||
@ -177,10 +202,10 @@ class LoginAdvancedFragment :
|
||||
}
|
||||
}
|
||||
|
||||
override fun setErrorPassIncorrect() {
|
||||
override fun setErrorPassIncorrect(message: String?) {
|
||||
with(binding.loginFormPassLayout) {
|
||||
requestFocus()
|
||||
error = getString(R.string.login_incorrect_password)
|
||||
error = message ?: getString(R.string.login_incorrect_password)
|
||||
}
|
||||
}
|
||||
|
||||
@ -296,11 +321,13 @@ class LoginAdvancedFragment :
|
||||
}
|
||||
|
||||
override fun notifyParentAccountLogged(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||
(activity as? LoginActivity)?.onFormFragmentAccountLogged(studentsWithSemesters, Triple(
|
||||
binding.loginFormUsername.text.toString(),
|
||||
binding.loginFormPass.text.toString(),
|
||||
resources.getStringArray(R.array.hosts_values)[1]
|
||||
))
|
||||
(activity as? LoginActivity)?.onFormFragmentAccountLogged(
|
||||
studentsWithSemesters, Triple(
|
||||
binding.loginFormUsername.text.toString(),
|
||||
binding.loginFormPass.text.toString(),
|
||||
resources.getStringArray(R.array.hosts_values)[1]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -34,9 +34,9 @@ class LoginAdvancedPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun onBadCredentials() {
|
||||
private fun onBadCredentials(message: String?) {
|
||||
view?.run {
|
||||
setErrorPassIncorrect()
|
||||
setErrorPassIncorrect(message)
|
||||
showSoftKeyboard()
|
||||
Timber.i("Entered wrong username or password")
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ interface LoginAdvancedView : BaseView {
|
||||
|
||||
fun setErrorPassInvalid(focus: Boolean)
|
||||
|
||||
fun setErrorPassIncorrect()
|
||||
fun setErrorPassIncorrect(message: String?)
|
||||
|
||||
fun clearUsernameError()
|
||||
|
||||
|
@ -5,6 +5,7 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
@ -41,10 +42,12 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
get() = binding.loginFormPass.text.toString()
|
||||
|
||||
override val formHostValue: String
|
||||
get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty()
|
||||
get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||
.orEmpty()
|
||||
|
||||
override val formHostSymbol: String
|
||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())).orEmpty()
|
||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||
.orEmpty()
|
||||
|
||||
override val nicknameLabel: String
|
||||
get() = getString(R.string.login_nickname_hint)
|
||||
@ -88,7 +91,13 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
|
||||
with(binding.loginFormHost) {
|
||||
setText(hostKeys.getOrNull(0).orEmpty())
|
||||
setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys))
|
||||
setAdapter(
|
||||
LoginSymbolAdapter(
|
||||
context,
|
||||
R.layout.support_simple_spinner_dropdown_item,
|
||||
hostKeys
|
||||
)
|
||||
)
|
||||
setOnClickListener { if (binding.loginFormContainer.visibility == GONE) dismissDropDown() }
|
||||
}
|
||||
}
|
||||
@ -142,24 +151,31 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
}
|
||||
}
|
||||
|
||||
override fun setErrorPassIncorrect() {
|
||||
with(binding.loginFormPassLayout) {
|
||||
error = getString(R.string.login_incorrect_password)
|
||||
override fun setErrorPassIncorrect(message: String?) {
|
||||
val error = message ?: getString(R.string.login_incorrect_password_default)
|
||||
|
||||
with(binding) {
|
||||
loginFormUsernameLayout.error = " "
|
||||
loginFormPassLayout.error = " "
|
||||
loginFormErrorBox.text = getString(R.string.login_incorrect_password, error)
|
||||
loginFormErrorBox.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun setErrorEmailInvalid(domain: String) {
|
||||
with(binding.loginFormUsernameLayout) {
|
||||
error = getString(R.string.login_invalid_custom_email,domain)
|
||||
error = getString(R.string.login_invalid_custom_email, domain)
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearUsernameError() {
|
||||
binding.loginFormUsernameLayout.error = null
|
||||
binding.loginFormErrorBox.isVisible = false
|
||||
}
|
||||
|
||||
override fun clearPassError() {
|
||||
binding.loginFormPassLayout.error = null
|
||||
binding.loginFormErrorBox.isVisible = false
|
||||
}
|
||||
|
||||
override fun showSoftKeyboard() {
|
||||
@ -183,12 +199,18 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
binding.loginFormVersion.text = "v${appInfo.versionName}"
|
||||
}
|
||||
|
||||
override fun notifyParentAccountLogged(studentsWithSemesters: List<StudentWithSemesters>, loginData: Triple<String, String, String>) {
|
||||
override fun notifyParentAccountLogged(
|
||||
studentsWithSemesters: List<StudentWithSemesters>,
|
||||
loginData: Triple<String, String, String>
|
||||
) {
|
||||
(activity as? LoginActivity)?.onFormFragmentAccountLogged(studentsWithSemesters, loginData)
|
||||
}
|
||||
|
||||
override fun openPrivacyPolicyPage() {
|
||||
context?.openInternetBrowser("https://wulkanowy.github.io/polityka-prywatnosci.html", ::showMessage)
|
||||
context?.openInternetBrowser(
|
||||
"https://wulkanowy.github.io/polityka-prywatnosci.html",
|
||||
::showMessage
|
||||
)
|
||||
}
|
||||
|
||||
override fun showContact(show: Boolean) {
|
||||
@ -210,7 +232,10 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
}
|
||||
|
||||
override fun openFaqPage() {
|
||||
context?.openInternetBrowser("https://wulkanowy.github.io/czesto-zadawane-pytania/dlaczego-nie-moge-sie-zalogowac", ::showMessage)
|
||||
context?.openInternetBrowser(
|
||||
"https://wulkanowy.github.io/czesto-zadawane-pytania/dlaczego-nie-moge-sie-zalogowac",
|
||||
::showMessage
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@ -223,7 +248,8 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
chooserTitle = requireContext().getString(R.string.login_email_intent_title),
|
||||
email = "wulkanowyinc@gmail.com",
|
||||
subject = requireContext().getString(R.string.login_email_subject),
|
||||
body = requireContext().getString(R.string.login_email_text,
|
||||
body = requireContext().getString(
|
||||
R.string.login_email_text,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||
appInfo.systemVersion.toString(),
|
||||
appInfo.versionName,
|
||||
|
@ -30,7 +30,7 @@ class LoginFormPresenter @Inject constructor(
|
||||
showVersion()
|
||||
|
||||
loginErrorHandler.onBadCredentials = {
|
||||
setErrorPassIncorrect()
|
||||
setErrorPassIncorrect(it)
|
||||
showSoftKeyboard()
|
||||
Timber.i("Entered wrong username or password")
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ interface LoginFormView : BaseView {
|
||||
|
||||
fun setErrorPassInvalid(focus: Boolean)
|
||||
|
||||
fun setErrorPassIncorrect()
|
||||
fun setErrorPassIncorrect(message: String?)
|
||||
|
||||
fun setErrorEmailInvalid(domain: String)
|
||||
|
||||
|
@ -79,12 +79,7 @@ class MessagePreviewAdapter @Inject constructor() :
|
||||
|
||||
val readText = when {
|
||||
recipientCount > 1 -> {
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.message_read_by,
|
||||
message.readBy,
|
||||
message.readBy,
|
||||
recipientCount
|
||||
)
|
||||
context.getString(R.string.message_read_by, message.readBy, recipientCount)
|
||||
}
|
||||
message.readBy == 1 -> {
|
||||
context.getString(R.string.message_read, context.getString(R.string.all_yes))
|
||||
|
@ -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.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.thelittlefireman.appkillermanager.AppKillerManager
|
||||
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 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) {
|
||||
findPreference<Preference>(getString(R.string.pref_key_notification_debug))?.isVisible =
|
||||
showDebugNotificationSwitch
|
||||
@ -57,12 +75,11 @@ class NotificationsFragment : PreferenceFragmentCompat(),
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.pref_key_notifications_system_settings))?.run {
|
||||
setOnPreferenceClickListener {
|
||||
findPreference<Preference>(getString(R.string.pref_key_notifications_system_settings))
|
||||
?.setOnPreferenceClickListener {
|
||||
presenter.onOpenSystemSettingsClicked()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
super.onResume()
|
||||
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
|
@ -31,6 +31,9 @@ class NotificationsPresenter @Inject constructor(
|
||||
)
|
||||
initView(appInfo.isDebug)
|
||||
}
|
||||
|
||||
checkNotificationPiggybackState()
|
||||
|
||||
Timber.i("Settings notifications view was initialized")
|
||||
}
|
||||
|
||||
@ -39,7 +42,7 @@ class NotificationsPresenter @Inject constructor(
|
||||
|
||||
preferencesRepository.apply {
|
||||
when (key) {
|
||||
isUpcomingLessonsNotificationsEnableKey -> {
|
||||
isUpcomingLessonsNotificationsEnableKey, isUpcomingLessonsNotificationsPersistentKey -> {
|
||||
if (!isUpcomingLessonsNotificationsEnable) {
|
||||
timetableNotificationHelper.cancelNotification()
|
||||
}
|
||||
@ -47,6 +50,11 @@ class NotificationsPresenter @Inject constructor(
|
||||
isDebugNotificationEnableKey -> {
|
||||
chuckerCollector.showNotification = isDebugNotificationEnable
|
||||
}
|
||||
isNotificationPiggybackEnabledKey -> {
|
||||
if (isNotificationPiggybackEnabled && view?.isNotificationPermissionGranted == false) {
|
||||
view?.openNotificationPermissionDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
analytics.logEvent("setting_changed", "name" to key)
|
||||
@ -59,4 +67,18 @@ class NotificationsPresenter @Inject constructor(
|
||||
fun onOpenSystemSettingsClicked() {
|
||||
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 {
|
||||
|
||||
val isNotificationPermissionGranted: Boolean
|
||||
|
||||
fun initView(showDebugNotificationSwitch: Boolean)
|
||||
|
||||
fun showFixSyncDialog()
|
||||
@ -11,4 +13,8 @@ interface NotificationsView : BaseView {
|
||||
fun openSystemSettings()
|
||||
|
||||
fun enableNotification(notificationKey: String, enable: Boolean)
|
||||
|
||||
fun openNotificationPermissionDialog()
|
||||
|
||||
fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean)
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
package io.github.wulkanowy.ui.modules.timetable
|
||||
|
||||
import android.graphics.Paint
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.R
|
||||
@ -151,8 +152,8 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
|
||||
if (lesson.isStudentPlan && showTimers) {
|
||||
timers[position] = timer(period = 1000) {
|
||||
if (ViewCompat.isAttachedToWindow(root)) {
|
||||
root.post { updateTimeLeft(binding, lesson, position) }
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
updateTimeLeft(binding, lesson, position)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -176,8 +177,8 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
|
||||
private fun updateTimeLeft(binding: ItemTimetableBinding, lesson: Timetable, position: Int) {
|
||||
val isShowTimeUntil = lesson.isShowTimeUntil(getPreviousLesson(position))
|
||||
val until = lesson.until
|
||||
val left = lesson.left
|
||||
val until = lesson.until.plusMinutes(1)
|
||||
val left = lesson.left?.plusMinutes(1)
|
||||
val isJustFinished = lesson.isJustFinished
|
||||
|
||||
with(binding) {
|
||||
@ -190,17 +191,10 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
visibility = VISIBLE
|
||||
text = context.getString(
|
||||
R.string.timetable_time_until,
|
||||
if (until.seconds <= 60) {
|
||||
context.getString(
|
||||
R.string.timetable_seconds,
|
||||
until.seconds.toString(10)
|
||||
)
|
||||
} else {
|
||||
context.getString(
|
||||
R.string.timetable_minutes,
|
||||
until.toMinutes().toString(10)
|
||||
)
|
||||
}
|
||||
context.getString(
|
||||
R.string.timetable_minutes,
|
||||
until.toMinutes().toString(10)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -212,17 +206,10 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
visibility = VISIBLE
|
||||
text = context.getString(
|
||||
R.string.timetable_time_left,
|
||||
if (left.seconds < 60) {
|
||||
context.getString(
|
||||
R.string.timetable_seconds,
|
||||
left.seconds.toString(10)
|
||||
)
|
||||
} else {
|
||||
context.getString(
|
||||
R.string.timetable_minutes,
|
||||
left.toMinutes().toString(10)
|
||||
)
|
||||
}
|
||||
context.getString(
|
||||
R.string.timetable_minutes,
|
||||
left.toMinutes().toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -360,7 +347,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
private fun updateTeacherColor(teacherTextView: TextView, lesson: Timetable) {
|
||||
teacherTextView.setTextColor(
|
||||
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
|
||||
)
|
||||
)
|
||||
|
@ -89,14 +89,22 @@ class TimetableDialog : DialogFragment() {
|
||||
R.attr.colorPrimary
|
||||
)
|
||||
)
|
||||
timetableDialogChangesValue.setTextColor(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
timetableDialogChangesValue.setTextColor(
|
||||
requireContext().getThemeAttrColor(
|
||||
R.attr.colorPrimary
|
||||
)
|
||||
)
|
||||
} else {
|
||||
timetableDialogChangesTitle.setTextColor(
|
||||
requireContext().getThemeAttrColor(
|
||||
R.attr.colorTimetableChange
|
||||
)
|
||||
)
|
||||
timetableDialogChangesValue.setTextColor(requireContext().getThemeAttrColor(R.attr.colorTimetableChange))
|
||||
timetableDialogChangesValue.setTextColor(
|
||||
requireContext().getThemeAttrColor(
|
||||
R.attr.colorTimetableChange
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
else -> {
|
||||
timetableDialogTeacherTitle.visibility = GONE
|
||||
|
@ -173,7 +173,7 @@ class TimetableWidgetFactory(
|
||||
updateNotCanceledLessonNumberColor(this, lesson)
|
||||
updateNotCanceledSubjectColor(this, lesson)
|
||||
|
||||
val teacherChange = lesson.teacherOld.isNotBlank() && lesson.teacher != lesson.teacherOld
|
||||
val teacherChange = lesson.teacherOld.isNotBlank()
|
||||
updateNotCanceledRoom(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.data.db.entities.Grade
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
@JvmName("calcSummaryAverage")
|
||||
fun List<GradeSummary>.calcAverage(plusModifier: Double, minusModifier: Double) = asSequence()
|
||||
fun List<GradeSummary>.calcFinalAverage(plusModifier: Double, minusModifier: Double) = asSequence()
|
||||
.mapNotNull {
|
||||
if (it.finalGrade.matches("[0-6][+-]?".toRegex())) {
|
||||
when {
|
||||
|
@ -33,7 +33,7 @@ class AutoRefreshHelper @Inject constructor(
|
||||
private val sharedPref: SharedPrefProvider
|
||||
) {
|
||||
|
||||
fun isShouldBeRefreshed(key: String): Boolean {
|
||||
fun shouldBeRefreshed(key: String): Boolean {
|
||||
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()
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
Wersja 1.2.3
|
||||
Wersja 1.3.0
|
||||
|
||||
- naprawiliśmy pomieszane imiona nauczycieli z salami w planie lekcji
|
||||
- dodaliśmy brakujące okienka ze szczegółami na ekranie zebrań
|
||||
- klikając w kafelek z lekcjami na jutro aplikacja teraz przekierowuje na ekran z planem na jutro
|
||||
- naprawiliśmy błąd przy wylogowywaniu innego niż bieżący uczeń
|
||||
- naprawiliśmy logowanie na platformę Opolskiej eSzkoły
|
||||
- dodaliśmy centrum powiadomień i opcję odbierania pushy z oficjalnej aplikacji (dla zaawansowanych)
|
||||
- dodaliśmy objaśnienie do informacji o obliczonych średnich w podsumowaniu
|
||||
- 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
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@android:color/white" />
|
||||
<corners android:radius="5dp" />
|
||||
<solid android:color="#bbffffff" />
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/colorWidgetBackground" />
|
||||
<corners android:radius="5dp" />
|
||||
<solid android:color="#BB191919" />
|
||||
<corners android:radius="8dp" />
|
||||
</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_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginLeft="32dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginRight="32dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/login_header_default"
|
||||
android:textSize="16sp"
|
||||
@ -126,6 +124,20 @@
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
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
|
||||
android:id="@+id/loginFormUsernameLayout"
|
||||
@ -134,15 +146,15 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:layout_marginTop="48dp"
|
||||
android:layout_marginTop="28dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:hint="@string/login_nickname_hint"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/loginFormPassLayout"
|
||||
app:layout_constraintEnd_toEndOf="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
|
||||
android:id="@+id/loginFormUsername"
|
||||
@ -170,7 +182,6 @@
|
||||
android:hint="@string/login_password_hint"
|
||||
app:errorEnabled="true"
|
||||
app:errorIconDrawable="@null"
|
||||
app:layout_constraintBottom_toTopOf="@+id/loginFormRecoverLink"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginFormUsernameLayout"
|
||||
@ -200,7 +211,6 @@
|
||||
android:textAppearance="?android:textAppearance"
|
||||
app:backgroundTint="?android:windowBackground"
|
||||
app:fontFamily="sans-serif-medium"
|
||||
app:layout_constraintBottom_toTopOf="@id/loginFormHostLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginFormPassLayout"
|
||||
tools:visibility="visible" />
|
||||
@ -217,7 +227,6 @@
|
||||
android:layout_marginRight="24dp"
|
||||
android:hint="@string/login_host_hint"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toTopOf="@+id/loginFormAdvancedButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginFormRecoverLink">
|
||||
@ -262,14 +271,13 @@
|
||||
android:id="@+id/loginFormPrivacyLink"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:gravity="start|center_vertical"
|
||||
android:text="@string/login_privacy_policy"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:fontFamily="sans-serif-medium"
|
||||
app:layout_constraintStart_toStartOf="@id/loginFormAdvancedButton"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginFormAdvancedButton"
|
||||
app:layout_constraintTop_toTopOf="@+id/loginFormVersion"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
|
@ -13,6 +13,8 @@
|
||||
android:id="@+id/messageTabRecycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="64dp"
|
||||
tools:listitem="@layout/item_message" />
|
||||
</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"?>
|
||||
<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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@ -12,7 +13,7 @@
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
@ -24,13 +25,30 @@
|
||||
android:text="@string/grade_summary_calculated_average"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gradeSummaryScrollableHeaderCalculated"
|
||||
android:layout_width="match_parent"
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="21sp"
|
||||
tools:text="6,00" />
|
||||
android:layout_gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<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
|
||||
android:id="@+id/gradeSummaryScrollableHeaderCalculatedSubjectCount"
|
||||
@ -57,13 +75,30 @@
|
||||
android:text="@string/grade_summary_final_average"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gradeSummaryScrollableHeaderFinal"
|
||||
android:layout_width="match_parent"
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="21sp"
|
||||
tools:text="6,00" />
|
||||
android:layout_gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<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
|
||||
android:id="@+id/gradeSummaryScrollableHeaderFinalSubjectCount"
|
||||
|
@ -1,10 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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
|
||||
android:id="@+id/dashboard_menu_tiles"
|
||||
android:icon="@drawable/ic_more_settings"
|
||||
android:orderInCategory="1"
|
||||
android:orderInCategory="2"
|
||||
android:title="@string/pref_dashboard_appearance_tiles_title"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
<item
|
||||
android:id="@+id/mainMenuAccount"
|
||||
android:orderInCategory="2"
|
||||
android:orderInCategory="10"
|
||||
app:showAsAction="always"
|
||||
tools:ignore="MenuTitle" />
|
||||
</menu>
|
||||
|
@ -41,8 +41,8 @@
|
||||
<item>Barvy známek v deníku</item>
|
||||
</string-array>
|
||||
<string-array name="grade_average_mode_entries">
|
||||
<item>Průměrná známka od druhého semestru</item>
|
||||
<item>Průměr známek z obou semestrů</item>
|
||||
<item>Průměr známek pouze z vybraného semestru</item>
|
||||
<item>Průměr z průměrů z obou semestrů</item>
|
||||
<item>Průměr známek z celého roku</item>
|
||||
</string-array>
|
||||
<string-array name="dashboard_tile_entries">
|
||||
|
@ -12,7 +12,7 @@
|
||||
<string name="about_title">O aplikaci</string>
|
||||
<string name="logviewer_title">Prohlížeč protokolů</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="license_title">Licence</string>
|
||||
<string name="message_title">Zprávy</string>
|
||||
@ -24,6 +24,7 @@
|
||||
<string name="account_details_title">Podrobnosti účtu</string>
|
||||
<string name="student_info_title">Informace o žáku</string>
|
||||
<string name="dashboard_title">Domů</string>
|
||||
<string name="notifications_center_title">Centrum upozornění</string>
|
||||
<!--Subtitles-->
|
||||
<string name="grade_subtitle">Semestr %1$d, %2$d/%3$d</string>
|
||||
<!--Login-->
|
||||
@ -42,7 +43,8 @@
|
||||
<string name="login_symbol_hint">Symbol</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_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_token">Neplatný token</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_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_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_from_subjects">z %1$d z %2$d předmětů</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_with_attachments">Pouze s přílohami</string>
|
||||
<string name="message_read">Přečtena: %s</string>
|
||||
<plurals name="message_read_by">
|
||||
<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>
|
||||
<string name="message_read_by">Přečtena přes: %1$d z %2$d osob</string>
|
||||
<plurals name="message_number_item">
|
||||
<item quantity="one">%d zpráva</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="other">Máte %1$d nových setkání</item>
|
||||
</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>
|
||||
<!--Director information-->
|
||||
<string name="school_announcement_title">Školní oznámení</string>
|
||||
@ -592,7 +593,7 @@
|
||||
<string name="all_yes">Ano</string>
|
||||
<string name="all_no">Ne</string>
|
||||
<string name="all_save">Uložit</string>
|
||||
<string name="all_title">Title</string>
|
||||
<string name="all_title">Titul</string>
|
||||
<!--Timetable Widget-->
|
||||
<string name="widget_timetable_no_items">Žádné lekce</string>
|
||||
<string name="widget_timetable_theme_title">Vybrat motiv</string>
|
||||
@ -602,7 +603,7 @@
|
||||
<!--Preferences-->
|
||||
<string name="pref_view_header">Vzhled a chování aplikací</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_present">Zobrazit přítomnost</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_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_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_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_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_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_switch">Automatická aktualizace</string>
|
||||
<string name="pref_services_suspended">Pozastaveno na dovolené</string>
|
||||
|
@ -41,8 +41,8 @@
|
||||
<item>Farben der Bewertungen im Logbuch</item>
|
||||
</string-array>
|
||||
<string-array name="grade_average_mode_entries">
|
||||
<item>Durchschnittsnote für das 2. Semester</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>
|
||||
</string-array>
|
||||
<string-array name="dashboard_tile_entries">
|
||||
|
@ -24,6 +24,7 @@
|
||||
<string name="account_details_title">Kontodetails</string>
|
||||
<string name="student_info_title">Schülerinfo</string>
|
||||
<string name="dashboard_title">Übersicht</string>
|
||||
<string name="notifications_center_title">Benachrichtigungszentrum</string>
|
||||
<!--Subtitles-->
|
||||
<string name="grade_subtitle">Semester %1$d, %2$d/%3$d</string>
|
||||
<!--Login-->
|
||||
@ -42,7 +43,8 @@
|
||||
<string name="login_symbol_hint">Symbol</string>
|
||||
<string name="login_sign_in">Anmelden</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_token">Ungültige token</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_predicted_grade">Vorhergesagte Note</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_from_subjects">aus %1$d von %2$d Schulfächern</string>
|
||||
<string name="grade_menu_summary">Zusammenfassung</string>
|
||||
@ -155,7 +161,7 @@
|
||||
<!--Additional lessons-->
|
||||
<string name="additional_lessons_title">Zusätzliche Lektionen</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-->
|
||||
<string name="attendance_summary_button">Übersicht über die Schulbesuch</string>
|
||||
<string name="attendance_absence_school">Aus schulischen Gründen abwesend</string>
|
||||
@ -185,8 +191,8 @@
|
||||
<item quantity="other">Neue prüfungen</item>
|
||||
</plurals>
|
||||
<plurals name="exam_notify_new_item_content">
|
||||
<item quantity="one">Du hast %d neue Prüfung erhalten</item>
|
||||
<item quantity="other">Sie haben %d neue Prüfungen erhalten</item>
|
||||
<item quantity="one">Du hast %d neue Prüfung</item>
|
||||
<item quantity="other">Du hast %d neue Prüfungen</item>
|
||||
</plurals>
|
||||
<plurals name="exam_number_item">
|
||||
<item quantity="one">%d prüfung</item>
|
||||
@ -212,16 +218,13 @@
|
||||
<string name="message_subject">Thema</string>
|
||||
<string name="message_content">Inhalt</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_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_with_attachments">Nur mit Anhängen</string>
|
||||
<string name="message_read">Lesen: %s</string>
|
||||
<plurals name="message_read_by">
|
||||
<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>
|
||||
<string name="message_read_by">Lesen von: %1$d von %2$d Personen</string>
|
||||
<plurals name="message_number_item">
|
||||
<item quantity="one">%d nachricht</item>
|
||||
<item quantity="other">%d nachrichten</item>
|
||||
@ -344,7 +347,7 @@
|
||||
<item quantity="one">Sie haben %1$d neue konferenz</item>
|
||||
<item quantity="other">Sie haben %1$d neue konferenzen</item>
|
||||
</plurals>
|
||||
<string name="conferences_present">Present at conference</string>
|
||||
<string name="conferences_present">Teilnahme an einem Meeting</string>
|
||||
<string name="conference_agenda">Agenda</string>
|
||||
<!--Director information-->
|
||||
<string name="school_announcement_title">Schulankündigungen</string>
|
||||
@ -514,7 +517,7 @@
|
||||
<string name="all_yes">Ja</string>
|
||||
<string name="all_no">Nein</string>
|
||||
<string name="all_save">Speichern</string>
|
||||
<string name="all_title">Title</string>
|
||||
<string name="all_title">Titel</string>
|
||||
<!--Timetable Widget-->
|
||||
<string name="widget_timetable_no_items">Keine Lektionen</string>
|
||||
<string name="widget_timetable_theme_title">Thema wählen</string>
|
||||
@ -524,7 +527,7 @@
|
||||
<!--Preferences-->
|
||||
<string name="pref_view_header">Aussehen & Verhalten</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_present">Anwesendheit zeigen</string>
|
||||
<string name="pref_view_app_theme">Thema</string>
|
||||
@ -539,12 +542,18 @@
|
||||
<string name="pref_notify_header">Benachrichtigungen</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_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_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_settings_button">Gehe zu den Einstellungen</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_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_switch">Automatische Aktualisierung</string>
|
||||
<string name="pref_services_suspended">An Feiertagen suspendiert</string>
|
||||
|
@ -41,8 +41,8 @@
|
||||
<item>Kolory ocen w dzienniku</item>
|
||||
</string-array>
|
||||
<string-array name="grade_average_mode_entries">
|
||||
<item>Średnia ocen z drugiego semestru</item>
|
||||
<item>Średnia średnich z obu semestrów</item>
|
||||
<item>Średnia ocen tylko z wybranego semestru</item>
|
||||
<item>Średnia ze średnich z obu semestrów</item>
|
||||
<item>Średnia wszystkich ocen z całego roku</item>
|
||||
</string-array>
|
||||
<string-array name="dashboard_tile_entries">
|
||||
|
@ -24,6 +24,7 @@
|
||||
<string name="account_details_title">Szczegóły konta</string>
|
||||
<string name="student_info_title">Informacje o uczniu</string>
|
||||
<string name="dashboard_title">Start</string>
|
||||
<string name="notifications_center_title">Centrum powiadomień</string>
|
||||
<!--Subtitles-->
|
||||
<string name="grade_subtitle">Semestr %1$d, %2$d/%3$d</string>
|
||||
<!--Login-->
|
||||
@ -42,7 +43,8 @@
|
||||
<string name="login_symbol_hint">Symbol</string>
|
||||
<string name="login_sign_in">Zaloguj</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_token">Nieprawidłowy token</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_predicted_grade">Przewidywana ocena</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_from_subjects">z %1$d na %2$d przedmiotów</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_with_attachments">Tylko z załącznikami</string>
|
||||
<string name="message_read">Przeczytana: %s</string>
|
||||
<plurals name="message_read_by">
|
||||
<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>
|
||||
<string name="message_read_by">Przeczytana przez: %1$d z %2$d osób</string>
|
||||
<plurals name="message_number_item">
|
||||
<item quantity="one">%d wiadomość</item>
|
||||
<item quantity="few">%d wiadomości</item>
|
||||
@ -602,7 +603,7 @@
|
||||
<!--Preferences-->
|
||||
<string name="pref_view_header">Wygląd i zachowanie aplikacji</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_present">Pokazuj obecność</string>
|
||||
<string name="pref_view_app_theme">Motyw</string>
|
||||
@ -617,12 +618,18 @@
|
||||
<string name="pref_notify_header">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_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_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_settings_button">Przejdź do ustawień</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_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_switch">Automatyczna aktualizacja</string>
|
||||
<string name="pref_services_suspended">Zawieszona na wakacjach</string>
|
||||
|
@ -41,8 +41,8 @@
|
||||
<item>Цвета оценок в дневнике</item>
|
||||
</string-array>
|
||||
<string-array name="grade_average_mode_entries">
|
||||
<item>Средняя оценка со 2 семестра</item>
|
||||
<item>Средняя оценка с двух семестров</item>
|
||||
<item>Средние оценки только с выбранного семестра</item>
|
||||
<item>Средние значения для обоих семестров</item>
|
||||
<item>Средняя оценок со всего года</item>
|
||||
</string-array>
|
||||
<string-array name="dashboard_tile_entries">
|
||||
|
@ -24,6 +24,7 @@
|
||||
<string name="account_details_title">Данные аккаунта</string>
|
||||
<string name="student_info_title">Информация о студенте</string>
|
||||
<string name="dashboard_title">Панель</string>
|
||||
<string name="notifications_center_title">Центр уведомлений</string>
|
||||
<!--Subtitles-->
|
||||
<string name="grade_subtitle">%1$d семестр, %2$d/%3$d</string>
|
||||
<!--Login-->
|
||||
@ -42,7 +43,8 @@
|
||||
<string name="login_symbol_hint">Symbol</string>
|
||||
<string name="login_sign_in">Войти</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_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_predicted_grade">Ожидаемая оценка</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_from_subjects">от %1$d из %2$d субъектов</string>
|
||||
<string name="grade_menu_summary">Итоги</string>
|
||||
@ -238,12 +244,7 @@
|
||||
<string name="message_chip_only_unread">Только непрочитанные</string>
|
||||
<string name="message_chip_only_with_attachments">Только с вложениями</string>
|
||||
<string name="message_read">Чтение: %s</string>
|
||||
<plurals name="message_read_by">
|
||||
<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>
|
||||
<string name="message_read_by">Прочитано: %1$d из %2$d человек</string>
|
||||
<plurals name="message_number_item">
|
||||
<item quantity="one">%d сообщение</item>
|
||||
<item quantity="few">%d сообщения</item>
|
||||
@ -402,8 +403,8 @@
|
||||
<item quantity="many">У вас %1$d новая конференция</item>
|
||||
<item quantity="other">У вас %1$d новых конференций</item>
|
||||
</plurals>
|
||||
<string name="conferences_present">Present at conference</string>
|
||||
<string name="conference_agenda">Agenda</string>
|
||||
<string name="conferences_present">Присутствует на конференции</string>
|
||||
<string name="conference_agenda">Повестка дня</string>
|
||||
<!--Director information-->
|
||||
<string name="school_announcement_title">Объявления школ</string>
|
||||
<string name="school_announcement_no_items">Нет объявлений о школе</string>
|
||||
@ -592,7 +593,7 @@
|
||||
<string name="all_yes">Да</string>
|
||||
<string name="all_no">Нет</string>
|
||||
<string name="all_save">Сохранить</string>
|
||||
<string name="all_title">Title</string>
|
||||
<string name="all_title">Тема</string>
|
||||
<!--Timetable Widget-->
|
||||
<string name="widget_timetable_no_items">Нет уроков</string>
|
||||
<string name="widget_timetable_theme_title">Выбрать тему</string>
|
||||
@ -602,7 +603,7 @@
|
||||
<!--Preferences-->
|
||||
<string name="pref_view_header">Внешний вид приложения & поведение</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_present">Показать присутствие</string>
|
||||
<string name="pref_view_app_theme">Тема</string>
|
||||
@ -617,12 +618,18 @@
|
||||
<string name="pref_notify_header">Уведомления</string>
|
||||
<string name="pref_notify_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_fix_sync_issues">Исправить проблемы с синхронизацией и уведомлениями</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_debug_switch">Показывать дебаг-уведомления</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_switch">Автоматическая синхронизация</string>
|
||||
<string name="pref_services_suspended">Приостановить синхронизации во время каникул</string>
|
||||
|
@ -41,8 +41,8 @@
|
||||
<item>Farby známok v denníku</item>
|
||||
</string-array>
|
||||
<string-array name="grade_average_mode_entries">
|
||||
<item>Priemer známok až od druhého semestra</item>
|
||||
<item>Priemer známok z oboch semestrov</item>
|
||||
<item>Priemer známok iba z vybraného semestra</item>
|
||||
<item>Priemer z priemerov z oboch semestrov</item>
|
||||
<item>Priemer známok z celého roka</item>
|
||||
</string-array>
|
||||
<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