1
0
Fork 1

Compare commits

..

54 commits
1.4.3 ... 1.5.0

Author SHA1 Message Date
Mikołaj Pich
6dc16b288d Merge branch 'release/1.5.0' 2022-01-01 17:51:11 +01:00
Mikołaj Pich
daf97be9ad Version 1.5.0 2022-01-01 17:50:02 +01:00
Rafał Borcz
2bb2190410
New Crowdin updates (#1745) 2022-01-01 17:48:58 +01:00
Mikołaj Pich
aff1a7030d
Add a custom error message for ssl errors due to invalid clock setting (#1742) 2022-01-01 15:46:08 +01:00
Mikołaj Pich
8877322357
Differentiate school announcements by userLoginId (#1744) 2022-01-01 13:52:51 +01:00
Mikołaj Pich
bc672e94f8
Strip html from school announcements notifications (#1743) 2022-01-01 13:48:58 +01:00
Mikołaj Pich
a03bcf8e62
Display comment after entry in grade notifications (#1741) 2021-12-31 11:36:14 +00:00
Mikołaj Pich
20673c4ead
Add basic support for kindergarten students (#1738) 2021-12-31 12:21:52 +01:00
Rafał Borcz
5321d00ee9
Fix play flavor build (#1740) 2021-12-31 12:10:56 +01:00
Michael
e6b2acabd5
Block app timezone to polish timezone (#1598) 2021-12-31 11:53:09 +01:00
Mikołaj Pich
bfd7f688ab
Move webview locale fix to account recovery fragment (#1739) 2021-12-31 10:24:02 +00:00
Mikołaj Pich
ba02531aa4
Fix timetable widget crash when there are no lessons for the day (#1737) 2021-12-31 09:40:15 +01:00
Michael
210c3a0e28
Remove HiltBroadcastReceiver (#1736) 2021-12-30 12:50:06 +01:00
Michael
68f0ecc45c
If only one student exists, don't show student name in timetable notification (#1711)
Co-authored-by: Rafał Borcz <RafalBO99@outlook.com>
2021-12-29 15:44:43 +01:00
Rafał Borcz
0965d03f1a
New Crowdin updates (#1734) 2021-12-29 11:57:17 +01:00
Michael
496641f594
Fix notification spam after login (#1715) 2021-12-29 08:31:43 +01:00
Rafał Borcz
684c258e2d
Remove admin message offline first cache (#1735) 2021-12-28 18:56:59 +01:00
Michael
5e96917508
Fix overlapping text in the error dialog (#1708) 2021-12-28 12:16:52 +01:00
Rafał Borcz
17096ad11b
New Crowdin updates (#1729) 2021-12-27 14:06:20 +01:00
Michael
bd883c9f38
Add option to remove notifications captured from vulcan.hebe (#1716) 2021-12-27 07:48:47 +00:00
Michael
6520f8a0d7
Fix that an incorrect day would be selected in MaterialDatePicker (#1723) 2021-12-27 08:10:30 +01:00
Mikołaj Pich
2eee50ad81
Replace view pager in login activity with simple fragment transactions (#1686) 2021-12-27 07:58:57 +01:00
dependabot[bot]
8560fd7e81
Bump coroutines from 1.5.2 to 1.6.0 (#1731) 2021-12-25 08:38:27 +00:00
dependabot[bot]
f718147ae9
Bump agcp from 1.6.2.300 to 1.6.3.200 (#1730) 2021-12-25 05:55:01 +00:00
dependabot[bot]
cd12c4c891
Bump agconnect-crash from 1.6.2.300 to 1.6.3.200 (#1732) 2021-12-25 05:54:34 +00:00
dependabot[bot]
65f114ce05
Bump kotlinx-serialization-json from 1.3.1 to 1.3.2 (#1733) 2021-12-25 05:54:17 +00:00
Mateusz Idziejczak
497083be97
Update timetable to next day if there is no more lessons today (#1551) 2021-12-25 05:46:24 +00:00
Michael
e26860ea5a
Fix state restoring in GradeStatistics (#1667)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2021-12-23 13:58:26 +01:00
Mateusz Idziejczak
094df212b4
Add additional lessons feature (#1550) 2021-12-21 00:36:59 +01:00
dependabot[bot]
cc22985dc5
Bump firebase-bom from 29.0.2 to 29.0.3 (#1728) 2021-12-20 20:22:56 +00:00
Rafał Borcz
ad9a2711c4
New Crowdin updates (#1687) 2021-12-19 22:46:41 +01:00
Mikołaj Pich
daf44c531c Merge branch 'release/1.4.4' into develop 2021-12-19 22:25:56 +01:00
Mikołaj Pich
4e12eb1552 Merge branch 'release/1.4.4' 2021-12-19 22:25:52 +01:00
Mikołaj Pich
c846cc999f Version 1.4.4 2021-12-19 22:25:47 +01:00
Mikołaj Pich
47d430292c
Update template of login issue email (#1724)
* Update sdk

* Update template of login issue email
2021-12-19 22:08:39 +01:00
Michael
5e1ff2243f
Fix exception in TimetableAdapter (#1721) 2021-12-12 23:09:34 +01:00
dependabot[bot]
a35bef58f2
Bump gradle from 7.0.3 to 7.0.4 (#1718) 2021-12-12 14:06:15 +00:00
dependabot[bot]
0005d84974
Bump hilt_version from 2.40.4 to 2.40.5 (#1719) 2021-12-12 14:05:53 +00:00
dependabot[bot]
19558cb871
Bump firebase-bom from 29.0.1 to 29.0.2 (#1720) 2021-12-12 14:05:35 +00:00
Michael
45e884127f
After deselecting fakelog, clear credentials from login form (#1713) 2021-12-12 15:04:31 +01:00
Michael
c87085a226
Fix invalid order of school announcements (#1689) 2021-12-11 17:35:55 +01:00
Michael
79b970256f
More strongly typed data in preferences (#1697) 2021-12-11 16:14:46 +00:00
Michael
70f038f15f
Remove useless group property from NotificationType (#1714) 2021-12-11 17:12:47 +01:00
Michael
9cabd7ef08
Deduplicate timetable time left string (#1710) 2021-12-11 16:42:33 +01:00
Rafał Borcz
d89e4ccfdf
Fix grade sorting by date (#1717) 2021-12-11 11:54:07 +01:00
Michael
1bcc4d199e
When replying to a message use Re instead of RE (#1712) 2021-12-10 16:24:39 +01:00
dependabot[bot]
2e85e88c5d
Bump agcp from 1.6.2.200 to 1.6.2.300 (#1693) 2021-12-08 10:21:00 +00:00
dependabot[bot]
6ab67fe25b
Bump firebase-bom from 29.0.0 to 29.0.1 (#1696) 2021-12-08 10:04:29 +00:00
dependabot[bot]
8563277a89
Bump robolectric from 4.7.2 to 4.7.3 (#1695) 2021-12-08 10:04:10 +00:00
dependabot[bot]
e458cc90b0
Bump agconnect-crash from 1.6.2.200 to 1.6.2.300 (#1694) 2021-12-08 10:03:42 +00:00
dependabot[bot]
651e3a21b9
Bump firebase-crashlytics-gradle from 2.8.0 to 2.8.1 (#1692) 2021-12-08 10:03:06 +00:00
dependabot[bot]
f6b969cfb1
Bump hilt_version from 2.40.2 to 2.40.4 (#1691) 2021-12-08 10:02:43 +00:00
Michael
d2aa940d46
Convert from a stringly typed grade color to enum GradeColorTheme (#1672) 2021-11-27 10:11:17 +01:00
Mikołaj Pich
ab435a72ea Merge branch 'release/1.4.3' into develop 2021-11-26 22:29:13 +01:00
206 changed files with 9777 additions and 1852 deletions

View file

@ -2,14 +2,6 @@
<code_scheme name="Project" version="173">
<option name="LINE_SEPARATOR" value="&#10;" />
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
</value>
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
@ -126,13 +118,6 @@
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View file

@ -22,8 +22,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 31
versionCode 101
versionName "1.4.3"
versionCode 103
versionName "1.5.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy"
@ -149,7 +149,9 @@ kapt {
play {
defaultToAppBundles = false
track = 'beta'
track = 'production'
releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS
userFraction = 0.25d
updatePriority = 1
enabled.set(false)
}
@ -167,18 +169,18 @@ huaweiPublish {
ext {
work_manager = "2.7.1"
android_hilt = "1.0.0"
room = "2.3.0"
room = "2.4.0"
chucker = "3.5.2"
mockk = "1.12.1"
coroutines = "1.5.2"
coroutines = "1.6.0"
}
dependencies {
implementation "io.github.wulkanowy:sdk:1.4.3"
implementation "io.github.wulkanowy:sdk:1.5.0"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
implementation "androidx.core:core-ktx:1.7.0"
@ -229,7 +231,7 @@ dependencies {
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
implementation 'com.fredporciuncula:flow-preferences:1.6.0'
playImplementation platform('com.google.firebase:firebase-bom:29.0.0')
playImplementation platform('com.google.firebase:firebase-bom:29.0.3')
playImplementation 'com.google.firebase:firebase-analytics-ktx'
playImplementation 'com.google.firebase:firebase-messaging:'
playImplementation 'com.google.firebase:firebase-crashlytics:'
@ -238,7 +240,7 @@ dependencies {
playImplementation 'com.google.android.gms:play-services-ads:20.5.0'
hmsImplementation 'com.huawei.hms:hianalytics:6.3.2.300'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.2.200'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.3.200'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
@ -250,7 +252,7 @@ dependencies {
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation 'org.robolectric:robolectric:4.7.2'
testImplementation 'org.robolectric:robolectric:4.7.3'
testImplementation "androidx.test:runner:1.4.0"
testImplementation "androidx.test.ext:junit:1.1.3"
testImplementation "androidx.test:core:1.4.0"

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import javax.inject.Singleton
import javax.inject.Inject
@Suppress("UNUSED_PARAMETER", "unused")
@Singleton
class InAppReviewHelper @Inject constructor(
@ApplicationContext private val context: Context

View file

@ -114,7 +114,6 @@
</intent-filter>
</service>
<receiver
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
android:exported="true"

View file

@ -1,10 +1,7 @@
package io.github.wulkanowy
import android.app.Application
import android.util.Log.DEBUG
import android.util.Log.INFO
import android.util.Log.VERBOSE
import android.webkit.WebView
import android.util.Log.*
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import com.yariksoffice.lingver.Lingver
@ -12,12 +9,7 @@ import dagger.hilt.android.HiltAndroidApp
import fr.bipi.tressence.file.FileLoggerTree
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.base.ThemeManager
import io.github.wulkanowy.utils.ActivityLifecycleLogger
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.CrashLogExceptionTree
import io.github.wulkanowy.utils.CrashLogTree
import io.github.wulkanowy.utils.DebugLogTree
import io.github.wulkanowy.utils.*
import timber.log.Timber
import javax.inject.Inject
@ -44,7 +36,6 @@ class WulkanowyApp : Application(), Configuration.Provider {
initializeAppLanguage()
themeManager.applyDefaultTheme()
initLogging()
fixWebViewLocale()
}
private fun initLogging() {
@ -76,15 +67,6 @@ class WulkanowyApp : Application(), Configuration.Provider {
}
}
private fun fixWebViewLocale() {
//https://stackoverflow.com/questions/40398528/android-webview-language-changes-abruptly-on-android-7-0-and-above
try {
WebView(this).destroy()
} catch (e: Throwable) {
//Ignore exceptions
}
}
override fun getWorkManagerConfiguration() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO)

View file

@ -1,6 +1,7 @@
package io.github.wulkanowy.data.db
import android.content.Context
import androidx.room.AutoMigration
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@ -66,49 +67,7 @@ import io.github.wulkanowy.data.db.entities.Teacher
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.db.migrations.Migration10
import io.github.wulkanowy.data.db.migrations.Migration11
import io.github.wulkanowy.data.db.migrations.Migration12
import io.github.wulkanowy.data.db.migrations.Migration13
import io.github.wulkanowy.data.db.migrations.Migration14
import io.github.wulkanowy.data.db.migrations.Migration15
import io.github.wulkanowy.data.db.migrations.Migration16
import io.github.wulkanowy.data.db.migrations.Migration17
import io.github.wulkanowy.data.db.migrations.Migration18
import io.github.wulkanowy.data.db.migrations.Migration19
import io.github.wulkanowy.data.db.migrations.Migration2
import io.github.wulkanowy.data.db.migrations.Migration20
import io.github.wulkanowy.data.db.migrations.Migration21
import io.github.wulkanowy.data.db.migrations.Migration22
import io.github.wulkanowy.data.db.migrations.Migration23
import io.github.wulkanowy.data.db.migrations.Migration24
import io.github.wulkanowy.data.db.migrations.Migration25
import io.github.wulkanowy.data.db.migrations.Migration26
import io.github.wulkanowy.data.db.migrations.Migration27
import io.github.wulkanowy.data.db.migrations.Migration28
import io.github.wulkanowy.data.db.migrations.Migration29
import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration30
import io.github.wulkanowy.data.db.migrations.Migration31
import io.github.wulkanowy.data.db.migrations.Migration32
import io.github.wulkanowy.data.db.migrations.Migration33
import io.github.wulkanowy.data.db.migrations.Migration34
import io.github.wulkanowy.data.db.migrations.Migration35
import io.github.wulkanowy.data.db.migrations.Migration36
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.Migration41
import io.github.wulkanowy.data.db.migrations.Migration42
import io.github.wulkanowy.data.db.migrations.Migration43
import io.github.wulkanowy.data.db.migrations.Migration44
import io.github.wulkanowy.data.db.migrations.Migration5
import io.github.wulkanowy.data.db.migrations.Migration6
import io.github.wulkanowy.data.db.migrations.Migration7
import io.github.wulkanowy.data.db.migrations.Migration8
import io.github.wulkanowy.data.db.migrations.Migration9
import io.github.wulkanowy.data.db.migrations.*
import io.github.wulkanowy.utils.AppInfo
import javax.inject.Singleton
@ -146,6 +105,10 @@ import javax.inject.Singleton
Notification::class,
AdminMessage::class
],
autoMigrations = [
AutoMigration(from = 44, to = 45),
AutoMigration(from = 46, to = 47),
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
)
@ -153,7 +116,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 44
const val VERSION_SCHEMA = 47
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(),
@ -198,7 +161,8 @@ abstract class AppDatabase : RoomDatabase() {
Migration41(sharedPrefProvider),
Migration42(),
Migration43(),
Migration44()
Migration44(),
Migration46(),
)
fun newInstance(

View file

@ -1,40 +1,33 @@
package io.github.wulkanowy.data.db
import androidx.room.TypeConverter
import io.github.wulkanowy.utils.toTimestamp
import kotlinx.serialization.SerializationException
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.Month
import java.time.ZoneOffset
import java.util.Date
import java.util.*
class Converters {
private val json = Json
@TypeConverter
fun timestampToDate(value: Long?): LocalDate? = value?.run {
Date(value).toInstant().atZone(ZoneOffset.UTC).toLocalDate()
}
fun timestampToLocalDate(value: Long?): LocalDate? =
value?.let(::Date)?.toInstant()?.atZone(ZoneOffset.UTC)?.toLocalDate()
@TypeConverter
fun dateToTimestamp(date: LocalDate?): Long? {
return date?.atStartOfDay()?.toInstant(ZoneOffset.UTC)?.toEpochMilli()
}
fun dateToTimestamp(date: LocalDate?): Long? = date?.toTimestamp()
@TypeConverter
fun timestampToTime(value: Long?): LocalDateTime? = value?.let {
LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneOffset.UTC)
}
fun instantToTimestamp(instant: Instant?): Long? = instant?.toEpochMilli()
@TypeConverter
fun timeToTimestamp(date: LocalDateTime?): Long? {
return date?.atZone(ZoneOffset.UTC)?.toInstant()?.toEpochMilli()
}
fun timestampToInstant(timestamp: Long?): Instant? = timestamp?.let(Instant::ofEpochMilli)
@TypeConverter
fun monthToInt(month: Month?) = month?.value

View file

@ -4,7 +4,7 @@ import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Conference
import kotlinx.coroutines.flow.Flow
import java.time.LocalDateTime
import java.time.Instant
import javax.inject.Singleton
@Dao
@ -12,5 +12,5 @@ import javax.inject.Singleton
interface ConferenceDao : BaseDao<Conference> {
@Query("SELECT * FROM Conferences WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :startDate")
fun loadAll(diaryId: Int, studentId: Int, startDate: LocalDateTime): Flow<List<Conference>>
fun loadAll(diaryId: Int, studentId: Int, startDate: Instant): Flow<List<Conference>>
}

View file

@ -10,6 +10,6 @@ import javax.inject.Singleton
@Singleton
interface SchoolAnnouncementDao : BaseDao<SchoolAnnouncement> {
@Query("SELECT * FROM SchoolAnnouncements WHERE student_id = :studentId")
@Query("SELECT * FROM SchoolAnnouncements WHERE student_id = :studentId ORDER BY date DESC")
fun loadAll(studentId: Int): Flow<List<SchoolAnnouncement>>
}

View file

@ -1,12 +1,7 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.*
import androidx.room.OnConflictStrategy.ABORT
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
import io.github.wulkanowy.data.db.entities.StudentWithSemesters

View file

@ -5,6 +5,7 @@ import androidx.room.Query
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import kotlinx.coroutines.flow.Flow
import java.time.LocalDate
import java.util.UUID
import javax.inject.Singleton
@Dao
@ -12,5 +13,13 @@ import javax.inject.Singleton
interface TimetableAdditionalDao : BaseDao<TimetableAdditional> {
@Query("SELECT * FROM TimetableAdditional WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<TimetableAdditional>>
fun loadAll(
diaryId: Int,
studentId: Int,
from: LocalDate,
end: LocalDate
): Flow<List<TimetableAdditional>>
@Query("DELETE FROM TimetableAdditional WHERE repeat_id = :repeatId")
suspend fun deleteAllByRepeatId(repeatId: UUID)
}

View file

@ -4,7 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.LocalDateTime
import java.time.Instant
@Entity(tableName = "Conferences")
data class Conference(
@ -27,7 +27,7 @@ data class Conference(
@ColumnInfo(name = "conference_id")
val conferenceId: Int,
val date: LocalDateTime
val date: Instant,
) : Serializable {
@PrimaryKey(autoGenerate = true)

View file

@ -3,7 +3,7 @@ package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.time.LocalDateTime
import java.time.Instant
@Entity(tableName = "GradesSummary")
data class GradeSummary(
@ -45,8 +45,8 @@ data class GradeSummary(
var isFinalGradeNotified: Boolean = true
@ColumnInfo(name = "predicted_grade_last_change")
var predictedGradeLastChange: LocalDateTime = LocalDateTime.now()
var predictedGradeLastChange: Instant = Instant.now()
@ColumnInfo(name = "final_grade_last_change")
var finalGradeLastChange: LocalDateTime = LocalDateTime.now()
var finalGradeLastChange: Instant = Instant.now()
}

View file

@ -4,7 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.LocalDateTime
import java.time.Instant
@Entity(tableName = "Messages")
data class Message(
@ -29,7 +29,7 @@ data class Message(
val subject: String,
val date: LocalDateTime,
val date: Instant,
@ColumnInfo(name = "folder_id")
val folderId: Int,

View file

@ -4,7 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.LocalDateTime
import java.time.Instant
@Entity(tableName = "MobileDevices")
data class MobileDevice(
@ -17,7 +17,7 @@ data class MobileDevice(
val name: String,
val date: LocalDateTime
val date: Instant,
) : Serializable {
@PrimaryKey(autoGenerate = true)

View file

@ -4,7 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import io.github.wulkanowy.services.sync.notifications.NotificationType
import java.time.LocalDateTime
import java.time.Instant
@Entity(tableName = "Notifications")
data class Notification(
@ -18,7 +18,7 @@ data class Notification(
val type: NotificationType,
val date: LocalDateTime,
val date: Instant,
val data: String? = null
) {

View file

@ -7,7 +7,12 @@ import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.LocalDate
@Entity(tableName = "Semesters", indices = [Index(value = ["student_id", "diary_id", "semester_id"], unique = true)])
@Entity(
tableName = "Semesters", indices = [Index(
value = ["student_id", "diary_id", "kindergarten_diary_id", "semester_id"],
unique = true
)]
)
data class Semester(
@ColumnInfo(name = "student_id")
@ -16,6 +21,9 @@ data class Semester(
@ColumnInfo(name = "diary_id")
val diaryId: Int,
@ColumnInfo(name = "kindergarten_diary_id", defaultValue = "0")
val kindergartenDiaryId: Int,
@ColumnInfo(name = "diary_name")
val diaryName: String,
@ -37,12 +45,11 @@ data class Semester(
@ColumnInfo(name = "unit_id")
val unitId: Int
): Serializable {
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
@ColumnInfo(name = "is_current")
var current: Boolean = false
}

View file

@ -5,7 +5,7 @@ import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.LocalDateTime
import java.time.Instant
@Entity(
tableName = "Students",
@ -74,7 +74,7 @@ data class Student(
val isCurrent: Boolean,
@ColumnInfo(name = "registration_date")
val registrationDate: LocalDateTime
val registrationDate: Instant,
) : Serializable {
@PrimaryKey(autoGenerate = true)

View file

@ -4,8 +4,8 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
@Entity(tableName = "Timetable")
data class Timetable(
@ -18,9 +18,9 @@ data class Timetable(
val number: Int,
val start: LocalDateTime,
val start: Instant,
val end: LocalDateTime,
val end: Instant,
val date: LocalDate,

View file

@ -4,8 +4,9 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*
@Entity(tableName = "TimetableAdditional")
data class TimetableAdditional(
@ -16,9 +17,9 @@ data class TimetableAdditional(
@ColumnInfo(name = "diary_id")
val diaryId: Int,
val start: LocalDateTime,
val start: Instant,
val end: LocalDateTime,
val end: Instant,
val date: LocalDate,
@ -27,4 +28,10 @@ data class TimetableAdditional(
@PrimaryKey(autoGenerate = true)
var id: Long = 0
@ColumnInfo(name = "repeat_id", defaultValue = "NULL")
var repeatId: UUID? = null
@ColumnInfo(name = "is_added_by_user", defaultValue = "0")
var isAddedByUser: Boolean = false
}

View file

@ -43,12 +43,14 @@ class Migration12 : Migration(11, 12) {
private fun getStudentsIds(database: SupportSQLiteDatabase): List<Int> {
val students = mutableListOf<Int>()
val studentsCursor = database.query("SELECT student_id FROM Students")
if (studentsCursor.moveToFirst()) {
database.query("SELECT student_id FROM Students").use {
if (it.moveToFirst()) {
do {
students.add(studentsCursor.getInt(0))
} while (studentsCursor.moveToNext())
students.add(it.getInt(0))
} while (it.moveToNext())
}
}
return students
}

View file

@ -25,12 +25,14 @@ class Migration13 : Migration(12, 13) {
private fun getStudentsIds(database: SupportSQLiteDatabase): MutableList<Pair<Int, String>> {
val students = mutableListOf<Pair<Int, String>>()
val studentsCursor = database.query("SELECT id, school_name FROM Students")
if (studentsCursor.moveToFirst()) {
database.query("SELECT id, school_name FROM Students").use {
if (it.moveToFirst()) {
do {
students.add(studentsCursor.getInt(0) to studentsCursor.getString(1))
} while (studentsCursor.moveToNext())
students.add(it.getInt(0) to it.getString(1))
} while (it.moveToNext())
}
}
return students
}
@ -42,12 +44,14 @@ class Migration13 : Migration(12, 13) {
private fun getStudentsAndClassIds(database: SupportSQLiteDatabase): List<Pair<Int, Int>> {
val students = mutableListOf<Pair<Int, Int>>()
val studentsCursor = database.query("SELECT student_id, class_id FROM Students")
if (studentsCursor.moveToFirst()) {
database.query("SELECT student_id, class_id FROM Students").use {
if (it.moveToFirst()) {
do {
students.add(studentsCursor.getInt(0) to studentsCursor.getInt(1))
} while (studentsCursor.moveToNext())
students.add(it.getInt(0) to it.getInt(1))
} while (it.moveToNext())
}
}
return students
}

View file

@ -22,23 +22,27 @@ class Migration27 : Migration(26, 27) {
private fun getStudentsIdsAndNames(database: SupportSQLiteDatabase): MutableList<Triple<Long, Int, String>> {
val students = mutableListOf<Triple<Long, Int, String>>()
val studentsCursor = database.query("SELECT id, user_login_id, student_name FROM Students")
if (studentsCursor.moveToFirst()) {
database.query("SELECT id, user_login_id, student_name FROM Students").use {
if (it.moveToFirst()) {
do {
students.add(Triple(studentsCursor.getLong(0), studentsCursor.getInt(1), studentsCursor.getString(2)))
} while (studentsCursor.moveToNext())
students.add(Triple(it.getLong(0), it.getInt(1), it.getString(2)))
} while (it.moveToNext())
}
}
return students
}
private fun getReportingUnits(database: SupportSQLiteDatabase): MutableList<Pair<Int, String>> {
val units = mutableListOf<Pair<Int, String>>()
val unitsCursor = database.query("SELECT sender_id, sender_name FROM ReportingUnits")
if (unitsCursor.moveToFirst()) {
database.query("SELECT sender_id, sender_name FROM ReportingUnits").use {
if (it.moveToFirst()) {
do {
units.add(unitsCursor.getInt(0) to unitsCursor.getString(1))
} while (unitsCursor.moveToNext())
units.add(it.getInt(0) to it.getString(1))
} while (it.moveToNext())
}
}
return units
}

View file

@ -10,15 +10,17 @@ class Migration35(private val appInfo: AppInfo) : Migration(34, 35) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Students ADD COLUMN `avatar_color` INTEGER NOT NULL DEFAULT 0")
val studentsCursor = database.query("SELECT * FROM Students")
while (studentsCursor.moveToNext()) {
val studentId = studentsCursor.getLongOrNull(0)
database.query("SELECT * FROM Students").use {
while (it.moveToNext()) {
val studentId = it.getLongOrNull(0)
database.execSQL(
"""UPDATE Students
"""
UPDATE Students
SET avatar_color = ${appInfo.defaultColorsForAvatar.random()}
WHERE id = $studentId"""
WHERE id = $studentId
"""
)
}
}
}
}

View file

@ -3,7 +3,7 @@ package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
import io.github.wulkanowy.data.enums.GradeExpandMode
class Migration41(private val sharedPrefProvider: SharedPrefProvider) : Migration(40, 41) {

View file

@ -0,0 +1,102 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import java.time.Instant
import java.time.ZoneId
import java.time.ZoneOffset
class Migration46 : Migration(45, 46) {
override fun migrate(database: SupportSQLiteDatabase) {
migrateConferences(database)
migrateMessages(database)
migrateMobileDevices(database)
migrateNotifications(database)
migrateTimetable(database)
migrateTimetableAdditional(database)
}
private fun migrateConferences(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM Conferences").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
database.execSQL("UPDATE Conferences SET date = $timestampUtc WHERE id = $id")
}
}
}
private fun migrateMessages(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM Messages").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
database.execSQL("UPDATE Messages SET date = $timestampUtc WHERE id = $id")
}
}
}
private fun migrateMobileDevices(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM MobileDevices").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
database.execSQL("UPDATE MobileDevices SET date = $timestampUtc WHERE id = $id")
}
}
}
private fun migrateNotifications(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM Notifications").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
database.execSQL("UPDATE Notifications SET date = $timestampUtc WHERE id = $id")
}
}
}
private fun migrateTimetable(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM Timetable").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocalStart = it.getLong(it.getColumnIndexOrThrow("start"))
val timestampLocalEnd = it.getLong(it.getColumnIndexOrThrow("end"))
val timestampUtcStart = timestampLocalStart.timestampLocalToUTC()
val timestampUtcEnd = timestampLocalEnd.timestampLocalToUTC()
database.execSQL("UPDATE Timetable SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id")
}
}
}
private fun migrateTimetableAdditional(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM TimetableAdditional").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocalStart = it.getLong(it.getColumnIndexOrThrow("start"))
val timestampLocalEnd = it.getLong(it.getColumnIndexOrThrow("end"))
val timestampUtcStart = timestampLocalStart.timestampLocalToUTC()
val timestampUtcEnd = timestampLocalEnd.timestampLocalToUTC()
database.execSQL("UPDATE TimetableAdditional SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id")
}
}
}
private fun Long.timestampLocalToUTC(): Long = Instant.ofEpochMilli(this)
.atZone(ZoneOffset.UTC)
.withZoneSameLocal(ZoneId.of("Europe/Warsaw"))
.withZoneSameInstant(ZoneId.systemDefault())
.toInstant()
.toEpochMilli()
}

View file

@ -0,0 +1,12 @@
package io.github.wulkanowy.data.enums
enum class AppTheme(val value: String) {
SYSTEM("system"),
LIGHT("light"),
DARK("dark"),
BLACK("black");
companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: LIGHT
}
}

View file

@ -0,0 +1,13 @@
package io.github.wulkanowy.data.enums
import java.io.Serializable
enum class GradeColorTheme(val value: String) : Serializable {
VULCAN("vulcan"),
MATERIAL("material"),
GRADE_COLOR("grade_color");
companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: VULCAN
}
}

View file

@ -0,0 +1,11 @@
package io.github.wulkanowy.data.enums
enum class GradeExpandMode(val value: String) {
ONE("one"),
UNLIMITED("any"),
ALWAYS_EXPANDED("always");
companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: ONE
}
}

View file

@ -0,0 +1,10 @@
package io.github.wulkanowy.data.enums
enum class GradeSortingMode(val value: String) {
ALPHABETIC("alphabetic"),
DATE("date");
companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: ALPHABETIC
}
}

View file

@ -0,0 +1,11 @@
package io.github.wulkanowy.data.enums
enum class TimetableMode(val value: String) {
WHOLE_PLAN("yes"),
ONLY_CURRENT_GROUP("no"),
SMALL_OTHER_GROUP("small");
companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: ONLY_CURRENT_GROUP
}
}

View file

@ -10,7 +10,7 @@ fun List<SdkConference>.mapToEntities(semester: Semester) = map {
diaryId = semester.diaryId,
agenda = it.agenda,
conferenceId = it.id,
date = it.date,
date = it.dateZoned.toInstant(),
presentOnConference = it.presentOnConference,
subject = it.subject,
title = it.title

View file

@ -6,7 +6,7 @@ import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformatio
fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
SchoolAnnouncement(
studentId = student.studentId,
studentId = student.userLoginId,
date = it.date,
subject = it.subject,
content = it.content,

View file

@ -4,7 +4,7 @@ import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageAttachment
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Student
import java.time.LocalDateTime
import java.time.Instant
import io.github.wulkanowy.sdk.pojo.Message as SdkMessage
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
@ -18,7 +18,7 @@ fun List<SdkMessage>.mapToEntities(student: Student) = map {
senderId = it.sender?.loginId ?: 0,
recipient = it.recipients.singleOrNull()?.name ?: "Wielu adresatów",
subject = it.subject.trim(),
date = it.date ?: LocalDateTime.now(),
date = it.dateZoned?.toInstant() ?: Instant.now(),
folderId = it.folderId,
unread = it.unread ?: false,
removed = it.removed,

View file

@ -3,13 +3,13 @@ package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.pojos.MobileDeviceToken
import io.github.wulkanowy.sdk.pojo.Token as SdkToken
import io.github.wulkanowy.sdk.pojo.Device as SdkDevice
import io.github.wulkanowy.sdk.pojo.Token as SdkToken
fun List<SdkDevice>.mapToEntities(semester: Semester) = map {
MobileDevice(
userLoginId = semester.studentId,
date = it.createDate,
date = it.createDateZoned.toInstant(),
deviceId = it.id,
name = it.name
)

View file

@ -7,6 +7,7 @@ fun List<SdkSemester>.mapToEntities(studentId: Int) = map {
Semester(
studentId = studentId,
diaryId = it.diaryId,
kindergartenDiaryId = it.kindergartenDiaryId,
diaryName = it.diaryName,
schoolYear = it.schoolYear,
semesterId = it.semesterId,

View file

@ -2,7 +2,7 @@ package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import java.time.LocalDateTime
import java.time.Instant
import io.github.wulkanowy.sdk.pojo.Student as SdkStudent
fun List<SdkStudent>.mapToEntities(password: String = "", colors: List<Long>) = map {
@ -24,7 +24,7 @@ fun List<SdkStudent>.mapToEntities(password: String = "", colors: List<Long>) =
scrapperBaseUrl = it.scrapperBaseUrl,
loginType = it.loginType.name,
isCurrent = false,
registrationDate = LocalDateTime.now(),
registrationDate = Instant.now(),
mobileBaseUrl = it.mobileBaseUrl,
privateKey = it.privateKey,
certificateKey = it.certificateKey,

View file

@ -21,8 +21,8 @@ fun List<SdkTimetable>.mapToEntities(semester: Semester) = map {
studentId = semester.studentId,
diaryId = semester.diaryId,
number = it.number,
start = it.start,
end = it.end,
start = it.startZoned.toInstant(),
end = it.endZoned.toInstant(),
date = it.date,
subject = it.subject,
subjectOld = it.subjectOld,
@ -45,8 +45,8 @@ fun List<SdkTimetableAdditional>.mapToEntities(semester: Semester) = map {
diaryId = semester.diaryId,
subject = it.subject,
date = it.date,
start = it.start,
end = it.end
start = it.startZoned.toInstant(),
end = it.endZoned.toInstant(),
)
}

View file

@ -4,7 +4,6 @@ import io.github.wulkanowy.data.api.AdminMessageService
import io.github.wulkanowy.data.db.dao.AdminMessageDao
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@ -14,23 +13,17 @@ import javax.inject.Singleton
class AdminMessageRepository @Inject constructor(
private val adminMessageService: AdminMessageService,
private val adminMessageDao: AdminMessageDao,
private val appInfo: AppInfo,
private val refreshHelper: AutoRefreshHelper,
private val appInfo: AppInfo
) {
private val saveFetchResultMutex = Mutex()
private val cacheKey = "admin_messages"
suspend fun getAdminMessages(student: Student, forceRefresh: Boolean) = networkBoundResource(
suspend fun getAdminMessages(student: Student) = networkBoundResource(
mutex = saveFetchResultMutex,
query = { adminMessageDao.loadAll() },
fetch = { adminMessageService.getAdminMessages() },
shouldFetch = {
refreshHelper.shouldBeRefreshed(cacheKey) || forceRefresh
},
shouldFetch = { true },
saveFetchResult = { oldItems, newItems ->
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
refreshHelper.updateLastRefreshTimestamp(cacheKey)
},
showSavedOnLoading = false,
mapResult = { adminMessages ->

View file

@ -7,13 +7,7 @@ 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.sdk.pojo.Absent
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract
import io.github.wulkanowy.utils.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate
@ -52,7 +46,8 @@ class AttendanceRepository @Inject constructor(
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
},
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getAttendance(start.monday, end.sunday, semester.semesterId)
.mapToEntities(semester)
},
@ -90,7 +85,8 @@ class AttendanceRepository @Inject constructor(
timeId = attendance.timeId
)
}
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.excuseForAbsence(items, reason)
}
}

View file

@ -5,11 +5,7 @@ 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
import io.github.wulkanowy.utils.*
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@ -38,7 +34,8 @@ class AttendanceSummaryRepository @Inject constructor(
},
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getAttendanceSummary(subjectId)
.mapToEntities(semester, subjectId)
},

View file

@ -5,13 +5,7 @@ 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.monday
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract
import io.github.wulkanowy.utils.*
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate
import javax.inject.Inject
@ -51,7 +45,8 @@ class CompletedLessonsRepository @Inject constructor(
)
},
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getCompletedLessons(start.monday, end.sunday)
.mapToEntities(semester)
},

View file

@ -6,16 +6,10 @@ 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
import io.github.wulkanowy.utils.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneOffset
import javax.inject.Inject
import javax.inject.Singleton
@ -35,7 +29,7 @@ class ConferenceRepository @Inject constructor(
semester: Semester,
forceRefresh: Boolean,
notify: Boolean = false,
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
startDate: Instant = Instant.EPOCH,
) = networkBoundResource(
mutex = saveFetchResultMutex,
shouldFetch = {
@ -46,7 +40,8 @@ class ConferenceRepository @Inject constructor(
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
},
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getConferences()
.mapToEntities(semester)
.filter { it.date >= startDate }
@ -66,7 +61,7 @@ class ConferenceRepository @Inject constructor(
conferenceDb.loadAll(
diaryId = semester.diaryId,
studentId = semester.studentId,
startDate = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)
startDate = Instant.EPOCH,
)
suspend fun updateConference(conference: List<Conference>) = conferenceDb.updateAll(conference)

View file

@ -6,13 +6,7 @@ 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.endExamsDay
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.startExamsDay
import io.github.wulkanowy.utils.uniqueSubtract
import io.github.wulkanowy.utils.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate
@ -54,7 +48,8 @@ class ExamRepository @Inject constructor(
)
},
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getExams(start.startExamsDay, start.endExamsDay, semester.semesterId)
.mapToEntities(semester)
},

View file

@ -8,16 +8,12 @@ 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
import io.github.wulkanowy.utils.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDateTime
import java.time.Instant
import javax.inject.Inject
import javax.inject.Singleton
@ -51,7 +47,7 @@ class GradeRepository @Inject constructor(
},
fetch = {
val (details, summary) = sdk.init(student)
.switchDiary(semester.diaryId, semester.schoolYear)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getGrades(semester.semesterId)
details.mapToEntities(semester) to summary.mapToEntities(semester)
@ -70,8 +66,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 {
@ -101,13 +97,13 @@ class GradeRepository @Inject constructor(
}
summary.predictedGradeLastChange = when {
oldSummary == null -> LocalDateTime.now()
summary.predictedGrade != oldSummary.predictedGrade -> LocalDateTime.now()
oldSummary == null -> Instant.now()
summary.predictedGrade != oldSummary.predictedGrade -> Instant.now()
else -> oldSummary.predictedGradeLastChange
}
summary.finalGradeLastChange = when {
oldSummary == null -> LocalDateTime.now()
summary.finalGrade != oldSummary.finalGrade -> LocalDateTime.now()
oldSummary == null -> Instant.now()
summary.finalGrade != oldSummary.finalGrade -> Instant.now()
else -> oldSummary.finalGradeLastChange
}
})

View file

@ -12,13 +12,9 @@ import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems
import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems
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
import io.github.wulkanowy.utils.*
import kotlinx.coroutines.sync.Mutex
import java.util.Locale
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@ -54,7 +50,8 @@ class GradeStatisticsRepository @Inject constructor(
},
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getGradesPartialStatistics(semester.semesterId)
.mapToEntities(semester)
},
@ -101,7 +98,8 @@ class GradeStatisticsRepository @Inject constructor(
},
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getGradesSemesterStatistics(semester.semesterId)
.mapToEntities(semester)
},
@ -155,7 +153,8 @@ class GradeStatisticsRepository @Inject constructor(
},
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getGradesPointsStatistics(semester.semesterId)
.mapToEntities(semester)
},

View file

@ -6,13 +6,7 @@ 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.monday
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract
import io.github.wulkanowy.utils.*
import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate
import javax.inject.Inject
@ -53,7 +47,8 @@ class HomeworkRepository @Inject constructor(
)
},
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getHomework(start.monday, end.sunday)
.mapToEntities(semester)
},

View file

@ -8,11 +8,7 @@ import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken
import io.github.wulkanowy.data.pojos.MobileDeviceToken
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
import io.github.wulkanowy.utils.*
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@ -40,7 +36,8 @@ class MobileDeviceRepository @Inject constructor(
},
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getRegisteredDevices()
.mapToEntities(semester)
},
@ -53,14 +50,16 @@ class MobileDeviceRepository @Inject constructor(
)
suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.unregisterDevice(device.deviceId)
mobileDb.deleteAll(listOf(device))
}
suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken {
return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
return sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getToken()
.mapToMobileDeviceToken()
}

View file

@ -6,11 +6,7 @@ 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
import io.github.wulkanowy.utils.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
@ -42,7 +38,8 @@ class NoteRepository @Inject constructor(
},
query = { noteDb.loadAll(student.studentId) },
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getNotes(semester.semesterId)
.mapToEntities(semester)
},

View file

@ -7,21 +7,16 @@ import com.fredporciuncula.flow.preferences.FlowSharedPreferences
import com.fredporciuncula.flow.preferences.Preference
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.sdk.toLocalDate
import io.github.wulkanowy.data.enums.*
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
import io.github.wulkanowy.utils.toLocalDateTime
import io.github.wulkanowy.utils.toTimestamp
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.Instant
import javax.inject.Inject
import javax.inject.Singleton
@ -72,14 +67,16 @@ class PreferencesRepository @Inject constructor(
)
val appThemeKey = context.getString(R.string.pref_key_app_theme)
val appTheme: String
get() = getString(appThemeKey, R.string.pref_default_app_theme)
val appTheme: AppTheme
get() = AppTheme.getByValue(getString(appThemeKey, R.string.pref_default_app_theme))
val gradeColorTheme: String
get() = getString(
val gradeColorTheme: GradeColorTheme
get() = GradeColorTheme.getByValue(
getString(
R.string.pref_key_grade_color_scheme,
R.string.pref_default_grade_color_scheme
)
)
val appLanguageKey = context.getString(R.string.pref_key_app_language)
val appLanguage
@ -128,6 +125,12 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_notification_piggyback
)
val isNotificationPiggybackRemoveOriginalEnabled: Boolean
get() = getBoolean(
R.string.pref_key_notifications_piggyback_cancel_original,
R.bool.pref_default_notification_piggyback_cancel_original
)
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
val isDebugNotificationEnable: Boolean
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
@ -156,11 +159,13 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_timetable_show_groups
)
val showWholeClassPlan: String
get() = getString(
val showWholeClassPlan: TimetableMode
get() = TimetableMode.getByValue(
getString(
R.string.pref_key_timetable_show_whole_class,
R.string.pref_default_timetable_show_whole_class
)
)
val gradeSortingMode: GradeSortingMode
get() = GradeSortingMode.getByValue(
@ -195,10 +200,10 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_optional_arithmetic_average
)
var lasSyncDate: LocalDateTime
var lasSyncDate: Instant?
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()
.takeIf { it != 0L }?.let(Instant::ofEpochMilli)
set(value) = sharedPref.edit().putLong("last_sync_date", value?.toEpochMilli() ?: 0).apply()
var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
get() {
@ -257,11 +262,12 @@ class PreferencesRepository @Inject constructor(
get() = sharedPref.getInt(PREF_KEY_IN_APP_REVIEW_COUNT, 0)
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
var inAppReviewDate: LocalDate?
var inAppReviewDate: Instant?
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()
?.let(Instant::ofEpochMilli)
set(value) = sharedPref.edit {
putLong(PREF_KEY_IN_APP_REVIEW_DATE, value?.toEpochMilli() ?: 0)
}
var isAppReviewDone: Boolean
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)

View file

@ -38,7 +38,9 @@ class SchoolRepository @Inject constructor(
},
query = { schoolDb.load(semester.studentId, semester.classId) },
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getSchool()
.mapToEntity(semester)
},
saveFetchResult = { old, new ->

View file

@ -5,11 +5,7 @@ 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.DispatchersProvider
import io.github.wulkanowy.utils.getCurrentOrLast
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.isCurrent
import io.github.wulkanowy.utils.uniqueSubtract
import io.github.wulkanowy.utils.*
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
@ -43,10 +39,14 @@ class SemesterRepository @Inject constructor(
): Boolean {
val isNoSemesters = semesters.isEmpty()
val isRefreshOnModeChangeRequired =
if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
semesters.firstOrNull { it.isCurrent }?.diaryId == 0
} else false
val isRefreshOnModeChangeRequired = when {
Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API -> {
semesters.firstOrNull { it.isCurrent }?.let {
0 == it.diaryId && 0 == it.kindergartenDiaryId
} == true
}
else -> false
}
val isRefreshOnNoCurrentAppropriate =
refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent }

View file

@ -28,7 +28,8 @@ class StudentInfoRepository @Inject constructor(
shouldFetch = { it == null || forceRefresh },
query = { studentInfoDao.loadStudentInfo(student.studentId) },
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getStudentInfo().mapToEntity(semester)
},
saveFetchResult = { old, new ->

View file

@ -128,4 +128,7 @@ class StudentRepository @Inject constructor(
suspend fun updateStudentNickAndAvatar(studentNickAndAvatar: StudentNickAndAvatar) =
studentDb.update(studentNickAndAvatar)
suspend fun isOneUniqueStudent() = getSavedStudents(false)
.distinctBy { it.student.studentName }.size == 1
}

View file

@ -5,11 +5,7 @@ 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
import io.github.wulkanowy.utils.*
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@ -37,7 +33,8 @@ class SubjectRepository @Inject constructor(
},
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getSubjects().mapToEntities(semester)
},
saveFetchResult = { old, new ->

View file

@ -5,11 +5,7 @@ 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
import io.github.wulkanowy.utils.*
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@ -37,7 +33,8 @@ class TeacherRepository @Inject constructor(
},
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getTeachers(semester.semesterId)
.mapToEntities(semester)
},

View file

@ -3,22 +3,12 @@ package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.pojos.TimetableFull
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract
import io.github.wulkanowy.utils.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.sync.Mutex
@ -62,7 +52,7 @@ class TimetableRepository @Inject constructor(
query = { getFullTimetableFromDatabase(student, semester, start, end) },
fetch = {
val timetableFull = sdk.init(student)
.switchDiary(semester.diaryId, semester.schoolYear)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getTimetableFull(start.monday, end.sunday)
timetableFull.mapToEntities(semester)
@ -152,7 +142,8 @@ class TimetableRepository @Inject constructor(
old: List<TimetableAdditional>,
new: List<TimetableAdditional>
) {
timetableAdditionalDb.deleteAll(old uniqueSubtract new)
val oldFiltered = old.filter { !it.isAddedByUser }
timetableAdditionalDb.deleteAll(oldFiltered uniqueSubtract new)
timetableAdditionalDb.insertAll(new uniqueSubtract old)
}
@ -160,4 +151,14 @@ class TimetableRepository @Inject constructor(
timetableHeaderDb.deleteAll(old uniqueSubtract new)
timetableHeaderDb.insertAll(new uniqueSubtract old)
}
suspend fun saveAdditionalList(additionalList: List<TimetableAdditional>) =
timetableAdditionalDb.insertAll(additionalList)
suspend fun deleteAdditional(additional: TimetableAdditional, deleteSeries: Boolean) =
if (deleteSeries) {
timetableAdditionalDb.deleteAllByRepeatId(additional.repeatId!!)
} else {
timetableAdditionalDb.deleteAll(listOf(additional))
}
}

View file

@ -1,9 +0,0 @@
package io.github.wulkanowy.services
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
abstract class HiltBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {}
}

View file

@ -1,6 +1,7 @@
package io.github.wulkanowy.services.alarm
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
@ -12,14 +13,12 @@ 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
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.PendingIntentCompat
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.getCompatColor
import io.github.wulkanowy.utils.toLocalDateTime
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.launchIn
@ -28,7 +27,7 @@ import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class TimetableNotificationReceiver : HiltBroadcastReceiver() {
class TimetableNotificationReceiver : BroadcastReceiver() {
@Inject
lateinit var studentRepository: StudentRepository
@ -41,6 +40,8 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
const val NOTIFICATION_TYPE_UPCOMING = 2
const val NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION = 3
// FIXME only shows one notification even if there are multiple students.
// Probably want to fix after #721 is merged.
const val NOTIFICATION_ID = 2137
const val STUDENT_NAME = "student_name"
@ -56,20 +57,24 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
@OptIn(DelicateCoroutinesApi::class)
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
Timber.d("Receiving intent... ${intent.toUri(0)}")
flowWithResource {
val showStudentName = !studentRepository.isOneUniqueStudent()
val student = studentRepository.getCurrentStudent(false)
val studentId = intent.getIntExtra(STUDENT_ID, 0)
if (student.studentId == studentId) prepareNotification(context, intent)
else Timber.d("Notification studentId($studentId) differs from current(${student.studentId})")
if (student.studentId == studentId) {
prepareNotification(context, intent, showStudentName)
} else {
Timber.d("Notification studentId($studentId) differs from current(${student.studentId})")
}
}.onEach {
if (it.status == Status.ERROR) Timber.e(it.error!!)
}.launchIn(GlobalScope)
}
private fun prepareNotification(context: Context, intent: Intent) {
private fun prepareNotification(context: Context, intent: Intent, showStudentName: Boolean) {
val type = intent.getIntExtra(LESSON_TYPE, 0)
val isPersistent = preferencesRepository.isUpcomingLessonsNotificationsPersistent
@ -78,7 +83,7 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
}
val studentId = intent.getIntExtra(STUDENT_ID, 0)
val studentName = intent.getStringExtra(STUDENT_NAME)
val studentName = intent.getStringExtra(STUDENT_NAME).takeIf { showStudentName }
val subject = intent.getStringExtra(LESSON_TITLE)
val room = intent.getStringExtra(LESSON_ROOM)
@ -89,21 +94,28 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
val nextSubject = intent.getStringExtra(LESSON_NEXT_TITLE)
val nextRoom = intent.getStringExtra(LESSON_NEXT_ROOM)
Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId")
Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: $start, student: $studentId")
showNotification(
context, 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 {
val notificationTitleResId =
if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next
val notificationTitle =
context.getString(notificationTitleResId, "($room) $subject".removePrefix("()"))
val nextLessonText = nextSubject?.let {
context.getString(
R.string.timetable_later,
"($nextRoom) $nextSubject".removePrefix("()")
)
}
showNotification(
context = context,
isPersistent = isPersistent,
studentName = studentName,
countDown = if (type == NOTIFICATION_TYPE_CURRENT) end else start,
timeout = end - start,
title = notificationTitle,
next = nextLessonText
)
}
@ -130,9 +142,10 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
.setTimeoutAfter(timeout)
.setSmallIcon(R.drawable.ic_stat_timetable)
.setColor(context.getCompatColor(R.color.colorPrimary))
.setStyle(NotificationCompat.InboxStyle().also {
it.setSummaryText(studentName)
it.addLine(next)
.setStyle(NotificationCompat.InboxStyle()
.addLine(next)
.also { inboxStyle ->
studentName?.let { inboxStyle.setSummaryText(it) }
})
.setContentIntent(
PendingIntent.getActivity(

View file

@ -28,12 +28,12 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio
import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.PendingIntentCompat
import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.toTimestamp
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.time.Duration.ofMinutes
import java.time.Instant
import java.time.Instant.now
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalDateTime.now
import javax.inject.Inject
class TimetableNotificationSchedulerHelper @Inject constructor(
@ -43,14 +43,14 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
private val dispatchersProvider: DispatchersProvider,
) {
private fun getRequestCode(time: LocalDateTime, studentId: Int) =
(time.toTimestamp() * studentId).toInt()
private fun getRequestCode(time: Instant, studentId: Int): Int =
(time.toEpochMilli() * studentId).toInt()
private fun getUpcomingLessonTime(
index: Int,
day: List<Timetable>,
lesson: Timetable
) = day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
): Instant = day.getOrNull(index - 1)?.end ?: lesson.start.minus(ofMinutes(30))
suspend fun cancelScheduled(lessons: List<Timetable>, student: Student) {
val studentId = student.studentId
@ -71,7 +71,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
}
}
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
private fun cancelScheduledTo(range: ClosedRange<Instant>, requestCode: Int) {
if (now() in range) cancelNotification()
alarmManager.cancel(
@ -150,8 +150,8 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
putExtra(STUDENT_ID, student.studentId)
putExtra(STUDENT_NAME, student.nickOrName)
putExtra(LESSON_ROOM, lesson.room)
putExtra(LESSON_START, lesson.start.toTimestamp())
putExtra(LESSON_END, lesson.end.toTimestamp())
putExtra(LESSON_START, lesson.start.toEpochMilli())
putExtra(LESSON_END, lesson.end.toEpochMilli())
putExtra(LESSON_TITLE, lesson.subject)
putExtra(LESSON_NEXT_TITLE, nextLesson?.subject)
putExtra(LESSON_NEXT_ROOM, nextLesson?.room)
@ -162,11 +162,11 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
intent: Intent,
studentId: Int,
notificationType: Int,
time: LocalDateTime
time: Instant
) {
try {
AlarmManagerCompat.setExactAndAllowWhileIdle(
alarmManager, RTC_WAKEUP, time.toTimestamp(),
alarmManager, RTC_WAKEUP, time.toEpochMilli(),
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
it.putExtra(LESSON_TYPE, notificationType)
}, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE)

View file

@ -19,6 +19,9 @@ class VulcanNotificationListenerService : NotificationListenerService() {
override fun onNotificationPosted(statusBarNotification: StatusBarNotification?) {
if (statusBarNotification?.packageName == "pl.edu.vulcan.hebe" && preferenceRepository.isNotificationPiggybackEnabled) {
syncManager.startOneTimeSyncWorker()
if (preferenceRepository.isNotificationPiggybackRemoveOriginalEnabled) {
cancelNotification(statusBarNotification.key)
}
}
}
}

View file

@ -74,10 +74,12 @@ class SyncManager @Inject constructor(
}
}
fun startOneTimeSyncWorker(): Flow<WorkInfo?> {
// if quiet, no notifications will be sent
fun startOneTimeSyncWorker(quiet: Boolean = false): Flow<WorkInfo?> {
val work = OneTimeWorkRequestBuilder<SyncWorker>()
.setInputData(
Data.Builder()
.putBoolean("quiet", quiet)
.putBoolean("one_time", true)
.build()
)

View file

@ -23,7 +23,7 @@ import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.getCompatColor
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.time.LocalDateTime
import java.time.Instant
import kotlin.random.Random
@HiltWorker
@ -38,18 +38,23 @@ class SyncWorker @AssistedInject constructor(
private val dispatchersProvider: DispatchersProvider
) : CoroutineWorker(appContext, workerParameters) {
override suspend fun doWork() = withContext(dispatchersProvider.io) {
override suspend fun doWork(): Result = withContext(dispatchersProvider.io) {
Timber.i("SyncWorker is starting")
if (!studentRepository.isCurrentStudentSet()) return@withContext Result.failure()
val (student, semester) = try {
val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student, true)
student to semester
} catch (e: Throwable) {
return@withContext getResultFromErrors(listOf(e))
}
val exceptions = works.mapNotNull { work ->
try {
Timber.i("${work::class.java.simpleName} is starting")
work.doWork(student, semester)
work.doWork(student, semester, isNotificationsEnabled())
Timber.i("${work::class.java.simpleName} result: Success")
null
} catch (e: Throwable) {
@ -62,20 +67,7 @@ class SyncWorker @AssistedInject constructor(
}
}
}
val result = when {
exceptions.isNotEmpty() && inputData.getBoolean("one_time", false) -> {
Result.failure(
Data.Builder()
.putString("error", exceptions.map { it.stackTraceToString() }.toString())
.build()
)
}
exceptions.isNotEmpty() -> Result.retry()
else -> {
preferencesRepository.lasSyncDate = LocalDateTime.now()
Result.success()
}
}
val result = getResultFromErrors(exceptions)
if (preferencesRepository.isDebugNotificationEnable) notify(result)
Timber.i("SyncWorker result: $result")
@ -83,6 +75,27 @@ class SyncWorker @AssistedInject constructor(
return@withContext result
}
private fun isNotificationsEnabled(): Boolean {
val quiet = inputData.getBoolean("quiet", false)
return preferencesRepository.isNotificationsEnable && !quiet
}
private fun getResultFromErrors(errors: List<Throwable>): Result = when {
errors.isNotEmpty() && inputData.getBoolean("one_time", false) -> {
Result.failure(
Data.Builder()
.putString("error_message", errors.joinToString { it.message.toString() })
.putString("error_stack", errors.map { it.stackTraceToString() }.toString())
.build()
)
}
errors.isNotEmpty() -> Result.retry()
else -> {
preferencesRepository.lasSyncDate = Instant.now()
Result.success()
}
}
private fun notify(result: Result) {
notificationManager.notify(
Random.nextInt(Int.MAX_VALUE),

View file

@ -18,7 +18,7 @@ import io.github.wulkanowy.utils.PendingIntentCompat
import io.github.wulkanowy.utils.getCompatBitmap
import io.github.wulkanowy.utils.getCompatColor
import io.github.wulkanowy.utils.nickOrName
import java.time.LocalDateTime
import java.time.Instant
import javax.inject.Inject
import kotlin.random.Random
@ -57,7 +57,7 @@ class AppNotificationManager @Inject constructor(
NotificationCompat.BigTextStyle()
.bigText(notificationData.content)
.also { builder ->
if (shouldShowStudentName()) {
if (!studentRepository.isOneUniqueStudent()) {
builder.setSummaryText(student.nickOrName)
}
}
@ -74,7 +74,7 @@ class AppNotificationManager @Inject constructor(
student: Student
) {
val notificationType = groupNotificationData.type
val groupType = notificationType.group ?: return
val groupType = notificationType.channel
val group = "${groupType}_${student.id}"
sendSummaryNotification(groupNotificationData, group, student)
@ -102,7 +102,7 @@ class AppNotificationManager @Inject constructor(
NotificationCompat.BigTextStyle()
.bigText(notificationData.content)
.also { builder ->
if (shouldShowStudentName()) {
if (!studentRepository.isOneUniqueStudent()) {
builder.setSummaryText(student.nickOrName)
}
}
@ -134,7 +134,7 @@ class AppNotificationManager @Inject constructor(
.setStyle(
NotificationCompat.InboxStyle()
.also { builder ->
if (shouldShowStudentName()) {
if (!studentRepository.isOneUniqueStudent()) {
builder.setSummaryText(student.nickOrName)
}
groupNotificationData.notificationDataList.forEach {
@ -169,12 +169,9 @@ class AppNotificationManager @Inject constructor(
title = notificationData.title,
content = notificationData.content,
type = notificationType,
date = LocalDateTime.now()
date = Instant.now(),
)
notificationRepository.saveNotification(notificationEntity)
}
private suspend fun shouldShowStudentName(): Boolean =
studentRepository.getSavedStudents(decryptPass = false).size > 1
}

View file

@ -11,8 +11,8 @@ import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.getPlural
import io.github.wulkanowy.utils.toFormattedString
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import javax.inject.Inject
class ChangeTimetableNotification @Inject constructor(
@ -21,7 +21,7 @@ class ChangeTimetableNotification @Inject constructor(
) {
suspend fun notify(items: List<Timetable>, student: Student) {
val currentTime = LocalDateTime.now()
val currentTime = Instant.now()
val changedLessons = items.filter { (it.canceled || it.changes) && it.start > currentTime }
val notificationDataList = changedLessons.groupBy { it.date }
.map { (date, lessons) ->

View file

@ -11,7 +11,7 @@ import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.getPlural
import io.github.wulkanowy.utils.toFormattedString
import java.time.LocalDateTime
import java.time.Instant
import javax.inject.Inject
class NewConferenceNotification @Inject constructor(
@ -20,7 +20,7 @@ class NewConferenceNotification @Inject constructor(
) {
suspend fun notify(items: List<Conference>, student: Student) {
val today = LocalDateTime.now()
val today = Instant.now()
val lines = items.filter { !it.date.isBefore(today) }
.map {
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"

View file

@ -22,7 +22,10 @@ class NewGradeNotification @Inject constructor(
val notificationDataList = items.map {
NotificationData(
title = context.getPlural(R.plurals.grade_new_items, 1),
content = "${it.subject}: ${it.entry}",
content = buildString {
append("${it.subject}: ${it.entry}")
if (it.comment.isNotBlank()) append(" (${it.comment})")
},
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
)
}

View file

@ -1,6 +1,7 @@
package io.github.wulkanowy.services.sync.notifications
import android.content.Context
import androidx.core.text.parseAsHtml
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
@ -28,7 +29,7 @@ class NewSchoolAnnouncementNotification @Inject constructor(
R.plurals.school_announcement_notify_new_item_title,
1
),
content = "${it.subject}: ${it.content}"
content = "${it.subject}: ${it.content.parseAsHtml()}"
)
}
val groupNotificationData = GroupNotificationData(

View file

@ -14,72 +14,58 @@ import io.github.wulkanowy.services.sync.channels.PushChannel
import io.github.wulkanowy.services.sync.channels.TimetableChangeChannel
enum class NotificationType(
val group: String?,
val channel: String,
val icon: Int
) {
NEW_CONFERENCE(
group = "new_conferences_group",
channel = NewConferencesChannel.CHANNEL_ID,
icon = R.drawable.ic_more_conferences,
),
NEW_EXAM(
group = "new_exam_group",
channel = NewExamChannel.CHANNEL_ID,
icon = R.drawable.ic_main_exam
),
NEW_GRADE_DETAILS(
group = "new_grade_details_group",
channel = NewGradesChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_grade,
),
NEW_GRADE_PREDICTED(
group = "new_grade_predicted_group",
channel = NewGradesChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_grade,
),
NEW_GRADE_FINAL(
group = "new_grade_final_group",
channel = NewGradesChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_grade,
),
NEW_HOMEWORK(
group = "new_homework_group",
channel = NewHomeworkChannel.CHANNEL_ID,
icon = R.drawable.ic_more_homework,
),
NEW_LUCKY_NUMBER(
group = null,
channel = LuckyNumberChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_luckynumber,
),
NEW_MESSAGE(
group = "new_message_group",
channel = NewMessagesChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_message,
),
NEW_NOTE(
group = "new_notes_group",
channel = NewNotesChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_note
),
NEW_ANNOUNCEMENT(
group = "new_school_announcements_group",
channel = NewSchoolAnnouncementsChannel.CHANNEL_ID,
icon = R.drawable.ic_all_about
),
CHANGE_TIMETABLE(
group = "change_timetable_group",
channel = TimetableChangeChannel.CHANNEL_ID,
icon = R.drawable.ic_main_timetable
),
NEW_ATTENDANCE(
group = "new_attendance_group",
channel = NewAttendanceChannel.CHANNEL_ID,
icon = R.drawable.ic_main_attendance
),
PUSH(
group = null,
channel = PushChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_all
)

View file

@ -10,7 +10,12 @@ class AttendanceSummaryWork @Inject constructor(
private val attendanceSummaryRepository: AttendanceSummaryRepository
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
attendanceSummaryRepository.getAttendanceSummary(student, semester, -1, true).waitForResult()
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
attendanceSummaryRepository.getAttendanceSummary(
student = student,
semester = semester,
subjectId = -1,
forceRefresh = true,
).waitForResult()
}
}

View file

@ -3,7 +3,6 @@ package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.AttendanceRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification
import io.github.wulkanowy.utils.previousOrSameSchoolDay
import io.github.wulkanowy.utils.waitForResult
@ -14,17 +13,16 @@ import javax.inject.Inject
class AttendanceWork @Inject constructor(
private val attendanceRepository: AttendanceRepository,
private val newAttendanceNotification: NewAttendanceNotification,
private val preferencesRepository: PreferencesRepository
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
attendanceRepository.getAttendance(
student = student,
semester = semester,
start = now().previousOrSameSchoolDay,
end = now().previousOrSameSchoolDay,
forceRefresh = true,
notify = preferencesRepository.isNotificationsEnable
notify = notify,
)
.waitForResult()

View file

@ -13,7 +13,13 @@ class CompletedLessonWork @Inject constructor(
private val completedLessonsRepository: CompletedLessonsRepository
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
completedLessonsRepository.getCompletedLessons(student, semester, now().monday, now().sunday, true).waitForResult()
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
completedLessonsRepository.getCompletedLessons(
student = student,
semester = semester,
start = now().monday,
end = now().sunday,
forceRefresh = true,
).waitForResult()
}
}

View file

@ -3,7 +3,6 @@ package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.ConferenceRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.services.sync.notifications.NewConferenceNotification
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first
@ -11,16 +10,15 @@ import javax.inject.Inject
class ConferenceWork @Inject constructor(
private val conferenceRepository: ConferenceRepository,
private val preferencesRepository: PreferencesRepository,
private val newConferenceNotification: NewConferenceNotification,
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
conferenceRepository.getConferences(
student = student,
semester = semester,
forceRefresh = true,
notify = preferencesRepository.isNotificationsEnable
notify = notify
).waitForResult()
conferenceRepository.getConferenceFromDatabase(semester).first()

View file

@ -3,7 +3,6 @@ package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.ExamRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.services.sync.notifications.NewExamNotification
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first
@ -12,18 +11,17 @@ import javax.inject.Inject
class ExamWork @Inject constructor(
private val examRepository: ExamRepository,
private val preferencesRepository: PreferencesRepository,
private val newExamNotification: NewExamNotification,
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
examRepository.getExams(
student = student,
semester = semester,
start = now(),
end = now(),
forceRefresh = true,
notify = preferencesRepository.isNotificationsEnable
notify = notify,
).waitForResult()
examRepository.getExamsFromDatabase(semester, now()).first()

View file

@ -10,7 +10,7 @@ class GradeStatisticsWork @Inject constructor(
private val gradeStatisticsRepository: GradeStatisticsRepository
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
with(gradeStatisticsRepository) {
getGradesPartialStatistics(student, semester, "Wszystkie", forceRefresh = true).waitForResult()
getGradesSemesterStatistics(student, semester, "Wszystkie", forceRefresh = true).waitForResult()

View file

@ -3,7 +3,6 @@ package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.services.sync.notifications.NewGradeNotification
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first
@ -11,16 +10,15 @@ import javax.inject.Inject
class GradeWork @Inject constructor(
private val gradeRepository: GradeRepository,
private val preferencesRepository: PreferencesRepository,
private val newGradeNotification: NewGradeNotification,
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
gradeRepository.getGrades(
student = student,
semester = semester,
forceRefresh = true,
notify = preferencesRepository.isNotificationsEnable
notify = notify,
).waitForResult()
gradeRepository.getGradesFromDatabase(semester).first()

View file

@ -3,7 +3,6 @@ package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.HomeworkRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification
import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.waitForResult
@ -13,18 +12,17 @@ import javax.inject.Inject
class HomeworkWork @Inject constructor(
private val homeworkRepository: HomeworkRepository,
private val preferencesRepository: PreferencesRepository,
private val newHomeworkNotification: NewHomeworkNotification,
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
homeworkRepository.getHomework(
student = student,
semester = semester,
start = now().nextOrSameSchoolDay,
end = now().nextOrSameSchoolDay,
forceRefresh = true,
notify = preferencesRepository.isNotificationsEnable
notify = notify,
).waitForResult()
homeworkRepository.getHomeworkFromDatabase(semester, now(), now().plusDays(7)).first()

View file

@ -3,22 +3,20 @@ package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.LuckyNumberRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.services.sync.notifications.NewLuckyNumberNotification
import io.github.wulkanowy.utils.waitForResult
import javax.inject.Inject
class LuckyNumberWork @Inject constructor(
private val luckyNumberRepository: LuckyNumberRepository,
private val preferencesRepository: PreferencesRepository,
private val newLuckyNumberNotification: NewLuckyNumberNotification,
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
luckyNumberRepository.getLuckyNumber(
student = student,
forceRefresh = true,
notify = preferencesRepository.isNotificationsEnable
notify = notify,
).waitForResult()
luckyNumberRepository.getNotNotifiedLuckyNumber(student)?.let {

View file

@ -4,7 +4,6 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.services.sync.notifications.NewMessageNotification
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first
@ -12,17 +11,16 @@ import javax.inject.Inject
class MessageWork @Inject constructor(
private val messageRepository: MessageRepository,
private val preferencesRepository: PreferencesRepository,
private val newMessageNotification: NewMessageNotification,
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
messageRepository.getMessages(
student = student,
semester = semester,
folder = RECEIVED,
forceRefresh = true,
notify = preferencesRepository.isNotificationsEnable
notify = notify
).waitForResult()
messageRepository.getMessagesFromDatabase(student).first()

View file

@ -3,7 +3,6 @@ package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.NoteRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.services.sync.notifications.NewNoteNotification
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first
@ -11,16 +10,15 @@ import javax.inject.Inject
class NoteWork @Inject constructor(
private val noteRepository: NoteRepository,
private val preferencesRepository: PreferencesRepository,
private val newNoteNotification: NewNoteNotification,
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
noteRepository.getNotes(
student = student,
semester = semester,
forceRefresh = true,
notify = preferencesRepository.isNotificationsEnable
notify = notify,
).waitForResult()
noteRepository.getNotesFromDatabase(student).first()

View file

@ -11,7 +11,7 @@ class RecipientWork @Inject constructor(
private val recipientRepository: RecipientRepository
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
reportingUnitRepository.refreshReportingUnits(student)
reportingUnitRepository.getReportingUnits(student).let { units ->

View file

@ -2,7 +2,6 @@ package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository
import io.github.wulkanowy.services.sync.notifications.NewSchoolAnnouncementNotification
import io.github.wulkanowy.utils.waitForResult
@ -11,15 +10,14 @@ import javax.inject.Inject
class SchoolAnnouncementWork @Inject constructor(
private val schoolAnnouncementRepository: SchoolAnnouncementRepository,
private val preferencesRepository: PreferencesRepository,
private val newSchoolAnnouncementNotification: NewSchoolAnnouncementNotification,
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
schoolAnnouncementRepository.getSchoolAnnouncements(
student = student,
forceRefresh = true,
notify = preferencesRepository.isNotificationsEnable
notify = notify,
).waitForResult()

View file

@ -8,7 +8,7 @@ import javax.inject.Inject
class TeacherWork @Inject constructor(private val teacherRepository: TeacherRepository) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
teacherRepository.getTeachers(student, semester, true).waitForResult()
}
}

View file

@ -2,7 +2,6 @@ package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification
import io.github.wulkanowy.utils.nextOrSameSchoolDay
@ -14,17 +13,16 @@ import javax.inject.Inject
class TimetableWork @Inject constructor(
private val timetableRepository: TimetableRepository,
private val changeTimetableNotification: ChangeTimetableNotification,
private val preferencesRepository: PreferencesRepository
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
timetableRepository.getTimetable(
student = student,
semester = semester,
start = now().nextOrSameSchoolDay,
end = now().nextOrSameSchoolDay,
forceRefresh = true,
notify = preferencesRepository.isNotificationsEnable
notify = notify,
)
.waitForResult()

View file

@ -5,5 +5,5 @@ import io.github.wulkanowy.data.db.entities.Student
interface Work {
suspend fun doWork(student: Student, semester: Semester)
suspend fun doWork(student: Student, semester: Semester, notify: Boolean)
}

View file

@ -1,100 +1,82 @@
package io.github.wulkanowy.ui.base
import android.app.Dialog
import android.content.ClipData
import android.content.ClipboardManager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import androidx.appcompat.app.AlertDialog
import androidx.core.content.getSystemService
import androidx.core.os.bundleOf
import androidx.core.view.isGone
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.DialogErrorBinding
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.getString
import io.github.wulkanowy.utils.openAppInMarket
import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser
import okhttp3.internal.http2.StreamResetException
import java.io.InterruptedIOException
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import io.github.wulkanowy.utils.*
import javax.inject.Inject
@AndroidEntryPoint
class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
private lateinit var error: Throwable
class ErrorDialog : DialogFragment() {
@Inject
lateinit var appInfo: AppInfo
companion object {
private const val ARGUMENT_KEY = "Data"
private const val ARGUMENT_KEY = "error"
fun newInstance(error: Throwable) = ErrorDialog().apply {
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, error) }
arguments = bundleOf(ARGUMENT_KEY to error)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
arguments?.run {
error = getSerializable(ARGUMENT_KEY) as Throwable
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val error = requireArguments().getSerializable(ARGUMENT_KEY) as Throwable
val binding = DialogErrorBinding.inflate(LayoutInflater.from(context))
binding.bindErrorDetails(error)
return getAlertDialog(binding, error).apply {
enableReportButtonIfErrorIsReportable(error)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = DialogErrorBinding.inflate(inflater).apply { binding = this }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
private fun getAlertDialog(binding: DialogErrorBinding, error: Throwable): AlertDialog {
return MaterialAlertDialogBuilder(requireContext()).apply {
val errorStacktrace = error.stackTraceToString()
with(binding) {
errorDialogContent.text = errorStacktrace.replace(": ${error.localizedMessage}", "")
with(errorDialogHorizontalScroll) {
post { fullScroll(HorizontalScrollView.FOCUS_LEFT) }
}
errorDialogCopy.setOnClickListener {
val clip = ClipData.newPlainText("Error details", errorStacktrace)
activity?.getSystemService<ClipboardManager>()?.setPrimaryClip(clip)
Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show()
}
errorDialogCancel.setOnClickListener { dismiss() }
errorDialogReport.setOnClickListener {
setTitle(R.string.all_details)
setView(binding.root)
setNeutralButton(R.string.about_feedback) { _, _ ->
openConfirmDialog { openEmailClient(errorStacktrace) }
}
errorDialogHumanizedMessage.text = resources.getString(error)
setNegativeButton(android.R.string.cancel) { _, _ -> }
setPositiveButton(android.R.string.copy) { _, _ -> copyErrorToClipboard(errorStacktrace) }
}.create()
}
private fun DialogErrorBinding.bindErrorDetails(error: Throwable) {
return with(this) {
errorDialogHumanizedMessage.text = resources.getErrorString(error)
errorDialogErrorMessage.text = error.localizedMessage
errorDialogErrorMessage.isGone = error.localizedMessage.isNullOrBlank()
errorDialogReport.isEnabled = when (error) {
is UnknownHostException,
is InterruptedIOException,
is ConnectException,
is StreamResetException,
is SocketTimeoutException,
is ServiceUnavailableException,
is FeatureDisabledException,
is FeatureNotAvailableException -> false
else -> true
errorDialogContent.text = error.stackTraceToString()
.replace(": ${error.localizedMessage}", "")
}
}
private fun AlertDialog.enableReportButtonIfErrorIsReportable(error: Throwable) {
setOnShowListener {
getButton(AlertDialog.BUTTON_NEUTRAL).isEnabled = error.isShouldBeReported()
}
}
private fun copyErrorToClipboard(errorStacktrace: String) {
val clip = ClipData.newPlainText("Error details", errorStacktrace)
requireActivity().getSystemService<ClipboardManager>()?.setPrimaryClip(clip)
Toast.makeText(requireContext(), R.string.all_copied, LENGTH_LONG).show()
}
private fun openConfirmDialog(callback: () -> Unit) {
@ -127,4 +109,8 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
}
)
}
private fun showMessage(text: String) {
Toast.makeText(requireContext(), text, LENGTH_LONG).show()
}
}

View file

@ -5,7 +5,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
import io.github.wulkanowy.utils.getString
import io.github.wulkanowy.utils.getErrorString
import io.github.wulkanowy.utils.security.ScramblerException
import timber.log.Timber
import javax.inject.Inject
@ -26,7 +26,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
}
protected open fun proceed(error: Throwable) {
showErrorMessage(context.resources.getString(error), error)
showErrorMessage(context.resources.getErrorString(error), error)
when (error) {
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
is ScramblerException, is BadCredentialsException -> onSessionExpired()

View file

@ -7,6 +7,7 @@ import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.AppTheme
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.main.MainActivity
@ -20,7 +21,7 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
fun applyActivityTheme(activity: AppCompatActivity) {
if (isThemeApplicable(activity)) {
applyDefaultTheme()
if (preferencesRepository.appTheme == "black") {
if (preferencesRepository.appTheme == AppTheme.BLACK) {
when (activity) {
is MainActivity -> activity.setTheme(R.style.WulkanowyTheme_Black)
is LoginActivity -> activity.setTheme(R.style.WulkanowyTheme_Login_Black)
@ -32,11 +33,10 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
fun applyDefaultTheme() {
AppCompatDelegate.setDefaultNightMode(
when (val theme = preferencesRepository.appTheme) {
"light" -> MODE_NIGHT_NO
"dark", "black" -> MODE_NIGHT_YES
"system" -> MODE_NIGHT_FOLLOW_SYSTEM
else -> throw IllegalArgumentException("Wrong theme: $theme")
when (preferencesRepository.appTheme) {
AppTheme.LIGHT -> MODE_NIGHT_NO
AppTheme.DARK, AppTheme.BLACK -> MODE_NIGHT_YES
AppTheme.SYSTEM -> MODE_NIGHT_FOLLOW_SYSTEM
}
)
}

View file

@ -13,13 +13,8 @@ import io.github.wulkanowy.ui.modules.about.license.LicenseFragment
import io.github.wulkanowy.ui.modules.debug.DebugFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.getCompatDrawable
import io.github.wulkanowy.utils.openAppInMarket
import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.toLocalDateTime
import io.github.wulkanowy.utils.*
import java.time.Instant
import javax.inject.Inject
@AndroidEntryPoint
@ -38,7 +33,7 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about
override val versionRes: Triple<String, String, Drawable?>?
get() = context?.run {
val buildTimestamp =
appInfo.buildTimestamp.toLocalDateTime().toFormattedString("yyyy-MM-dd")
Instant.ofEpochMilli(appInfo.buildTimestamp).toFormattedString("yyyy-MM-dd")
val versionSignature =
"${appInfo.versionName}-${appInfo.buildFlavor} (${appInfo.versionCode}), $buildTimestamp"
Triple(

View file

@ -14,8 +14,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.view.ActionMode
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.datepicker.CalendarConstraints
import com.google.android.material.datepicker.MaterialDatePicker
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance
@ -27,12 +25,10 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.SchoolDaysValidator
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.schoolYearStart
import io.github.wulkanowy.utils.toLocalDateTime
import io.github.wulkanowy.utils.toTimestamp
import io.github.wulkanowy.utils.openMaterialDatePicker
import java.time.LocalDate
import javax.inject.Inject
@ -224,29 +220,15 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
(activity as? MainActivity)?.showDialogFragment(AttendanceDialog.newInstance(lesson))
}
override fun showDatePickerDialog(currentDate: LocalDate) {
val baseDate = currentDate.schoolYearStart
val rangeStart = baseDate.toTimestamp()
val rangeEnd = LocalDate.now().plusWeeks(1).toTimestamp()
val constraintsBuilder = CalendarConstraints.Builder().apply {
setValidator(SchoolDaysValidator(rangeStart, rangeEnd))
setStart(rangeStart)
setEnd(rangeEnd)
}
val datePicker = MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(constraintsBuilder.build())
.setSelection(currentDate.toTimestamp())
.build()
datePicker.addOnPositiveButtonClickListener {
val date = it.toLocalDateTime()
presenter.onDateSet(date.year, date.monthValue, date.dayOfMonth)
}
if (!parentFragmentManager.isStateSaved) {
datePicker.show(parentFragmentManager, null)
override fun showDatePickerDialog(selectedDate: LocalDate) {
openMaterialDatePicker(
selected = selectedDate,
rangeStart = selectedDate.firstSchoolDayInSchoolYear,
rangeEnd = LocalDate.now().plusWeeks(1),
onDateSelected = {
presenter.onDateSet(it.year, it.monthValue, it.dayOfMonth)
}
)
}
override fun showExcuseDialog() {

View file

@ -48,7 +48,7 @@ interface AttendanceView : BaseView {
fun showAttendanceDialog(lesson: Attendance)
fun showDatePickerDialog(currentDate: LocalDate)
fun showDatePickerDialog(selectedDate: LocalDate)
fun showExcuseDialog()

View file

@ -20,6 +20,7 @@ import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.databinding.ItemDashboardAccountBinding
import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding
import io.github.wulkanowy.databinding.ItemDashboardAnnouncementsBinding
@ -36,9 +37,7 @@ import io.github.wulkanowy.utils.left
import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.toFormattedString
import timber.log.Timber
import java.time.Duration
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.*
import java.util.Timer
import javax.inject.Inject
import kotlin.concurrent.timer
@ -262,7 +261,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
val isLoading = item.isLoading
val dashboardGradesAdapter = gradesViewHolder.adapter.apply {
this.items = subjectWithGrades.toList()
this.gradeTheme = gradeTheme.orEmpty()
this.gradeColorTheme = gradeTheme ?: GradeColorTheme.VULCAN
}
with(gradesViewHolder.binding) {
@ -290,7 +289,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
var dateToNavigate = LocalDate.now()
fun updateLessonState() {
val currentDateTime = LocalDateTime.now()
val currentDateTime = Instant.now()
val currentDate = LocalDate.now()
val tomorrowDate = currentDate.plusDays(1)
@ -360,7 +359,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
binding: ItemDashboardLessonsBinding,
header: TimetableHeader? = null,
) {
val currentDateTime = LocalDateTime.now()
val currentDateTime = Instant.now()
val nextLessons = timetableToShow.filter { it.end.isAfter(currentDateTime) }
.sortedBy { it.start }
@ -385,7 +384,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
private fun updateFirstLessonView(
binding: ItemDashboardLessonsBinding,
firstLesson: Timetable?,
currentDateTime: LocalDateTime
currentDateTime: Instant
) {
val context = binding.root.context
val sansSerifFont = Typeface.create("sans-serif", Typeface.NORMAL)
@ -421,10 +420,12 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
isFirstTimeRangeVisible = true
isFirstTimeVisible = false
} else {
firstTimeText = context.resources.getQuantityString(
R.plurals.dashboard_timetable_first_lesson_time_in_minutes,
minutesToStartLesson.toInt(),
minutesToStartLesson
firstTimeText = context.getString(
R.string.timetable_time_until,
context.getString(
R.string.timetable_minutes,
minutesToStartLesson.toString()
)
)
firstTimeRangeText = ""
@ -460,10 +461,12 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
0
}
firstTimeText = context.resources.getQuantityString(
R.plurals.dashboard_timetable_first_lesson_time_more_minutes,
minutesToEndLesson.toInt(),
minutesToEndLesson
firstTimeText = context.getString(
R.string.timetable_time_left,
context.getString(
R.string.timetable_minutes,
minutesToEndLesson.toString()
)
)
firstTimeRangeText = ""

View file

@ -28,10 +28,7 @@ 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
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.*
import java.time.LocalDate
import javax.inject.Inject
@ -178,8 +175,8 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
binding.dashboardErrorContainer.isVisible = show
}
override fun setErrorDetails(message: String) {
binding.dashboardErrorMessage.text = message
override fun setErrorDetails(error: Throwable) {
binding.dashboardErrorMessage.text = requireContext().resources.getErrorString(error)
}
override fun resetView() {

View file

@ -4,6 +4,7 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.databinding.SubitemDashboardGradesBinding
import io.github.wulkanowy.databinding.SubitemDashboardSmallGradeBinding
import io.github.wulkanowy.utils.getBackgroundColor
@ -12,7 +13,7 @@ class DashboardGradesAdapter : RecyclerView.Adapter<DashboardGradesAdapter.ViewH
var items = listOf<Pair<String, List<Grade>>>()
var gradeTheme = ""
lateinit var gradeColorTheme: GradeColorTheme
override fun getItemCount() = items.size
@ -36,7 +37,7 @@ class DashboardGradesAdapter : RecyclerView.Adapter<DashboardGradesAdapter.ViewH
with(subitemBinding.dashboardSmallGradeSubitemValue) {
text = it.entry
setBackgroundResource(it.getBackgroundColor(gradeTheme))
setBackgroundResource(it.getBackgroundColor(gradeColorTheme))
}
dashboardGradesSubitemGradeContainer.addView(subitemBinding.root)

View file

@ -6,6 +6,7 @@ import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.data.pojos.TimetableFull
import io.github.wulkanowy.data.db.entities.Homework as EntitiesHomework
@ -52,7 +53,7 @@ sealed class DashboardItem(val type: Type) {
data class Grades(
val subjectWithGrades: Map<String, List<Grade>>? = null,
val gradeTheme: String? = null,
val gradeTheme: GradeColorTheme? = null,
override val error: Throwable? = null,
override val isLoading: Boolean = false
) : DashboardItem(Type.GRADES) {

View file

@ -6,37 +6,17 @@ import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.repositories.AdminMessageRepository
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
import io.github.wulkanowy.data.repositories.ConferenceRepository
import io.github.wulkanowy.data.repositories.ExamRepository
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.HomeworkRepository
import io.github.wulkanowy.data.repositories.LuckyNumberRepository
import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.data.repositories.*
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.flowWithResourceIn
import io.github.wulkanowy.utils.nextOrSameSchoolDay
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import javax.inject.Inject
class DashboardPresenter @Inject constructor(
@ -552,7 +532,7 @@ class DashboardPresenter @Inject constructor(
student = student,
semester = semester,
forceRefresh = forceRefresh,
startDate = LocalDateTime.now()
startDate = Instant.now(),
)
}.onEach {
when (it.status) {
@ -582,7 +562,7 @@ class DashboardPresenter @Inject constructor(
}
private fun loadAdminMessage(student: Student, forceRefresh: Boolean) {
flowWithResourceIn { adminMessageRepository.getAdminMessages(student, forceRefresh) }
flowWithResourceIn { adminMessageRepository.getAdminMessages(student) }
.map {
val isDismissed = it.data?.id in preferencesRepository.dismissedAdminMessageIds
it.copy(data = it.data.takeUnless { isDismissed })
@ -716,7 +696,7 @@ class DashboardPresenter @Inject constructor(
itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
val isGeneralError =
filteredItems.none { it.error == null } && filteredItems.isNotEmpty() || isAccountItemError
val errorMessage = itemsLoadedList.map { it.error?.stackTraceToString() }.toString()
val firstError = itemsLoadedList.mapNotNull { it.error }.firstOrNull()
val filteredOriginalLoadedList =
dashboardItemLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT }
@ -726,7 +706,7 @@ class DashboardPresenter @Inject constructor(
filteredOriginalLoadedList.none { it.error == null } && filteredOriginalLoadedList.isNotEmpty() || wasAccountItemError
if (isGeneralError && isItemsLoaded) {
lastError = Exception(errorMessage)
lastError = requireNotNull(firstError)
view?.run {
showProgress(false)
@ -734,6 +714,7 @@ class DashboardPresenter @Inject constructor(
if ((forceRefresh && wasGeneralError) || !forceRefresh) {
showContent(false)
showErrorView(true)
setErrorDetails(lastError)
}
}
}

View file

@ -18,7 +18,7 @@ interface DashboardView : BaseView {
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun setErrorDetails(error: Throwable)
fun resetView()

Some files were not shown because too many files have changed in this diff Show more