1
0

Compare commits

...

83 Commits

Author SHA1 Message Date
232d8d38bd Merge branch 'release/0.17.0' 2020-04-05 18:42:36 +02:00
4833e1e130 Version 0.17.0 2020-04-05 18:35:00 +02:00
bb30cf2ce3 Revert "Add "System theme" option to widgets" (#753) 2020-04-05 18:32:57 +02:00
c9b35bed7e Bump chucker from 3.1.1 to 3.2.0 (#755) 2020-04-05 16:22:50 +00:00
999672fcc3 Bump threetenbp from 1.4.2 to 1.4.3 (#756) 2020-04-05 15:32:18 +00:00
6a0ce3a58d Add force sync feature in settings (#643) 2020-04-05 16:46:49 +02:00
3612326628 Add missing sdk initialization in lucky number repository (#752) 2020-04-04 20:59:44 +02:00
bf61dd1bad Add more details in email template (#751) 2020-04-04 20:57:50 +02:00
18c1153e12 Add mark as done feature in homework (#743) 2020-04-03 17:39:36 +02:00
651be69ad2 Add firebase messaging (#740) 2020-04-02 22:47:10 +02:00
394e3bb79c Add firebase messaging (#740) 2020-04-02 22:43:03 +02:00
502a98b70a Add message attachments (#734) 2020-04-02 20:27:53 +02:00
da357775ff Add better validation to login/email field (#741) 2020-04-02 20:27:14 +02:00
c0adeaadfd Add "System theme" option to widgets (#739) 2020-04-02 20:26:28 +02:00
358c87528a Update gradle to 6.2.2 (#750) 2020-04-02 00:34:24 +02:00
0cb4866f40 Bump appcompat from 1.2.0-alpha03 to 1.2.0-beta01 (#749) 2020-04-01 22:21:25 +00:00
6ec13c896d Bump about_libraries from 8.0.2 to 8.1.0 (#747) 2020-04-01 22:01:41 +00:00
a0587a8bce Bump fragment-ktx from 1.2.3 to 1.2.4 (#748) 2020-04-01 22:00:50 +00:00
184c9413f8 Bump firebase-core from 17.2.3 to 17.3.0 (#746) 2020-04-01 18:58:26 +00:00
6440820dc5 Bump gradle from 3.6.1 to 3.6.2 (#744) 2020-04-01 18:56:41 +00:00
d9322b0df4 Bump aboutlibraries-core from 7.1.0 to 8.0.0 (#731) 2020-03-29 18:55:00 +02:00
b9a19b60e4 Bump appcompat from 1.1.0 to 1.2.0-alpha03 to fix webview crash… (#737) 2020-03-29 14:38:39 +02:00
6f697eff47 Add points to notes (#738) 2020-03-29 14:26:56 +02:00
d9c8bb399b Change strings.xml form from male to impersonal (#736) 2020-03-28 19:51:22 +01:00
2137b6c225 Bump threetenabp from 1.2.2 to 1.2.3 (#735) 2020-03-28 10:56:07 +00:00
0320079d02 Bump chucker from 2.0.4 to 3.1.1 (#733) 2020-03-28 11:44:09 +01:00
95a833ea85 Bump room from 2.2.4 to 2.2.5 (#727) 2020-03-24 22:04:25 +00:00
a85a4fe7a0 Bump kotlin_version from 1.3.70 to 1.3.71 (#732) 2020-03-24 20:30:48 +00:00
f94b8c9be8 Bump threetenbp from 1.4.1 to 1.4.2 (#730) 2020-03-24 19:53:59 +00:00
5dfe9cdd4f Bump work_manager from 2.3.3 to 2.3.4 (#728) 2020-03-24 19:52:47 +00:00
9a83b43d57 Bump fragment-ktx from 1.2.2 to 1.2.3 (#729) 2020-03-24 19:52:33 +00:00
7d21babd38 Bump rxjava from 2.2.18 to 2.2.19 (#726) 2020-03-14 22:25:05 +00:00
f763a42323 Bump mockito-inline from 3.3.1 to 3.3.3 (#725) 2020-03-14 22:20:14 +00:00
478596c4e6 Fix marking message as read in hybrid and mobile api mode (#722) 2020-03-14 23:19:59 +01:00
37842a3603 Bump dagger from 2.26 to 2.27 (#724) 2020-03-14 22:19:47 +00:00
1775e2fe62 Bump mockito-android from 3.3.1 to 3.3.3 (#723) 2020-03-14 22:18:34 +00:00
68b26d5e2b Update gradle-publisher to 2.7.2 (#719) 2020-03-07 13:38:15 +01:00
6304395050 Bump swiperefreshlayout from 1.1.0-alpha03 to 1.1.0-beta01 (#718) 2020-03-05 10:59:07 +00:00
fa3c357665 Bump work_manager from 2.3.2 to 2.3.3 (#717) 2020-03-05 10:58:38 +00:00
83282aeab6 Merge tag '0.16.0' into develop
Version 0.16.0
2020-03-05 09:47:43 +01:00
5de2e9afbd Merge branch 'release/0.16.0' 2020-03-05 09:47:37 +01:00
cd99c6b2aa Version 0.16.0 2020-03-05 09:20:46 +01:00
42aacb755c Add some login help messages (#716) 2020-03-04 22:39:28 +01:00
a880b3a9db Bump kotlin_version from 1.3.61 to 1.3.70 (#715)
Bumps `kotlin_version` from 1.3.61 to 1.3.70.

Updates `kotlin-gradle-plugin` from 1.3.61 to 1.3.70

Updates `kotlin-stdlib-jdk8` from 1.3.61 to 1.3.70
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.3.61...v1.3.70)

Updates `kotlin-test` from 1.3.61 to 1.3.70
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.3.61...v1.3.70)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-04 16:39:13 +01:00
7837fae2aa Add German language (#712) 2020-03-03 22:20:57 +01:00
70e9f025bb Replace Creators with Contributors (#675) 2020-03-03 18:07:38 +01:00
ab1d9b358e Change asset name and contributor name (#714) 2020-03-03 18:06:37 +01:00
2634c270b1 Set missing styles to small lesson items (#707) 2020-03-02 22:45:56 +01:00
79bd2fccdf Replace Maybe with Single in Message DAO (#710) 2020-03-02 22:31:55 +01:00
87107ec474 Filter lucky numbers by school shortcut (#708) 2020-03-02 22:22:41 +01:00
8aaa066142 Update Ukrainian language (#711) 2020-03-02 21:31:02 +01:00
5b7d465064 Add Ukrainian language (#709) 2020-03-02 20:56:53 +01:00
75c94865e4 Sort the second group's lessons below the student's lessons (#706) 2020-02-29 23:07:47 +01:00
f294e3d57c Add separate host in login form to login to VULCAN adfslight (#704) 2020-02-29 10:30:20 +01:00
565114a2d2 Update gradle to 6.2.1 (#705) 2020-02-29 02:37:58 +01:00
be057dd63c Show list of charts in grade statistics (#689) 2020-02-29 02:19:48 +01:00
e61c2bced8 Add new notifications categories (#685) 2020-02-29 01:15:26 +01:00
3abfb9f819 Bump gradle from 3.6.0 to 3.6.1 (#702) 2020-02-28 23:19:38 +00:00
7c86fabd7b Bump firebase-core from 17.2.2 to 17.2.3 (#703) 2020-02-28 23:19:00 +00:00
59f2d4b0f3 Bump gradle from 3.5.3 to 3.6.0 (#698) 2020-02-27 21:22:37 +00:00
8571586b0c Bump room from 2.2.3 to 2.2.4 (#694) 2020-02-27 21:11:48 +00:00
e21de811e2 Bump mockito-android from 3.2.4 to 3.3.1 (#696) 2020-02-27 20:35:50 +00:00
7b502ce9a8 Bump mockito-inline from 3.2.4 to 3.3.1 (#693) 2020-02-27 20:35:28 +00:00
33447d2ada Bump fragment-ktx from 1.2.0 to 1.2.2 (#695) 2020-02-27 20:35:05 +00:00
7f6b2ec096 Bump coil from 0.9.4 to 0.9.5 (#699) 2020-02-27 20:33:25 +00:00
2c35117dfa Bump rxjava from 2.2.17 to 2.2.18 (#700) 2020-02-27 20:32:38 +00:00
5b2ca07506 Bump work_manager from 2.3.0 to 2.3.2 (#697) 2020-02-27 20:30:36 +00:00
18d6ec6961 Add account recover (#635) 2020-02-27 00:10:11 +01:00
96c1bb4c69 Automatically switch semesters without sync (#681) 2020-02-24 00:24:40 +01:00
00f5b9431e Add log viewer (#686) 2020-02-22 21:24:06 +01:00
9a87df7315 Respect user settings in timetable app widget (#687) 2020-02-22 20:42:02 +01:00
30e43501ac Refactor the resizing of the lucky number app widget (#682) 2020-02-14 10:04:53 +01:00
34738a4839 Show semester in appbar subtitle in grades view (#684) 2020-02-14 09:58:58 +01:00
1cc2080cb9 Bump material from 1.1.0-rc02 to 1.1.0 (#680) 2020-02-05 00:22:51 +00:00
0826e45a25 Bump coil from 0.9.2 to 0.9.4 (#679) 2020-02-05 00:22:12 +00:00
6925204019 Fix showing empty total summary and ordering of summary months (#678) 2020-02-05 00:53:07 +01:00
bdbf1fe304 Add dynamic nick/email in login (#676) 2020-02-05 00:42:49 +01:00
720a530a6c Disable signed in students in login (#677) 2020-02-04 09:43:24 +01:00
2f56f7e4a4 Bump dagger from 2.25.4 to 2.26 (#673) 2020-02-02 15:50:12 +00:00
c3a6842027 Add total attendance summary (#672) 2020-02-02 16:40:00 +01:00
731afbb00c Fix login more options button in dark theme (#674) 2020-02-02 16:38:14 +01:00
fb3853dc70 Fix timetable_show_whole_class_entries en translation (#668) 2020-01-30 00:13:34 +01:00
ec5503678a Merge tag '0.15.0' into develop
Version 0.15.0
2020-01-26 18:15:16 +01:00
238 changed files with 12290 additions and 1451 deletions

1
.gitignore vendored
View File

@ -63,6 +63,7 @@ captures/
.idea/dynamic.xml
.idea/uiDesigner.xml
.idea/runConfigurations.xml
.idea/discord.xml
# Keystore files
*.jks

View File

@ -1,9 +1,6 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="LINE_SEPARATOR" value="&#10;" />
<AndroidXmlCodeStyleSettings>
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>

View File

@ -4,7 +4,7 @@ jdk: oraclejdk8
env:
global:
- ANDROID_API_LEVEL=29
- ANDROID_BUILD_TOOLS_VERSION=29.0.2
- ANDROID_BUILD_TOOLS_VERSION=29.0.3
cache:
directories:
@ -14,7 +14,7 @@ cache:
branches:
only:
- develop
- 0.15.0
- 0.17.0
android:
licenses:
@ -49,9 +49,9 @@ script:
- ./gradlew dependencies --stacktrace --daemon
- fossa --no-ansi || true
#- ./gradlew lintPlayRelease -x fabricGenerateResourcesPlayRelease --stacktrace --daemon
- ./gradlew testPlayDebugUnitTest -x fabricGenerateResourcesPlay --stacktrace --daemon
- ./gradlew createFdroidDebugCoverageReport --stacktrace --daemon
- ./gradlew jacocoTestReport --stacktrace --daemon
- ./gradlew -Pcoverage testPlayDebugUnitTest -x fabricGenerateResourcesPlay --stacktrace --daemon
- ./gradlew -Pcoverage createFdroidDebugCoverageReport --stacktrace --daemon
- ./gradlew -Pcoverage jacocoTestReport --stacktrace --daemon
- if [ -z ${SONAR_HOST+x} ]; then echo "sonar scan skipped"; else
git fetch --unshallow;
./gradlew sonarqube -x test -x lint -x fabricGenerateResourcesPlayRelease -x fabricGenerateResourcesFdroidRelease -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_KEY -Dsonar.branch.name=${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} --stacktrace --daemon;

View File

@ -4,21 +4,22 @@ apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'io.fabric'
apply plugin: 'com.github.triplet.play'
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
apply from: 'jacoco.gradle'
apply from: 'sonarqube.gradle'
apply from: 'hooks.gradle'
android {
compileSdkVersion 29
buildToolsVersion '29.0.2'
buildToolsVersion '29.0.3'
defaultConfig {
applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 16
minSdkVersion 17
targetSdkVersion 29
versionCode 52
versionName "0.15.0"
versionCode 54
versionName "0.17.0"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
@ -61,7 +62,7 @@ android {
buildConfigField "boolean", "CRASHLYTICS_ENABLED", project.hasProperty("enableCrashlytics") ? "true" : "false"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
testCoverageEnabled = true
testCoverageEnabled = project.hasProperty('coverage')
ext.enableCrashlytics = project.hasProperty("enableCrashlytics")
}
}
@ -96,6 +97,10 @@ android {
exclude 'META-INF/library_release.kotlin_module'
exclude 'META-INF/library-core_release.kotlin_module'
}
aboutLibraries {
configPath = "app/src/main/res/raw"
}
}
androidExtensions {
@ -110,10 +115,11 @@ play {
}
ext {
work_manager = "2.3.0"
room = "2.2.3"
dagger = "2.25.4"
chucker = "2.0.4"
work_manager = "2.3.4"
room = "2.2.5"
dagger = "2.27"
// don't update https://github.com/ChuckerTeam/chucker/issues/242
chucker = "3.2.0"
mockk = "1.9.2"
}
@ -122,24 +128,24 @@ configurations.all {
}
dependencies {
implementation "io.github.wulkanowy:sdk:0.15.0"
implementation "io.github.wulkanowy:sdk:0.17.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.core:core-ktx:1.2.0-rc01"
implementation "androidx.core:core-ktx:1.2.0"
implementation "androidx.activity:activity-ktx:1.1.0"
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.appcompat:appcompat:1.2.0-beta01"
implementation "androidx.appcompat:appcompat-resources:1.1.0"
implementation "androidx.fragment:fragment-ktx:1.2.0"
implementation "androidx.fragment:fragment-ktx:1.2.4"
implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.multidex:multidex:2.0.1"
implementation "androidx.preference:preference-ktx:1.1.0"
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.viewpager:viewpager:1.0.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-beta01"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.1.0-rc02"
implementation "com.google.android.material:material:1.1.0"
implementation "com.github.wulkanowy:material-chips-input:2.0.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation "me.zhanghai.android.materialprogressbar:library:1.6.1"
@ -148,6 +154,8 @@ dependencies {
implementation "androidx.work:work-rxjava2:$work_manager"
implementation "androidx.work:work-gcm:$work_manager"
implementation 'com.github.PaulinaSadowska:RxWorkManagerObservers:1.0.0'
implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-rxjava2:$room"
implementation "androidx.room:room-ktx:$room"
@ -163,33 +171,37 @@ dependencies {
implementation "eu.davidea:flexible-adapter-ui:1.0.0"
implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
implementation "com.ncapdevi:frag-nav:3.3.0"
implementation "com.github.YarikSOffice:lingver:1.1.0"
implementation "com.github.YarikSOffice:lingver:1.2.1"
implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.6"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "io.reactivex.rxjava2:rxjava:2.2.17"
implementation "io.reactivex.rxjava2:rxjava:2.2.19"
implementation "com.google.code.gson:gson:2.8.6"
implementation "com.jakewharton.threetenabp:threetenabp:1.2.2"
implementation "com.jakewharton.threetenabp:threetenabp:1.2.3"
implementation "com.jakewharton.timber:timber:4.7.1"
implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation "com.mikepenz:aboutlibraries-core:7.1.0"
implementation "fr.bipi.treessence:treessence:0.3.2"
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
implementation "io.coil-kt:coil:0.9.5"
implementation("io.coil-kt:coil:0.9.2")
playImplementation "com.google.firebase:firebase-core:17.2.2"
playImplementation 'com.google.firebase:firebase-analytics:17.3.0'
playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.4'
playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.4"
playImplementation "com.google.firebase:firebase-messaging:20.1.0"
playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1"
playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
releaseImplementation "fr.o80.chucker:library-no-op:$chucker"
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
debugImplementation "fr.o80.chucker:library:$chucker"
debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
debugImplementation "com.amitshekhar.android:debug-db:1.0.6"
testImplementation "junit:junit:4.13"
testImplementation "io.mockk:mockk:$mockk"
testImplementation "org.threeten:threetenbp:1.4.1"
testImplementation "org.mockito:mockito-inline:3.2.4"
testImplementation "org.threeten:threetenbp:1.4.3"
testImplementation "org.mockito:mockito-inline:3.3.3"
androidTestImplementation "androidx.test:core:1.2.0"
androidTestImplementation "androidx.test:runner:1.2.0"
@ -197,7 +209,7 @@ dependencies {
androidTestImplementation "io.mockk:mockk-android:$mockk"
androidTestImplementation "androidx.room:room-testing:$room"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
androidTestImplementation "org.mockito:mockito-android:3.2.4"
androidTestImplementation "org.mockito:mockito-android:3.3.3"
}
apply plugin: 'com.google.gms.google-services'

View File

@ -43,3 +43,10 @@
#Config for Material Components
-keep class com.google.android.material.tabs.** { *; }
#Config for About Libraries
-keep class .R
-keep class **.R$* {
<fields>;
}

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

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@ import io.github.wulkanowy.data.db.entities.Semester
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.of
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@ -104,45 +103,45 @@ class Migration13Test : AbstractMigrationTest() {
val db = helper.runMigrationsAndValidate(dbName, 13, true, Migration13())
val semesters1 = getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 1 AND class_id = 5")
assertTrue { semesters1.single { it.isCurrent }.isCurrent }
assertTrue { semesters1.single { it.second }.second }
semesters1[0].run {
assertFalse(isCurrent)
assertEquals(1, semesterId)
assertEquals(1, diaryId)
assertFalse(second)
assertEquals(1, first.semesterId)
assertEquals(1, first.diaryId)
}
semesters1[2].run {
assertFalse(isCurrent)
assertEquals(3, semesterId)
assertEquals(2, diaryId)
assertFalse(second)
assertEquals(3, first.semesterId)
assertEquals(2, first.diaryId)
}
semesters1[3].run {
assertTrue(isCurrent)
assertEquals(4, semesterId)
assertEquals(2, diaryId)
assertTrue(second)
assertEquals(4, first.semesterId)
assertEquals(2, first.diaryId)
}
getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let {
assertTrue { it.single { it.isCurrent }.isCurrent }
assertEquals(1970, it[0].schoolYear)
assertEquals(of(1970, 1, 1), it[0].end)
assertEquals(of(1970, 1, 1), it[0].start)
assertFalse(it[0].isCurrent)
assertFalse(it[1].isCurrent)
assertFalse(it[2].isCurrent)
assertTrue(it[3].isCurrent)
assertTrue { it.single { it.second }.second }
assertEquals(1970, it[0].first.schoolYear)
assertEquals(of(1970, 1, 1), it[0].first.end)
assertEquals(of(1970, 1, 1), it[0].first.start)
assertFalse(it[0].second)
assertFalse(it[1].second)
assertFalse(it[2].second)
assertTrue(it[3].second)
}
getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let {
assertTrue { it.single { it.isCurrent }.isCurrent }
assertFalse(it[0].isCurrent)
assertFalse(it[1].isCurrent)
assertFalse(it[2].isCurrent)
assertTrue(it[3].isCurrent)
assertTrue { it.single { it.second }.second }
assertFalse(it[0].second)
assertFalse(it[1].second)
assertFalse(it[2].second)
assertTrue(it[3].second)
}
}
private fun getSemesters(db: SupportSQLiteDatabase, query: String): List<Semester> {
val semesters = mutableListOf<Semester>()
private fun getSemesters(db: SupportSQLiteDatabase, query: String): List<Pair<Semester, Boolean>> {
val semesters = mutableListOf<Pair<Semester, Boolean>>()
val cursor = db.query(query)
if (cursor.moveToFirst()) {
@ -153,13 +152,12 @@ class Migration13Test : AbstractMigrationTest() {
diaryName = cursor.getString(3),
semesterId = cursor.getInt(4),
semesterName = cursor.getInt(5),
isCurrent = cursor.getInt(6) == 1,
classId = cursor.getInt(7),
unitId = cursor.getInt(8),
schoolYear = cursor.getInt(9),
start = Converters().timestampToDate(cursor.getLong(10))!!,
end = Converters().timestampToDate(cursor.getLong(11))!!
))
) to (cursor.getInt(6) == 1))
} while (cursor.moveToNext())
}
return semesters.toList()

View File

@ -10,8 +10,8 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.of
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
@ -35,19 +35,19 @@ class AttendanceLocalTest {
@Test
fun saveAndReadTest() {
attendanceLocal.saveAttendance(listOf(
Attendance(1, 2, 3, LocalDate.of(2018, 9, 10), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name),
Attendance(1, 2, 3, LocalDate.of(2018, 9, 14), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.WAITING.name),
Attendance(1, 2, 3, LocalDate.of(2018, 9, 17), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name)
Attendance(1, 2, 3, of(2018, 9, 10), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name),
Attendance(1, 2, 3, of(2018, 9, 14), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.WAITING.name),
Attendance(1, 2, 3, of(2018, 9, 17), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name)
))
val attendance = attendanceLocal
.getAttendance(Semester(1, 2, "", 1, 3, 2019, true, now(), now(), 1, 1),
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 14)
)
.blockingGet()
.getAttendance(Semester(1, 2, "", 1, 3, 2019, now(), now(), 1, 1),
of(2018, 9, 10),
of(2018, 9, 14)
)
.blockingGet()
assertEquals(2, attendance.size)
assertEquals(attendance[0].date, LocalDate.of(2018, 9, 10))
assertEquals(attendance[1].date, LocalDate.of(2018, 9, 14))
assertEquals(attendance[0].date, of(2018, 9, 10))
assertEquals(attendance[1].date, of(2018, 9, 14))
}
}

View File

@ -11,6 +11,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.of
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
@ -35,20 +37,20 @@ class CompletedLessonsLocalTest {
@Test
fun saveAndReadTest() {
completedLessonsLocal.saveCompletedLessons(listOf(
getCompletedLesson(LocalDate.of(2018, 9, 10), 1),
getCompletedLesson(LocalDate.of(2018, 9, 14), 2),
getCompletedLesson(LocalDate.of(2018, 9, 17), 3)
getCompletedLesson(of(2018, 9, 10), 1),
getCompletedLesson(of(2018, 9, 14), 2),
getCompletedLesson(of(2018, 9, 17), 3)
))
val completed = completedLessonsLocal
.getCompletedLessons(Semester(1, 2, "", 1, 3, 2019, true, LocalDate.now(), LocalDate.now(), 1, 1),
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 14)
.getCompletedLessons(Semester(1, 2, "", 1, 3, 2019, now(), now(), 1, 1),
of(2018, 9, 10),
of(2018, 9, 14)
)
.blockingGet()
assertEquals(2, completed.size)
assertEquals(completed[0].date, LocalDate.of(2018, 9, 10))
assertEquals(completed[1].date, LocalDate.of(2018, 9, 14))
assertEquals(completed[0].date, of(2018, 9, 10))
assertEquals(completed[1].date, of(2018, 9, 14))
}
private fun getCompletedLesson(date: LocalDate, number: Int): CompletedLesson {

View File

@ -10,7 +10,8 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.of
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
@ -34,19 +35,19 @@ class ExamLocalTest {
@Test
fun saveAndReadTest() {
examLocal.saveExams(listOf(
Exam(1, 2, LocalDate.of(2018, 9, 10), LocalDate.now(), "", "", "", "", "", ""),
Exam(1, 2, LocalDate.of(2018, 9, 14), LocalDate.now(), "", "", "", "", "", ""),
Exam(1, 2, LocalDate.of(2018, 9, 17), LocalDate.now(), "", "", "", "", "", "")
Exam(1, 2, of(2018, 9, 10), now(), "", "", "", "", "", ""),
Exam(1, 2, of(2018, 9, 14), now(), "", "", "", "", "", ""),
Exam(1, 2, of(2018, 9, 17), now(), "", "", "", "", "", "")
))
val exams = examLocal
.getExams(Semester(1, 2, "", 1, 3, 2019, true, LocalDate.now(), LocalDate.now(), 1, 1),
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 14)
)
.blockingGet()
.getExams(Semester(1, 2, "", 1, 3, 2019, now(), now(), 1, 1),
of(2018, 9, 10),
of(2018, 9, 14)
)
.blockingGet()
assertEquals(2, exams.size)
assertEquals(exams[0].date, LocalDate.of(2018, 9, 10))
assertEquals(exams[1].date, LocalDate.of(2018, 9, 14))
assertEquals(exams[0].date, of(2018, 9, 10))
assertEquals(exams[1].date, of(2018, 9, 14))
}
}

View File

@ -40,7 +40,7 @@ class GradeLocalTest {
createGradeLocal(3, 5.0, LocalDate.of(2019, 2, 28), "", 2)
))
val semester = Semester(1, 2, "", 2019, 2, 1, true, now(), now(), 1, 1)
val semester = Semester(1, 2, "", 2019, 2, 1, now(), now(), 1, 1)
val grades = gradeLocal
.getGrades(semester)

View File

@ -6,7 +6,6 @@ import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.SdkHelper
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
@ -15,9 +14,6 @@ import io.github.wulkanowy.sdk.Sdk
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.SpyK
import io.mockk.just
import io.mockk.runs
import io.reactivex.Single
import org.junit.After
import org.junit.Before

View File

@ -11,7 +11,7 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
@ -40,10 +40,7 @@ class GradeStatisticsLocalTest {
getGradeStatistics("Fizyka", 1, 2)
))
val stats = gradeStatisticsLocal.getGradesStatistics(
Semester(2, 2, "", 2019, 1, 2, true, LocalDate.now(), LocalDate.now(), 1, 1), false,
"Matematyka"
).blockingGet()
val stats = gradeStatisticsLocal.getGradesStatistics(getSemester(), false, "Matematyka").blockingGet()
assertEquals(1, stats.size)
assertEquals(stats[0].subject, "Matematyka")
}
@ -56,12 +53,11 @@ class GradeStatisticsLocalTest {
getGradeStatistics("Fizyka", 1, 2)
))
val stats = gradeStatisticsLocal.getGradesStatistics(
Semester(2, 2, "", 2019, 1, 2, true, LocalDate.now(), LocalDate.now(), 1, 1), false,
"Wszystkie"
).blockingGet()
assertEquals(1, stats.size)
val stats = gradeStatisticsLocal.getGradesStatistics(getSemester(), false, "Wszystkie").blockingGet()
assertEquals(3, stats.size)
assertEquals(stats[0].subject, "Wszystkie")
assertEquals(stats[1].subject, "Matematyka")
assertEquals(stats[2].subject, "Chemia")
}
@Test
@ -72,11 +68,8 @@ class GradeStatisticsLocalTest {
getGradePointsStatistics("Fizyka", 1, 2)
))
val stats = gradeStatisticsLocal.getGradesPointsStatistics(
Semester(2, 2, "", 2019, 1, 2, true, LocalDate.now(), LocalDate.now(), 1, 1),
"Matematyka"
).blockingGet()
with(stats) {
val stats = gradeStatisticsLocal.getGradesPointsStatistics(getSemester(), "Matematyka").blockingGet()
with(stats[0]) {
assertEquals(subject, "Matematyka")
assertEquals(others, 5.0)
assertEquals(student, 5.0)
@ -87,10 +80,7 @@ class GradeStatisticsLocalTest {
fun saveAndRead_subjectEmpty() {
gradeStatisticsLocal.saveGradesPointsStatistics(listOf())
val stats = gradeStatisticsLocal.getGradesPointsStatistics(
Semester(2, 2, "", 2019, 1, 2, true, LocalDate.now(), LocalDate.now(), 1, 1),
"Matematyka"
).blockingGet()
val stats = gradeStatisticsLocal.getGradesPointsStatistics(getSemester(), "Matematyka").blockingGet()
assertEquals(null, stats)
}
@ -98,13 +88,14 @@ class GradeStatisticsLocalTest {
fun saveAndRead_allEmpty() {
gradeStatisticsLocal.saveGradesPointsStatistics(listOf())
val stats = gradeStatisticsLocal.getGradesPointsStatistics(
Semester(2, 2, "", 2019, 1, 2, true, LocalDate.now(), LocalDate.now(), 1, 1),
"Wszystkie"
).blockingGet()
val stats = gradeStatisticsLocal.getGradesPointsStatistics(getSemester(), "Wszystkie").blockingGet()
assertEquals(null, stats)
}
private fun getSemester(): Semester {
return Semester(2, 2, "", 2019, 1, 2, now(), now(), 1, 1)
}
private fun getGradeStatistics(subject: String, studentId: Int, semesterId: Int): GradeStatistics {
return GradeStatistics(studentId, semesterId, subject, 5, 5, false)
}

View File

@ -6,11 +6,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime.now
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
@ -36,7 +38,7 @@ class LuckyNumberLocalTest {
fun saveAndReadTest() {
luckyNumberLocal.saveLuckyNumber(LuckyNumber(1, LocalDate.of(2019, 1, 20), 14))
val luckyNumber = luckyNumberLocal.getLuckyNumber(Semester(1, 1, "", 1, 3, 2019, true, LocalDate.now(), LocalDate.now(), 1, 1),
val luckyNumber = luckyNumberLocal.getLuckyNumber(Student("", "", "", "", "", "", false, "", "", "", 1, 1, "", "", "", "", "", 1, false, now()),
LocalDate.of(2019, 1, 20)
).blockingGet()

View File

@ -42,7 +42,7 @@ class RecipientLocalTest {
))
val recipients = recipientLocal.getRecipients(
Student("fakelog.cf", "AUTO", "", "", "", "", false, "", "", "", 1, 0, "", "", "", "", 1, true, LocalDateTime.now()),
Student("fakelog.cf", "AUTO", "", "", "", "", false, "", "", "", 1, 0, "", "", "", "", "", 1, true, LocalDateTime.now()),
2,
ReportingUnit(1, 4, "", 0, "", emptyList())
).blockingGet()

View File

@ -39,7 +39,7 @@ class StudentLocalTest {
@Test
fun saveAndReadTest() {
studentLocal.saveStudents(listOf(Student(email = "test", password = "test123", schoolSymbol = "23", scrapperBaseUrl = "fakelog.cf", loginType = "AUTO", isCurrent = true, studentName = "", schoolName = "", studentId = 0, classId = 1, symbol = "", registrationDate = now(), className = "", loginMode = "API", certificateKey = "", privateKey = "", mobileBaseUrl = "", userLoginId = 0, isParent = false)))
studentLocal.saveStudents(listOf(Student(email = "test", password = "test123", schoolSymbol = "23", scrapperBaseUrl = "fakelog.cf", loginType = "AUTO", isCurrent = true, studentName = "", schoolShortName = "", schoolName = "", studentId = 0, classId = 1, symbol = "", registrationDate = now(), className = "", loginMode = "API", certificateKey = "", privateKey = "", mobileBaseUrl = "", userLoginId = 0, isParent = false)))
.blockingGet()
val student = studentLocal.getCurrentStudent(true).blockingGet()

View File

@ -21,7 +21,7 @@ fun createTimetableLocal(start: LocalDateTime, number: Int, room: String = "", s
teacher = teacher,
teacherOld = "",
info = "",
studentPlan = true,
isStudentPlan = true,
changes = changes,
canceled = false
)

View File

@ -41,7 +41,7 @@ class TimetableLocalTest {
))
val exams = timetableDb.getTimetable(
Semester(1, 2, "", 1, 1, 2019, true, LocalDate.now(), LocalDate.now(), 1, 1),
Semester(1, 2, "", 1, 1, 2019, LocalDate.now(), LocalDate.now(), 1, 1),
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 14)
).blockingGet()

View File

@ -0,0 +1,4 @@
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<bool name="pref_default_notification_debug">true</bool>
</resources>

View File

@ -5,13 +5,12 @@ package io.github.wulkanowy.utils
import android.content.Context
import timber.log.Timber
fun initCrashlytics(context: Context, appInfo: AppInfo) {
// do nothing
fun initCrashlytics(context: Context, appInfo: AppInfo) {}
open class TimberTreeNoOp : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {}
}
class CrashlyticsTree : Timber.Tree() {
class CrashlyticsTree : TimberTreeNoOp()
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
// do nothing
}
}
class CrashlyticsExceptionTree : TimberTreeNoOp()

View File

@ -22,7 +22,8 @@
<activity
android:name=".ui.modules.splash.SplashActivity"
android:screenOrientation="portrait"
android:theme="@style/WulkanowyTheme.SplashScreen">
android:theme="@style/WulkanowyTheme.SplashScreen"
tools:ignore="LockedOrientationActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -43,8 +44,8 @@
android:name=".ui.modules.message.send.SendMessageActivity"
android:configChanges="orientation|screenSize"
android:label="@string/send_message_title"
android:windowSoftInputMode="adjustResize"
android:theme="@style/WulkanowyTheme.NoActionBar" />
android:theme="@style/WulkanowyTheme.NoActionBar"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
android:excludeFromRecents="true"
@ -96,11 +97,29 @@
android:exported="false"
tools:node="remove" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<meta-data
android:name="io.fabric.ApiKey"
android:value="${fabric_api_key}" />
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="${crashlytics_enabled}" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_push" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="push_channel" />
</application>
</manifest>

View File

@ -20,7 +20,7 @@
"githubUsername": "doteq"
},
{
"displayName": "Pavuloff",
"displayName": "Paweł Krzyś",
"githubUsername": "pavuloff"
},
{
@ -30,5 +30,9 @@
{
"displayName": "Dinolek",
"githubUsername": "Dinolek"
},
{
"displayName": "Mateusz Idziejczak",
"githubUsername": "PanTajemnic"
}
]

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy
import android.content.Context
import android.util.Log.DEBUG
import android.util.Log.INFO
import android.util.Log.VERBOSE
import androidx.multidex.MultiDex
@ -11,11 +12,13 @@ import dagger.android.AndroidInjector
import dagger.android.support.DaggerApplication
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.utils.Log
import fr.bipi.tressence.file.FileLoggerTree
import io.github.wulkanowy.di.DaggerAppComponent
import io.github.wulkanowy.services.sync.SyncWorkerFactory
import io.github.wulkanowy.ui.base.ThemeManager
import io.github.wulkanowy.utils.ActivityLifecycleLogger
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.CrashlyticsExceptionTree
import io.github.wulkanowy.utils.CrashlyticsTree
import io.github.wulkanowy.utils.DebugLogTree
import io.github.wulkanowy.utils.initCrashlytics
@ -54,9 +57,17 @@ class WulkanowyApp : DaggerApplication(), Configuration.Provider {
private fun initLogging() {
if (appInfo.isDebug) {
Timber.plant(DebugLogTree())
FlexibleAdapter.enableLogs(Log.Level.DEBUG)
Timber.plant(DebugLogTree())
Timber.plant(FileLoggerTree.Builder()
.withFileName("wulkanowy.%g.log")
.withDirName(applicationContext.filesDir.absolutePath)
.withFileLimit(10)
.withMinPriority(DEBUG)
.build()
)
} else {
Timber.plant(CrashlyticsExceptionTree())
Timber.plant(CrashlyticsTree())
}
registerActivityLifecycleCallbacks(ActivityLifecycleLogger())

View File

@ -7,9 +7,9 @@ import android.content.res.Resources
import androidx.preference.PreferenceManager
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.WalledGardenInternetObservingStrategy
import com.readystatesoftware.chuck.api.ChuckCollector
import com.readystatesoftware.chuck.api.ChuckInterceptor
import com.readystatesoftware.chuck.api.RetentionManager
import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.chuckerteam.chucker.api.RetentionManager
import dagger.Module
import dagger.Provides
import io.github.wulkanowy.data.db.AppDatabase
@ -32,23 +32,25 @@ internal class RepositoryModule {
@Singleton
@Provides
fun provideSdk(chuckCollector: ChuckCollector, context: Context): Sdk {
fun provideSdk(chuckerCollector: ChuckerCollector, context: Context): Sdk {
return Sdk().apply {
androidVersion = android.os.Build.VERSION.RELEASE
buildTag = android.os.Build.MODEL
setSimpleHttpLogger { Timber.d(it) }
// for debug only
addInterceptor(ChuckInterceptor(context, chuckCollector).maxContentLength(250000L), true)
addInterceptor(ChuckerInterceptor(context, chuckerCollector), true)
}
}
@Singleton
@Provides
fun provideChuckCollector(context: Context, prefRepository: PreferencesRepository): ChuckCollector {
return ChuckCollector(context)
.showNotification(prefRepository.isDebugNotificationEnable)
.retentionManager(RetentionManager(context, ChuckCollector.Period.ONE_HOUR))
fun provideChuckerCollector(context: Context, prefRepository: PreferencesRepository): ChuckerCollector {
return ChuckerCollector(
context = context,
showNotification = prefRepository.isDebugNotificationEnable,
retentionPeriod = RetentionManager.Period.ONE_HOUR
)
}
@Singleton
@ -95,6 +97,10 @@ internal class RepositoryModule {
@Provides
fun provideMessagesDao(database: AppDatabase) = database.messagesDao
@Singleton
@Provides
fun provideMessageAttachmentsDao(database: AppDatabase) = database.messageAttachmentDao
@Singleton
@Provides
fun provideExamDao(database: AppDatabase) = database.examsDao

View File

@ -17,6 +17,7 @@ import io.github.wulkanowy.data.db.dao.GradeStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.db.dao.HomeworkDao
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
import io.github.wulkanowy.data.db.dao.NoteDao
@ -39,6 +40,7 @@ import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageAttachment
import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.db.entities.Recipient
@ -62,6 +64,10 @@ 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.Migration3
import io.github.wulkanowy.data.db.migrations.Migration4
import io.github.wulkanowy.data.db.migrations.Migration5
@ -85,6 +91,7 @@ import javax.inject.Singleton
GradeStatistics::class,
GradePointsStatistics::class,
Message::class,
MessageAttachment::class,
Note::class,
Homework::class,
Subject::class,
@ -103,7 +110,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 21
const val VERSION_SCHEMA = 25
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
return arrayOf(
@ -126,7 +133,11 @@ abstract class AppDatabase : RoomDatabase() {
Migration18(),
Migration19(sharedPrefProvider),
Migration20(),
Migration21()
Migration21(),
Migration22(),
Migration23(),
Migration24(),
Migration25()
)
}
@ -162,6 +173,8 @@ abstract class AppDatabase : RoomDatabase() {
abstract val messagesDao: MessagesDao
abstract val messageAttachmentDao: MessageAttachmentDao
abstract val noteDao: NoteDao
abstract val homeworkDao: HomeworkDao

View File

@ -48,4 +48,14 @@ class Converters {
fun gsonToIntList(value: String): List<Int> {
return Gson().fromJson(value, object : TypeToken<List<Int>>() {}.type)
}
@TypeConverter
fun stringPairListToGson(list: List<Pair<String, String>>): String {
return Gson().toJson(list)
}
@TypeConverter
fun gsonToStringPairList(value: String): List<Pair<String, String>> {
return Gson().fromJson(value, object : TypeToken<List<Pair<String, String>>>() {}.type)
}
}

View File

@ -11,7 +11,7 @@ import javax.inject.Singleton
interface GradePointsStatisticsDao : BaseDao<GradePointsStatistics> {
@Query("SELECT * FROM GradesPointsStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND subject = :subjectName")
fun loadSubject(semesterId: Int, studentId: Int, subjectName: String): Maybe<GradePointsStatistics>
fun loadSubject(semesterId: Int, studentId: Int, subjectName: String): Maybe<List<GradePointsStatistics>>
@Query("SELECT * FROM GradesPointsStatistics WHERE student_id = :studentId AND semester_id = :semesterId")
fun loadAll(semesterId: Int, studentId: Int): Maybe<List<GradePointsStatistics>>

View File

@ -0,0 +1,13 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import io.github.wulkanowy.data.db.entities.MessageAttachment
@Dao
interface MessageAttachmentDao : BaseDao<MessageAttachment> {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAttachments(items: List<MessageAttachment>): List<Long>
}

View File

@ -2,18 +2,22 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Transaction
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
import io.reactivex.Maybe
import io.reactivex.Single
@Dao
interface MessagesDao : BaseDao<Message> {
@Transaction
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND message_id = :messageId")
fun loadMessageWithAttachment(studentId: Int, messageId: Int): Single<MessageWithAttachment>
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder AND removed = 0 ORDER BY date DESC")
fun loadAll(studentId: Int, folder: Int): Maybe<List<Message>>
@Query("SELECT * FROM Messages WHERE id = :id")
fun load(id: Long): Maybe<Message>
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND removed = 1 ORDER BY date DESC")
fun loadDeleted(studentId: Int): Maybe<List<Message>>
}

View File

@ -27,10 +27,14 @@ data class Homework(
val teacher: String,
@ColumnInfo(name = "teacher_symbol")
val teacherSymbol: String
val teacherSymbol: String,
val attachments: List<Pair<String, String>>
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
@ColumnInfo(name = "is_done")
var isDone: Boolean = false
}

View File

@ -44,7 +44,10 @@ data class Message(
@ColumnInfo(name = "read_by")
val readBy: Int,
val removed: Boolean
val removed: Boolean,
@ColumnInfo(name = "has_attachments")
val hasAttachments: Boolean
) : Serializable {
@PrimaryKey(autoGenerate = true)

View File

@ -0,0 +1,26 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
@Entity(tableName = "MessageAttachments")
data class MessageAttachment(
@PrimaryKey
@ColumnInfo(name = "real_id")
val realId: Int,
@ColumnInfo(name = "message_id")
val messageId: Int,
@ColumnInfo(name = "one_drive_id")
val oneDriveId: String,
@ColumnInfo(name = "url")
val url: String,
@ColumnInfo(name = "filename")
val filename: String
) : Serializable

View File

@ -0,0 +1,12 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.Embedded
import androidx.room.Relation
data class MessageWithAttachment(
@Embedded
val message: Message,
@Relation(parentColumn = "message_id", entityColumn = "message_id")
val attachments: List<MessageAttachment>
)

View File

@ -16,8 +16,19 @@ data class Note(
val teacher: String,
@ColumnInfo(name = "teacher_symbol")
val teacherSymbol: String,
val category: String,
@ColumnInfo(name = "category_type")
val categoryType: Int,
@ColumnInfo(name = "is_points_show")
val isPointsShow: Boolean,
val points: Int,
val content: String
) : Serializable {

View File

@ -27,9 +27,6 @@ data class Semester(
@ColumnInfo(name = "semester_name")
val semesterName: Int,
@ColumnInfo(name = "is_current")
val isCurrent: Boolean,
val start: LocalDate,
val end: LocalDate,
@ -43,4 +40,8 @@ data class Semester(
@PrimaryKey(autoGenerate = true)
var id: Long = 0
@ColumnInfo(name = "is_current")
var current: Boolean = false
}

View File

@ -49,6 +49,9 @@ data class Student(
@ColumnInfo(name = "school_id")
val schoolSymbol: String,
@ColumnInfo(name ="school_short")
val schoolShortName: String,
@ColumnInfo(name = "school_name")
val schoolName: String,

View File

@ -41,7 +41,7 @@ data class Timetable(
val info: String,
@ColumnInfo(name = "student_plan")
val studentPlan: Boolean,
val isStudentPlan: Boolean,
val changes: Boolean,

View File

@ -0,0 +1,11 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration22 : Migration(21, 22) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Students ADD COLUMN school_short TEXT NOT NULL DEFAULT ''")
}
}

View File

@ -0,0 +1,14 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration23 : Migration(22, 23) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Notes ADD COLUMN teacher_symbol TEXT NOT NULL DEFAULT ''")
database.execSQL("ALTER TABLE Notes ADD COLUMN category_type INTEGER NOT NULL DEFAULT 0")
database.execSQL("ALTER TABLE Notes ADD COLUMN is_points_show INTEGER NOT NULL DEFAULT 0")
database.execSQL("ALTER TABLE Notes ADD COLUMN points INTEGER NOT NULL DEFAULT 0")
}
}

View File

@ -0,0 +1,21 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration24 : Migration(23, 24) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Messages ADD COLUMN has_attachments INTEGER NOT NULL DEFAULT 0")
database.execSQL("""
CREATE TABLE IF NOT EXISTS MessageAttachments (
real_id INTEGER NOT NULL,
message_id INTEGER NOT NULL,
one_drive_id TEXT NOT NULL,
url TEXT NOT NULL,
filename TEXT NOT NULL,
PRIMARY KEY(real_id)
)
""")
}
}

View File

@ -0,0 +1,12 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration25 : Migration(24, 25) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Homework ADD COLUMN is_done INTEGER NOT NULL DEFAULT 0")
database.execSQL("ALTER TABLE Homework ADD COLUMN attachments TEXT NOT NULL DEFAULT \"[]\"")
}
}

View File

@ -0,0 +1,14 @@
package io.github.wulkanowy.data.pojos
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeStatistics
import io.github.wulkanowy.ui.modules.grade.statistics.ViewType
data class GradeStatisticsItem(
val type: ViewType,
val partial: List<GradeStatistics>,
val points: GradePointsStatistics?
)

View File

@ -12,7 +12,7 @@ class AppCreatorRepository @Inject constructor(private val assets: AssetManager)
fun getAppCreators(): Single<List<AppCreator>> {
return Single.fromCallable<List<AppCreator>> {
Gson().fromJson(
assets.open("creators.json").bufferedReader().use { it.readText() },
assets.open("contributors.json").bufferedReader().use { it.readText() },
Array<AppCreator>::class.java
).toList()
}

View File

@ -5,7 +5,6 @@ import io.github.wulkanowy.data.db.dao.GradeStatisticsDao
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeStatistics
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.roundToDecimalPlaces
import io.reactivex.Maybe
import javax.inject.Inject
import javax.inject.Singleton
@ -30,23 +29,17 @@ class GradeStatisticsLocal @Inject constructor(
list.groupBy { it.grade }.map {
GradeStatistics(semester.studentId, semester.semesterId, subjectName, it.key,
it.value.fold(0) { acc, e -> acc + e.amount }, false)
}
} + list
}
else -> gradeStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName, isSemester)
}.filter { it.isNotEmpty() }
}
fun getGradesPointsStatistics(semester: Semester, subjectName: String): Maybe<GradePointsStatistics> {
fun getGradesPointsStatistics(semester: Semester, subjectName: String): Maybe<List<GradePointsStatistics>> {
return when (subjectName) {
"Wszystkie" -> gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId).flatMap { list ->
if (list.isEmpty()) return@flatMap Maybe.empty<GradePointsStatistics>()
Maybe.just(GradePointsStatistics(semester.studentId, semester.semesterId, subjectName,
(list.fold(.0) { acc, e -> acc + e.others } / list.size).roundToDecimalPlaces(2),
(list.fold(.0) { acc, e -> acc + e.student } / list.size).roundToDecimalPlaces(2)
))
}
"Wszystkie" -> gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId)
else -> gradePointsStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName)
}
}.filter { it.isNotEmpty() }
}
fun saveGradesStatistics(gradesStatistics: List<GradeStatistics>) {

View File

@ -5,8 +5,9 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeStatistics
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.pojos.GradeStatisticsItem
import io.github.wulkanowy.ui.modules.grade.statistics.ViewType
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Maybe
import io.reactivex.Single
import java.net.UnknownHostException
import javax.inject.Inject
@ -19,8 +20,8 @@ class GradeStatisticsRepository @Inject constructor(
private val remote: GradeStatisticsRemote
) {
fun getGradesStatistics(semester: Semester, subjectName: String, isSemester: Boolean, forceRefresh: Boolean = false): Single<List<GradeStatistics>> {
return local.getGradesStatistics(semester, isSemester, subjectName).filter { !forceRefresh }
fun getGradesStatistics(semester: Semester, subjectName: String, isSemester: Boolean, forceRefresh: Boolean = false): Single<List<GradeStatisticsItem>> {
return local.getGradesStatistics(semester, isSemester, subjectName).map { it.mapToStatisticItems() }.filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.getGradeStatistics(semester, isSemester)
@ -31,21 +32,43 @@ class GradeStatisticsRepository @Inject constructor(
local.deleteGradesStatistics(old.uniqueSubtract(new))
local.saveGradesStatistics(new.uniqueSubtract(old))
}
}.flatMap { local.getGradesStatistics(semester, isSemester, subjectName).toSingle(emptyList()) })
}.flatMap { local.getGradesStatistics(semester, isSemester, subjectName).map { it.mapToStatisticItems() }.toSingle(emptyList()) })
}
fun getGradesPointsStatistics(semester: Semester, subjectName: String, forceRefresh: Boolean): Maybe<GradePointsStatistics> {
return local.getGradesPointsStatistics(semester, subjectName).filter { !forceRefresh }
fun getGradesPointsStatistics(semester: Semester, subjectName: String, forceRefresh: Boolean): Single<List<GradeStatisticsItem>> {
return local.getGradesPointsStatistics(semester, subjectName).map { it.mapToStatisticsItem() }.filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMapMaybe {
if (it) remote.getGradePointsStatistics(semester).toMaybe()
else Maybe.error(UnknownHostException())
.flatMap {
if (it) remote.getGradePointsStatistics(semester)
else Single.error(UnknownHostException())
}.flatMap { new ->
local.getGradesPointsStatistics(semester).defaultIfEmpty(emptyList())
local.getGradesPointsStatistics(semester).toSingle(emptyList())
.doOnSuccess { old ->
local.deleteGradesPointsStatistics(old.uniqueSubtract(new))
local.saveGradesPointsStatistics(new.uniqueSubtract(old))
}
}.flatMap { local.getGradesPointsStatistics(semester, subjectName) })
}.flatMap { local.getGradesPointsStatistics(semester, subjectName).map { it.mapToStatisticsItem() }.toSingle(emptyList()) })
}
private fun List<GradeStatistics>.mapToStatisticItems(): List<GradeStatisticsItem> {
return groupBy { it.subject }.map {
GradeStatisticsItem(
type = ViewType.PARTIAL,
partial = it.value
.sortedByDescending { item -> item.grade }
.filter { item -> item.amount != 0 },
points = null
)
}
}
private fun List<GradePointsStatistics>.mapToStatisticsItem(): List<GradeStatisticsItem> {
return map {
GradeStatisticsItem(
type = ViewType.POINTS,
partial = emptyList(),
points = it
)
}
}
}

View File

@ -19,6 +19,10 @@ class HomeworkLocal @Inject constructor(private val homeworkDb: HomeworkDao) {
homeworkDb.deleteAll(homework)
}
fun updateHomework(homework: List<Homework>) {
homeworkDb.updateAll(homework)
}
fun getHomework(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Homework>> {
return homeworkDb.loadAll(semester.semesterId, semester.studentId, startDate, endDate)
.filter { it.isNotEmpty() }

View File

@ -23,7 +23,8 @@ class HomeworkRemote @Inject constructor(private val sdk: Sdk) {
subject = it.subject,
content = it.content,
teacher = it.teacher,
teacherSymbol = it.teacherSymbol
teacherSymbol = it.teacherSymbol,
attachments = it.attachments.map { attachment -> attachment.url to attachment.name }
)
}
}

View File

@ -7,6 +7,7 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.friday
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Completable
import io.reactivex.Single
import org.threeten.bp.LocalDate
import java.net.UnknownHostException
@ -36,4 +37,12 @@ class HomeworkRepository @Inject constructor(
}.flatMap { local.getHomework(semester, monday, friday).toSingle(emptyList()) })
}
}
fun toggleDone(homework: Homework): Completable {
return Completable.fromCallable {
local.updateHomework(listOf(homework.apply {
isDone = !isDone
}))
}
}
}

View File

@ -0,0 +1,39 @@
package io.github.wulkanowy.data.repositories.logger
import android.content.Context
import io.reactivex.Single
import java.io.File
import java.io.FileNotFoundException
import javax.inject.Inject
class LoggerRepository @Inject constructor(private val context: Context) {
fun getLastLogLines(): Single<List<String>> {
return getLastModified()
.map { it.readText() }
.map { it.split("\n") }
}
fun getLogFiles(): Single<List<File>> {
return Single.fromCallable {
File(context.filesDir.absolutePath).listFiles(File::isFile)?.filter {
it.name.endsWith(".log")
}
}
}
private fun getLastModified(): Single<File> {
return Single.fromCallable {
var lastModifiedTime = Long.MIN_VALUE
var chosenFile: File? = null
File(context.filesDir.absolutePath).listFiles(File::isFile)?.forEach { file ->
if (file.lastModified() > lastModifiedTime) {
lastModifiedTime = file.lastModified()
chosenFile = file
}
}
if (chosenFile == null) throw FileNotFoundException("Log file not found")
chosenFile
}
}
}

View File

@ -2,7 +2,7 @@ package io.github.wulkanowy.data.repositories.luckynumber
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.reactivex.Maybe
import org.threeten.bp.LocalDate
import javax.inject.Inject
@ -23,7 +23,7 @@ class LuckyNumberLocal @Inject constructor(private val luckyNumberDb: LuckyNumbe
luckyNumberDb.deleteAll(listOf(luckyNumber))
}
fun getLuckyNumber(semester: Semester, date: LocalDate): Maybe<LuckyNumber> {
return luckyNumberDb.load(semester.studentId, date)
fun getLuckyNumber(student: Student, date: LocalDate): Maybe<LuckyNumber> {
return luckyNumberDb.load(student.studentId, date)
}
}

View File

@ -1,7 +1,7 @@
package io.github.wulkanowy.data.repositories.luckynumber
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.Sdk
import io.reactivex.Maybe
import org.threeten.bp.LocalDate
@ -11,14 +11,13 @@ import javax.inject.Singleton
@Singleton
class LuckyNumberRemote @Inject constructor(private val sdk: Sdk) {
fun getLuckyNumber(semester: Semester): Maybe<LuckyNumber> {
return sdk.getLuckyNumber()
.map {
LuckyNumber(
studentId = semester.studentId,
date = LocalDate.now(),
luckyNumber = it
)
}
fun getLuckyNumber(student: Student): Maybe<LuckyNumber> {
return sdk.getLuckyNumber(student.schoolShortName).map {
LuckyNumber(
studentId = student.studentId,
date = LocalDate.now(),
luckyNumber = it
)
}
}
}

View File

@ -2,8 +2,9 @@ package io.github.wulkanowy.data.repositories.luckynumber
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.SdkHelper
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.reactivex.Completable
import io.reactivex.Maybe
import org.threeten.bp.LocalDate
@ -15,37 +16,40 @@ import javax.inject.Singleton
class LuckyNumberRepository @Inject constructor(
private val settings: InternetObservingSettings,
private val local: LuckyNumberLocal,
private val remote: LuckyNumberRemote
private val remote: LuckyNumberRemote,
private val sdkHelper: SdkHelper
) {
fun getLuckyNumber(semester: Semester, forceRefresh: Boolean = false, notify: Boolean = false): Maybe<LuckyNumber> {
return local.getLuckyNumber(semester, LocalDate.now()).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMapMaybe {
if (it) remote.getLuckyNumber(semester)
else Maybe.error(UnknownHostException())
}.flatMap { new ->
local.getLuckyNumber(semester, LocalDate.now())
.doOnSuccess { old ->
if (new != old) {
local.deleteLuckyNumber(old)
fun getLuckyNumber(student: Student, forceRefresh: Boolean = false, notify: Boolean = false): Maybe<LuckyNumber> {
return Maybe.just(sdkHelper.init(student)).flatMap {
local.getLuckyNumber(student, LocalDate.now()).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMapMaybe {
if (it) remote.getLuckyNumber(student)
else Maybe.error(UnknownHostException())
}.flatMap { new ->
local.getLuckyNumber(student, LocalDate.now())
.doOnSuccess { old ->
if (new != old) {
local.deleteLuckyNumber(old)
local.saveLuckyNumber(new.apply {
if (notify) isNotified = false
})
}
}
.doOnComplete {
local.saveLuckyNumber(new.apply {
if (notify) isNotified = false
})
}
}
.doOnComplete {
local.saveLuckyNumber(new.apply {
if (notify) isNotified = false
})
}
}.flatMap({ local.getLuckyNumber(semester, LocalDate.now()) }, { Maybe.error(it) },
{ local.getLuckyNumber(semester, LocalDate.now()) })
)
}.flatMap({ local.getLuckyNumber(student, LocalDate.now()) }, { Maybe.error(it) },
{ local.getLuckyNumber(student, LocalDate.now()) })
)
}
}
fun getNotNotifiedLuckyNumber(semester: Semester): Maybe<LuckyNumber> {
return local.getLuckyNumber(semester, LocalDate.now()).filter { !it.isNotified }
fun getNotNotifiedLuckyNumber(student: Student): Maybe<LuckyNumber> {
return local.getLuckyNumber(student, LocalDate.now()).filter { !it.isNotified }
}
fun updateLuckyNumber(luckyNumber: LuckyNumber): Completable {

View File

@ -1,15 +1,22 @@
package io.github.wulkanowy.data.repositories.message
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageAttachment
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.message.MessageFolder.TRASHED
import io.reactivex.Maybe
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class MessageLocal @Inject constructor(private val messagesDb: MessagesDao) {
class MessageLocal @Inject constructor(
private val messagesDb: MessagesDao,
private val messageAttachmentDao: MessageAttachmentDao
) {
fun saveMessages(messages: List<Message>) {
messagesDb.insertAll(messages)
@ -23,8 +30,12 @@ class MessageLocal @Inject constructor(private val messagesDb: MessagesDao) {
messagesDb.deleteAll(messages)
}
fun getMessage(id: Long): Maybe<Message> {
return messagesDb.load(id)
fun getMessageWithAttachment(student: Student, message: Message): Single<MessageWithAttachment> {
return messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId)
}
fun saveMessageAttachments(attachments: List<MessageAttachment>) {
messageAttachmentDao.insertAttachments(attachments)
}
fun getMessages(student: Student, folder: MessageFolder): Maybe<List<Message>> {

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.data.repositories.message
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.Semester
import io.github.wulkanowy.data.db.entities.Student
@ -33,14 +34,25 @@ class MessageRemote @Inject constructor(private val sdk: Sdk) {
unread = it.unread ?: false,
unreadBy = it.unreadBy ?: 0,
readBy = it.readBy ?: 0,
removed = it.removed
removed = it.removed,
hasAttachments = it.hasAttachments
)
}
}
}
fun getMessagesContent(message: Message, markAsRead: Boolean = false): Single<String> {
return sdk.getMessageContent(message.messageId, message.folderId, markAsRead, message.realId)
fun getMessagesContentDetails(message: Message, markAsRead: Boolean = false): Single<Pair<String, List<MessageAttachment>>> {
return sdk.getMessageDetails(message.messageId, message.folderId, markAsRead, message.realId).map { details ->
details.content to details.attachments.map {
MessageAttachment(
realId = it.id,
messageId = it.messageId,
oneDriveId = it.oneDriveId,
url = it.url,
filename = it.filename
)
}
}
}
fun sendMessage(subject: String, content: String, recipients: List<Recipient>): Single<SentMessage> {

View File

@ -4,6 +4,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.SdkHelper
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
@ -11,8 +12,8 @@ import io.github.wulkanowy.data.repositories.message.MessageFolder.RECEIVED
import io.github.wulkanowy.sdk.pojo.SentMessage
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Completable
import io.reactivex.Maybe
import io.reactivex.Single
import timber.log.Timber
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@ -47,25 +48,31 @@ class MessageRepository @Inject constructor(
}
}
fun getMessage(student: Student, messageDbId: Long, markAsRead: Boolean = false): Single<Message> {
fun getMessage(student: Student, message: Message, markAsRead: Boolean = false): Single<MessageWithAttachment> {
return Single.just(sdkHelper.init(student))
.flatMap { _ ->
local.getMessage(messageDbId)
.filter { it.content.isNotEmpty() }
local.getMessageWithAttachment(student, message)
.filter {
it.message.content.isNotEmpty().also { status ->
Timber.d("Message content in db empty: ${!status}")
} && !it.message.unread
}
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) local.getMessage(messageDbId).toSingle()
if (it) local.getMessageWithAttachment(student, message)
else Single.error(UnknownHostException())
}
.flatMap { dbMessage ->
remote.getMessagesContent(dbMessage, markAsRead).doOnSuccess {
local.updateMessages(listOf(dbMessage.copy(unread = false).apply {
id = dbMessage.id
content = content.ifBlank { it }
remote.getMessagesContentDetails(dbMessage.message, markAsRead).doOnSuccess { (downloadedMessage, attachments) ->
local.updateMessages(listOf(dbMessage.message.copy(unread = !markAsRead).apply {
id = dbMessage.message.id
content = content.ifBlank { downloadedMessage }
}))
local.saveMessageAttachments(attachments)
Timber.d("Message ${message.messageId} with blank content: ${dbMessage.message.content.isBlank()}, marked as read")
}
}.flatMap {
local.getMessage(messageDbId).toSingle()
local.getMessageWithAttachment(student, message)
}
)
}
@ -77,10 +84,6 @@ class MessageRepository @Inject constructor(
.toSingle(emptyList())
}
fun updateMessage(message: Message): Completable {
return Completable.fromCallable { local.updateMessages(listOf(message)) }
}
fun updateMessages(messages: List<Message>): Completable {
return Completable.fromCallable { local.updateMessages(messages) }
}
@ -93,13 +96,12 @@ class MessageRepository @Inject constructor(
}
}
fun deleteMessage(message: Message): Maybe<Boolean> {
fun deleteMessage(message: Message): Single<Boolean> {
return ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.deleteMessage(message)
else Single.error(UnknownHostException())
}
.filter { it }
.doOnSuccess {
if (!message.removed) local.updateMessages(listOf(message.copy(removed = true).apply {
id = message.id

View File

@ -18,7 +18,11 @@ class NoteRemote @Inject constructor(private val sdk: Sdk) {
studentId = semester.studentId,
date = it.date,
teacher = it.teacher,
teacherSymbol = it.teacherSymbol,
category = it.category,
categoryType = it.categoryType.id,
isPointsShow = it.showPoints,
points = it.points,
content = it.content
)
}

View File

@ -0,0 +1,19 @@
package io.github.wulkanowy.data.repositories.recover
import io.github.wulkanowy.sdk.Sdk
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class RecoverRemote @Inject constructor(private val sdk: Sdk) {
fun getReCaptchaSiteKey(host: String, symbol: String): Single<Pair<String, String>> {
return sdk.getPasswordResetCaptchaCode(host, symbol)
}
fun sendRecoverRequest(url: String, symbol: String, email: String, reCaptchaResponse: String): Single<String> {
return sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
}
}

View File

@ -0,0 +1,26 @@
package io.github.wulkanowy.data.repositories.recover
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.reactivex.Single
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class RecoverRepository @Inject constructor(private val settings: InternetObservingSettings, private val remote: RecoverRemote) {
fun getReCaptchaSiteKey(host: String, symbol: String): Single<Pair<String, String>> {
return ReactiveNetwork.checkInternetConnectivity(settings).flatMap {
if (it) remote.getReCaptchaSiteKey(host, symbol)
else Single.error(UnknownHostException())
}
}
fun sendRecoverRequest(url: String, symbol: String, email: String, reCaptchaResponse: String): Single<String> {
return ReactiveNetwork.checkInternetConnectivity(settings).flatMap {
if (it) remote.sendRecoverRequest(url, symbol, email, reCaptchaResponse)
else Single.error(UnknownHostException())
}
}
}

View File

@ -19,6 +19,6 @@ class SemesterLocal @Inject constructor(private val semesterDb: SemesterDao) {
}
fun getSemesters(student: Student): Maybe<List<Semester>> {
return semesterDb.loadAll(student.studentId, student.classId).filter { !it.isEmpty() }
return semesterDb.loadAll(student.studentId, student.classId).filter { it.isNotEmpty() }
}
}

View File

@ -20,7 +20,6 @@ class SemesterRemote @Inject constructor(private val sdk: Sdk) {
schoolYear = it.schoolYear,
semesterId = it.semesterId,
semesterName = it.semesterNumber,
isCurrent = it.current,
start = it.start,
end = it.end,
classId = it.classId,

View File

@ -5,10 +5,11 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter
import io.github.wulkanowy.data.SdkHelper
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.getCurrentOrLast
import io.github.wulkanowy.utils.isCurrent
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Maybe
import io.reactivex.Single
import timber.log.Timber
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@ -21,28 +22,30 @@ class SemesterRepository @Inject constructor(
private val sdkHelper: SdkHelper
) {
fun getSemesters(student: Student, forceRefresh: Boolean = false): Single<List<Semester>> {
fun getSemesters(student: Student, forceRefresh: Boolean = false, refreshOnNoCurrent: Boolean = false): Single<List<Semester>> {
return Maybe.just(sdkHelper.init(student))
.flatMap { local.getSemesters(student).filter { !forceRefresh } }
.flatMap {
local.getSemesters(student).filter { !forceRefresh }.filter {
if (refreshOnNoCurrent) {
it.any { semester -> semester.isCurrent }
} else true
}
}
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.getSemesters(student) else Single.error(UnknownHostException())
if (it) remote.getSemesters(student)
else Single.error(UnknownHostException())
}.flatMap { new ->
val currentSemesters = new.filter { it.isCurrent }
if (currentSemesters.size == 1) {
local.getSemesters(student).toSingle(emptyList())
.doOnSuccess { old ->
local.deleteSemesters(old.uniqueSubtract(new))
local.saveSemesters(new.uniqueSubtract(old))
}
} else {
Timber.i("Current semesters list:\n${new.joinToString(separator = "\n")}")
throw IllegalArgumentException("Current semester can be only one.")
if (new.isEmpty()) throw IllegalArgumentException("Empty semester list!")
local.getSemesters(student).toSingle(emptyList()).doOnSuccess { old ->
local.deleteSemesters(old.uniqueSubtract(new))
local.saveSemesters(new.uniqueSubtract(old))
}
}.flatMap { local.getSemesters(student).toSingle(emptyList()) })
}
fun getCurrentSemester(student: Student, forceRefresh: Boolean = false): Single<Semester> {
return getSemesters(student, forceRefresh).map { item -> item.single { it.isCurrent } }
return getSemesters(student, forceRefresh).map { it.getCurrentOrLast() }
}
}

View File

@ -22,6 +22,7 @@ class StudentRemote @Inject constructor(private val sdk: Sdk) {
userLoginId = student.userLoginId,
studentName = student.studentName,
schoolSymbol = student.schoolSymbol,
schoolShortName = student.schoolShortName,
schoolName = student.schoolName,
className = student.className,
classId = student.classId,

View File

@ -29,7 +29,7 @@ class StudentRepository @Inject constructor(
}
}
fun getStudentsScrapper(email: String, password: String, endpoint: String, symbol: String = ""): Single<List<Student>> {
fun getStudentsScrapper(email: String, password: String, endpoint: String, symbol: String): Single<List<Student>> {
return ReactiveNetwork.checkInternetConnectivity(settings).flatMap {
if (it) remote.getStudentsScrapper(email, password, endpoint, symbol)
else Single.error(UnknownHostException("No internet connection"))

View File

@ -30,7 +30,7 @@ class TimetableRemote @Inject constructor(private val sdk: Sdk) {
teacher = it.teacher,
teacherOld = it.teacherOld,
info = it.info,
studentPlan = it.studentPlan,
isStudentPlan = it.studentPlan,
changes = it.changes,
canceled = it.canceled
)

View File

@ -9,6 +9,13 @@ import dagger.Module
import dagger.Provides
import dagger.android.ContributesAndroidInjector
import dagger.multibindings.IntoSet
import io.github.wulkanowy.services.sync.channels.Channel
import io.github.wulkanowy.services.sync.channels.DebugChannel
import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
import io.github.wulkanowy.services.sync.channels.NewGradesChannel
import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
import io.github.wulkanowy.services.sync.channels.PushChannel
import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork
import io.github.wulkanowy.services.sync.works.AttendanceWork
import io.github.wulkanowy.services.sync.works.CompletedLessonWork
@ -29,11 +36,10 @@ import javax.inject.Singleton
@Suppress("unused")
@AssistedModule
@Module(includes = [AssistedInject_ServicesModule::class, ServicesModule.Static::class])
@Module(includes = [AssistedInject_ServicesModule::class])
abstract class ServicesModule {
@Module
object Static {
companion object {
@Provides
fun provideWorkManager(context: Context) = WorkManager.getInstance(context)
@ -101,4 +107,28 @@ abstract class ServicesModule {
@Binds
@IntoSet
abstract fun provideGradeStatistics(work: GradeStatisticsWork): Work
@Binds
@IntoSet
abstract fun provideDebugChannel(channel: DebugChannel): Channel
@Binds
@IntoSet
abstract fun provideLuckyNumberChannel(channel: LuckyNumberChannel): Channel
@Binds
@IntoSet
abstract fun provideNewGradesChannel(channel: NewGradesChannel): Channel
@Binds
@IntoSet
abstract fun provideNewMessageChannel(channel: NewMessagesChannel): Channel
@Binds
@IntoSet
abstract fun provideNewNotesChannel(channel: NewNotesChannel): Channel
@Binds
@IntoSet
abstract fun providePushChannel(channel: PushChannel): Channel
}

View File

@ -2,21 +2,27 @@ package io.github.wulkanowy.services.sync
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.O
import androidx.core.app.NotificationManagerCompat
import androidx.work.BackoffPolicy.EXPONENTIAL
import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingPeriodicWorkPolicy.KEEP
import androidx.work.ExistingPeriodicWorkPolicy.REPLACE
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType.CONNECTED
import androidx.work.NetworkType.UNMETERED
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.paulinasadowska.rxworkmanagerobservers.extensions.getWorkInfoByIdObservable
import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.SharedPrefProvider.Companion.APP_VERSION_CODE_KEY
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.DebugChannel
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.services.sync.channels.Channel
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.isHolidays
import io.reactivex.Observable
import org.threeten.bp.LocalDate.now
import timber.log.Timber
import java.util.concurrent.TimeUnit.MINUTES
@ -27,9 +33,9 @@ import javax.inject.Singleton
class SyncManager @Inject constructor(
private val workManager: WorkManager,
private val preferencesRepository: PreferencesRepository,
channels: Set<@JvmSuppressWildcards Channel>,
notificationManager: NotificationManagerCompat,
sharedPrefProvider: SharedPrefProvider,
newEntriesChannel: NewEntriesChannel,
debugChannel: DebugChannel,
appInfo: AppInfo
) {
@ -37,18 +43,18 @@ class SyncManager @Inject constructor(
if (now().isHolidays) stopSyncWorker()
if (SDK_INT > O) {
newEntriesChannel.create()
if (appInfo.isDebug) debugChannel.create()
channels.forEach { it.create() }
notificationManager.deleteNotificationChannel("new_entries_channel")
}
if (sharedPrefProvider.getLong(APP_VERSION_CODE_KEY, -1L) != appInfo.versionCode.toLong()) {
startSyncWorker(true)
startPeriodicSyncWorker(true)
sharedPrefProvider.putLong(APP_VERSION_CODE_KEY, appInfo.versionCode.toLong(), true)
}
Timber.i("SyncManager was initialized")
}
fun startSyncWorker(restart: Boolean = false) {
fun startPeriodicSyncWorker(restart: Boolean = false) {
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
PeriodicWorkRequestBuilder<SyncWorker>(preferencesRepository.servicesInterval, MINUTES)
@ -61,6 +67,19 @@ class SyncManager @Inject constructor(
}
}
fun startOneTimeSyncWorker(): Observable<WorkInfo> {
val work = OneTimeWorkRequestBuilder<SyncWorker>()
.setInputData(
Data.Builder()
.putBoolean("one_time", true)
.build()
)
.build()
workManager.enqueueUniqueWork("${SyncWorker::class.java.simpleName}_one_time", ExistingWorkPolicy.REPLACE, work)
return workManager.getWorkInfoByIdObservable(work.id)
}
fun stopSyncWorker() {
workManager.cancelUniqueWork(SyncWorker::class.java.simpleName)
}

View File

@ -1,10 +1,12 @@
package io.github.wulkanowy.services.sync
import android.content.Context
import android.os.Build.VERSION_CODES.LOLLIPOP
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.BigTextStyle
import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
import androidx.core.app.NotificationManagerCompat
import androidx.work.Data
import androidx.work.ListenableWorker
import androidx.work.RxWorker
import androidx.work.WorkerParameters
@ -17,6 +19,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.sdk.exception.FeatureDisabledException
import io.github.wulkanowy.services.sync.channels.DebugChannel
import io.github.wulkanowy.services.sync.works.Work
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.getCompatColor
import io.reactivex.Completable
import io.reactivex.Single
@ -30,7 +33,8 @@ class SyncWorker @AssistedInject constructor(
private val semesterRepository: SemesterRepository,
private val works: Set<@JvmSuppressWildcards Work>,
private val preferencesRepository: PreferencesRepository,
private val notificationManager: NotificationManagerCompat
private val notificationManager: NotificationManagerCompat,
private val appInfo: AppInfo
) : RxWorker(appContext, workerParameters) {
override fun createWork(): Single<Result> {
@ -52,8 +56,15 @@ class SyncWorker @AssistedInject constructor(
.toSingleDefault(Result.success())
.onErrorReturn {
Timber.e(it, "There was an error during synchronization")
if (it is FeatureDisabledException) Result.success()
else Result.retry()
when {
it is FeatureDisabledException -> Result.success()
inputData.getBoolean("one_time", false) -> {
Result.failure(Data.Builder()
.putString("error", it.toString())
.build())
}
else -> Result.retry()
}
}
.doOnSuccess {
if (preferencesRepository.isDebugNotificationEnable) notify(it)
@ -64,7 +75,7 @@ class SyncWorker @AssistedInject constructor(
private fun notify(result: Result) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID)
.setContentTitle("Debug notification")
.setSmallIcon(R.drawable.ic_more_settings)
.setSmallIcon(R.drawable.ic_stat_push)
.setAutoCancel(true)
.setColor(applicationContext.getCompatColor(R.color.colorPrimary))
.setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result"))

View File

@ -0,0 +1,6 @@
package io.github.wulkanowy.services.sync.channels
interface Channel {
fun create()
}

View File

@ -7,19 +7,22 @@ import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import io.github.wulkanowy.R
import io.github.wulkanowy.utils.AppInfo
import javax.inject.Inject
@TargetApi(26)
class DebugChannel @Inject constructor(
private val notificationManager: NotificationManagerCompat,
private val context: Context
) {
private val context: Context,
private val appInfo: AppInfo
) : Channel {
companion object {
const val CHANNEL_ID = "debug_channel"
}
fun create() {
override fun create() {
if (appInfo.isDebug) return
notificationManager.createNotificationChannel(
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_debug), IMPORTANCE_DEFAULT)
.apply {

View File

@ -0,0 +1,31 @@
package io.github.wulkanowy.services.sync.channels
import android.annotation.TargetApi
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import io.github.wulkanowy.R
import javax.inject.Inject
@TargetApi(26)
class LuckyNumberChannel @Inject constructor(
private val notificationManager: NotificationManagerCompat,
private val context: Context
) : Channel {
companion object {
const val CHANNEL_ID = "lucky_number_channel"
}
override fun create() {
notificationManager.createNotificationChannel(
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_lucky_number), NotificationManager.IMPORTANCE_HIGH)
.apply {
enableLights(true)
enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
})
}
}

View File

@ -0,0 +1,31 @@
package io.github.wulkanowy.services.sync.channels
import android.annotation.TargetApi
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import io.github.wulkanowy.R
import javax.inject.Inject
@TargetApi(26)
class NewGradesChannel @Inject constructor(
private val notificationManager: NotificationManagerCompat,
private val context: Context
) : Channel {
companion object {
const val CHANNEL_ID = "new_grade_channel"
}
override fun create() {
notificationManager.createNotificationChannel(
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_grades), NotificationManager.IMPORTANCE_HIGH)
.apply {
enableLights(true)
enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
})
}
}

View File

@ -0,0 +1,31 @@
package io.github.wulkanowy.services.sync.channels
import android.annotation.TargetApi
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import io.github.wulkanowy.R
import javax.inject.Inject
@TargetApi(26)
class NewMessagesChannel @Inject constructor(
private val notificationManager: NotificationManagerCompat,
private val context: Context
) : Channel {
companion object {
const val CHANNEL_ID = "new_message_channel"
}
override fun create() {
notificationManager.createNotificationChannel(
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_message), NotificationManager.IMPORTANCE_HIGH)
.apply {
enableLights(true)
enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
})
}
}

View File

@ -1,31 +1,31 @@
package io.github.wulkanowy.services.sync.channels
import android.annotation.TargetApi
import android.app.Notification.VISIBILITY_PUBLIC
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import io.github.wulkanowy.R
import javax.inject.Inject
@TargetApi(26)
class NewEntriesChannel @Inject constructor(
class NewNotesChannel @Inject constructor(
private val notificationManager: NotificationManagerCompat,
private val context: Context
) {
) : Channel {
companion object {
const val CHANNEL_ID = "new_entries_channel"
const val CHANNEL_ID = "new_notes_channel"
}
fun create() {
override fun create() {
notificationManager.createNotificationChannel(
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_entries), IMPORTANCE_HIGH)
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_notes), NotificationManager.IMPORTANCE_HIGH)
.apply {
enableLights(true)
enableVibration(true)
lockscreenVisibility = VISIBILITY_PUBLIC
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
})
}
}

View File

@ -0,0 +1,32 @@
package io.github.wulkanowy.services.sync.channels
import android.annotation.TargetApi
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import io.github.wulkanowy.R
import javax.inject.Inject
@TargetApi(26)
class PushChannel @Inject constructor(
private val notificationManager: NotificationManagerCompat,
private val context: Context
) : Channel {
companion object {
const val CHANNEL_ID = "push_channel"
}
override fun create() {
notificationManager.createNotificationChannel(
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_push), NotificationManager.IMPORTANCE_HIGH)
.apply {
enableLights(true)
enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
}
)
}
}

View File

@ -13,7 +13,7 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.grade.GradeRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.services.sync.channels.NewGradesChannel
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.getCompatColor
@ -38,7 +38,7 @@ class GradeWork @Inject constructor(
}
private fun notify(grades: List<Grade>) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID)
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewGradesChannel.CHANNEL_ID)
.setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items, grades.size, grades.size))
.setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items, grades.size, grades.size))
.setSmallIcon(R.drawable.ic_stat_grade)

View File

@ -13,7 +13,7 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.getCompatColor
@ -29,8 +29,8 @@ class LuckyNumberWork @Inject constructor(
) : Work {
override fun create(student: Student, semester: Semester): Completable {
return luckyNumberRepository.getLuckyNumber(semester, true, preferencesRepository.isNotificationsEnable)
.flatMap { luckyNumberRepository.getNotNotifiedLuckyNumber(semester) }
return luckyNumberRepository.getLuckyNumber(student, true, preferencesRepository.isNotificationsEnable)
.flatMap { luckyNumberRepository.getNotNotifiedLuckyNumber(student) }
.flatMapCompletable {
notify(it)
luckyNumberRepository.updateLuckyNumber(it.apply { isNotified = true })
@ -38,7 +38,7 @@ class LuckyNumberWork @Inject constructor(
}
private fun notify(luckyNumber: LuckyNumber) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID)
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, LuckyNumberChannel.CHANNEL_ID)
.setContentTitle(context.getString(R.string.lucky_number_notify_new_item_title))
.setContentText(context.getString(R.string.lucky_number_notify_new_item, luckyNumber.luckyNumber))
.setSmallIcon(R.drawable.ic_stat_luckynumber)

View File

@ -14,7 +14,7 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.message.MessageFolder.RECEIVED
import io.github.wulkanowy.data.repositories.message.MessageRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.getCompatColor
@ -39,7 +39,7 @@ class MessageWork @Inject constructor(
}
private fun notify(messages: List<Message>) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID)
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewMessagesChannel.CHANNEL_ID)
.setContentTitle(context.resources.getQuantityString(R.plurals.message_new_items, messages.size, messages.size))
.setContentText(context.resources.getQuantityString(R.plurals.message_notify_new_items, messages.size, messages.size))
.setSmallIcon(R.drawable.ic_stat_message)

View File

@ -13,7 +13,7 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.note.NoteRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.getCompatColor
@ -38,7 +38,7 @@ class NoteWork @Inject constructor(
}
private fun notify(notes: List<Note>) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewEntriesChannel.CHANNEL_ID)
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewNotesChannel.CHANNEL_ID)
.setContentTitle(context.resources.getQuantityString(R.plurals.note_new_items, notes.size, notes.size))
.setContentText(context.resources.getQuantityString(R.plurals.note_notify_new_items, notes.size, notes.size))
.setSmallIcon(R.drawable.ic_stat_note)

View File

@ -4,6 +4,7 @@ import android.content.Intent
import android.widget.RemoteViewsService
import dagger.android.AndroidInjection
import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.data.repositories.timetable.TimetableRepository
@ -22,6 +23,9 @@ class TimetableWidgetService : RemoteViewsService() {
@Inject
lateinit var semesterRepo: SemesterRepository
@Inject
lateinit var prefRepository: PreferencesRepository
@Inject
lateinit var sharedPref: SharedPrefProvider
@ -30,6 +34,6 @@ class TimetableWidgetService : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory {
AndroidInjection.inject(this)
return TimetableWidgetFactory(timetableRepo, studentRepo, semesterRepo, sharedPref, schedulers, applicationContext, intent)
return TimetableWidgetFactory(timetableRepo, studentRepo, semesterRepo, prefRepository, sharedPref, schedulers, applicationContext, intent)
}
}

View File

@ -1,7 +1,7 @@
package io.github.wulkanowy.ui.base
import android.content.res.Resources
import com.readystatesoftware.chuck.api.ChuckCollector
import com.chuckerteam.chucker.api.ChuckerCollector
import io.github.wulkanowy.R
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.sdk.exception.BadCredentialsException
@ -15,7 +15,7 @@ import java.net.SocketTimeoutException
import java.net.UnknownHostException
import javax.inject.Inject
open class ErrorHandler @Inject constructor(protected val resources: Resources, private val chuckCollector: ChuckCollector) {
open class ErrorHandler @Inject constructor(protected val resources: Resources, private val chuckerCollector: ChuckerCollector) {
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
@ -24,7 +24,7 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources,
var onNoCurrentStudent: () -> Unit = {}
fun dispatch(error: Throwable) {
chuckCollector.onError(error.javaClass.simpleName, error)
chuckerCollector.onError(error.javaClass.simpleName, error)
Timber.e(error, "An exception occurred while the Wulkanowy was running")
proceed(error)
}

View File

@ -1,12 +1,6 @@
package io.github.wulkanowy.ui.modules.about
import android.content.Intent
import android.content.Intent.ACTION_SENDTO
import android.content.Intent.EXTRA_EMAIL
import android.content.Intent.EXTRA_SUBJECT
import android.content.Intent.EXTRA_TEXT
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -16,12 +10,14 @@ import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.about.creator.CreatorFragment
import io.github.wulkanowy.ui.modules.about.contributor.ContributorFragment
import io.github.wulkanowy.ui.modules.about.license.LicenseFragment
import io.github.wulkanowy.ui.modules.about.logviewer.LogViewerFragment
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.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_about.*
@ -45,7 +41,7 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
override val creatorsRes: Triple<String, String, Drawable?>?
get() = context?.run {
Triple(getString(R.string.about_creator), getString(R.string.about_creator_summary), getCompatDrawable(R.drawable.ic_about_creator))
Triple(getString(R.string.about_contributor), getString(R.string.about_contributor_summary), getCompatDrawable(R.drawable.ic_about_creator))
}
override val feedbackRes: Triple<String, String, Drawable?>?
@ -110,6 +106,10 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
}
}
override fun openLogViewer() {
if (appInfo.isDebug) (activity as? MainActivity)?.pushView(LogViewerFragment.newInstance())
}
override fun openDiscordInvite() {
context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage)
}
@ -119,26 +119,17 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
}
override fun openEmailClient() {
val intent = Intent(ACTION_SENDTO)
.apply {
data = Uri.parse("mailto:")
putExtra(EXTRA_EMAIL, arrayOf("wulkanowyinc@gmail.com"))
putExtra(EXTRA_SUBJECT, "Zgłoszenie błędu")
putExtra(EXTRA_TEXT, "Tu umieść treść zgłoszenia\n\n${"-".repeat(40)}\n " +
"""
Build: ${appInfo.versionCode}
SDK: ${appInfo.systemVersion}
Device: ${appInfo.systemManufacturer} ${appInfo.systemModel}
""".trimIndent())
requireContext().openEmailClient(
chooserTitle = getString(R.string.about_feedback),
email = "wulkanowyinc@gmail.com",
subject = "Zgłoszenie błędu",
body = requireContext().getString(R.string.about_feedback_template,
"${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), appInfo.versionName
),
onActivityNotFound = {
requireContext().openInternetBrowser("https://github.com/wulkanowy/wulkanowy/issues", ::showMessage)
}
context?.let {
if (intent.resolveActivity(it.packageManager) != null) {
startActivity(Intent.createChooser(intent, getString(R.string.about_feedback)))
} else {
it.openInternetBrowser("https://github.com/wulkanowy/wulkanowy/issues", ::showMessage)
}
}
)
}
override fun openFaqPage() {
@ -150,7 +141,7 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
}
override fun openCreators() {
(activity as? MainActivity)?.pushView(CreatorFragment.newInstance())
(activity as? MainActivity)?.pushView(ContributorFragment.newInstance())
}
override fun openPrivacyPolicy() {

View File

@ -27,6 +27,11 @@ class AboutPresenter @Inject constructor(
if (item !is AboutItem) return
view?.run {
when (item.title) {
versionRes?.first -> {
Timber.i("Opening log viewer")
openLogViewer()
analytics.logEvent("about_open", "name" to "log_viewer")
}
feedbackRes?.first -> {
Timber.i("Opening email client")
openEmailClient()

View File

@ -25,6 +25,8 @@ interface AboutView : BaseView {
fun updateData(header: AboutScrollableHeader, items: List<AboutItem>)
fun openLogViewer()
fun openDiscordInvite()
fun openEmailClient()

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.ui.modules.about.creator
package io.github.wulkanowy.ui.modules.about.contributor
import android.os.Bundle
import android.view.LayoutInflater
@ -18,18 +18,18 @@ import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_creator.*
import javax.inject.Inject
class CreatorFragment : BaseFragment(), CreatorView, MainView.TitledView {
class ContributorFragment : BaseFragment(), ContributorView, MainView.TitledView {
@Inject
lateinit var presenter: CreatorPresenter
lateinit var presenter: ContributorPresenter
@Inject
lateinit var creatorsAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
override val titleStringId get() = R.string.creators_title
override val titleStringId get() = R.string.contributors_title
companion object {
fun newInstance() = CreatorFragment()
fun newInstance() = ContributorFragment()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@ -53,7 +53,7 @@ class CreatorFragment : BaseFragment(), CreatorView, MainView.TitledView {
creatorSeeMore.setOnClickListener { presenter.onSeeMoreClick() }
}
override fun updateData(data: List<CreatorItem>) {
override fun updateData(data: List<ContributorItem>) {
creatorsAdapter.updateDataSet(data)
}

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.ui.modules.about.creator
package io.github.wulkanowy.ui.modules.about.contributor
import android.view.View
import coil.api.load
@ -10,11 +10,12 @@ import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.data.pojos.AppCreator
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_creator.*
import kotlinx.android.synthetic.main.item_contributor.*
class CreatorItem(val creator: AppCreator) : AbstractFlexibleItem<CreatorItem.ViewHolder>() {
class ContributorItem(val creator: AppCreator) :
AbstractFlexibleItem<ContributorItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_creator
override fun getLayoutRes() = R.layout.item_contributor
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
@ -33,7 +34,7 @@ class CreatorItem(val creator: AppCreator) : AbstractFlexibleItem<CreatorItem.Vi
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CreatorItem
other as ContributorItem
if (creator != other.creator) return false

View File

@ -1,30 +1,28 @@
package io.github.wulkanowy.ui.modules.about.creator
package io.github.wulkanowy.ui.modules.about.contributor
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.pojos.AppCreator
import io.github.wulkanowy.data.repositories.appcreator.AppCreatorRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Single
import javax.inject.Inject
class CreatorPresenter @Inject constructor(
class ContributorPresenter @Inject constructor(
schedulers: SchedulersProvider,
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val appCreatorRepository: AppCreatorRepository
) : BasePresenter<CreatorView>(errorHandler, studentRepository, schedulers) {
) : BasePresenter<ContributorView>(errorHandler, studentRepository, schedulers) {
override fun onAttachView(view: CreatorView) {
override fun onAttachView(view: ContributorView) {
super.onAttachView(view)
view.initView()
loadData()
}
fun onItemSelected(item: AbstractFlexibleItem<*>) {
if (item !is CreatorItem) return
if (item !is ContributorItem) return
view?.openUserGithubPage(item.creator.githubUsername)
}
@ -34,7 +32,7 @@ class CreatorPresenter @Inject constructor(
private fun loadData() {
disposable.add(appCreatorRepository.getAppCreators()
.map { it.map { creator -> CreatorItem(creator) } }
.map { it.map { creator -> ContributorItem(creator) } }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doFinally { view?.showProgress(false) }

View File

@ -1,12 +1,12 @@
package io.github.wulkanowy.ui.modules.about.creator
package io.github.wulkanowy.ui.modules.about.contributor
import io.github.wulkanowy.ui.base.BaseView
interface CreatorView : BaseView {
interface ContributorView : BaseView {
fun initView()
fun updateData(data: List<CreatorItem>)
fun updateData(data: List<ContributorItem>)
fun openUserGithubPage(username: String)

View File

@ -19,7 +19,7 @@ class LicenseItem(val library: Library) : AbstractFlexibleItem<LicenseItem.ViewH
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
with(holder) {
licenseItemName.text = library.libraryName
licenseItemSummary.text = library.license?.licenseName
licenseItemSummary.text = library.license?.licenseName?.takeIf { it.isNotBlank() } ?: library.libraryVersion
}
}

View File

@ -27,10 +27,6 @@ class LicensePresenter @Inject constructor(
private fun loadData() {
disposable.add(Single.fromCallable { view?.appLibraries }
.map {
val exclude = listOf("Android-Iconics", "CircleImageView", "FastAdapter", "Jsoup", "okio", "Retrofit")
it.filter { library -> !exclude.contains(library.libraryName) }
}
.map { it.map { library -> LicenseItem(library) } }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)

View File

@ -0,0 +1,22 @@
package io.github.wulkanowy.ui.modules.about.logviewer
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class LogViewerAdapter : RecyclerView.Adapter<LogViewerAdapter.ViewHolder>() {
var lines = emptyList<String>()
class ViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(TextView(parent.context))
}
override fun getItemCount() = lines.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.text = lines[position]
}
}

View File

@ -0,0 +1,98 @@
package io.github.wulkanowy.ui.modules.about.logviewer
import android.content.Intent
import android.content.Intent.EXTRA_EMAIL
import android.content.Intent.EXTRA_STREAM
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
import android.net.Uri
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.LOLLIPOP
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.content.FileProvider
import androidx.recyclerview.widget.LinearLayoutManager
import io.github.wulkanowy.BuildConfig.APPLICATION_ID
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView
import kotlinx.android.synthetic.main.fragment_logviewer.*
import java.io.File
import javax.inject.Inject
class LogViewerFragment : BaseFragment(), LogViewerView, MainView.TitledView {
@Inject
lateinit var presenter: LogViewerPresenter
private val logAdapter = LogViewerAdapter()
override val titleStringId: Int
get() = R.string.logviewer_title
companion object {
fun newInstance() = LogViewerFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_logviewer, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
messageContainer = logViewerRecycler
presenter.onAttachView(this)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.action_menu_logviewer, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == R.id.logViewerMenuShare) presenter.onShareLogsSelected()
else false
}
override fun initView() {
with(logViewerRecycler) {
layoutManager = LinearLayoutManager(context)
adapter = logAdapter
}
logViewRefreshButton.setOnClickListener { presenter.onRefreshClick() }
}
override fun setLines(lines: List<String>) {
logAdapter.lines = lines
logAdapter.notifyDataSetChanged()
logViewerRecycler.scrollToPosition(lines.size - 1)
}
override fun shareLogs(files: List<File>) {
val intent = Intent(Intent.ACTION_SEND_MULTIPLE).apply {
type = "text/plain"
putExtra(EXTRA_EMAIL, arrayOf("wulkanowyinc@gmail.com"))
addFlags(FLAG_GRANT_READ_URI_PERMISSION)
putParcelableArrayListExtra(EXTRA_STREAM, ArrayList(files.map {
if (SDK_INT < LOLLIPOP) Uri.fromFile(it)
else FileProvider.getUriForFile(requireContext(), "$APPLICATION_ID.fileprovider", it)
}))
}
startActivity(Intent.createChooser(intent, getString(R.string.logviewer_share)))
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
}

View File

@ -0,0 +1,54 @@
package io.github.wulkanowy.ui.modules.about.logviewer
import io.github.wulkanowy.data.repositories.logger.LoggerRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.SchedulersProvider
import timber.log.Timber
import javax.inject.Inject
class LogViewerPresenter @Inject constructor(
schedulers: SchedulersProvider,
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val loggerRepository: LoggerRepository
) : BasePresenter<LogViewerView>(errorHandler, studentRepository, schedulers) {
override fun onAttachView(view: LogViewerView) {
super.onAttachView(view)
view.initView()
loadLogFile()
}
fun onShareLogsSelected(): Boolean {
disposable.add(loggerRepository.getLogFiles()
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({
Timber.i("Loading logs files result: ${it.joinToString { it.name }}")
view?.shareLogs(it)
}, {
Timber.i("Loading logs files result: An exception occurred")
errorHandler.dispatch(it)
}))
return true
}
fun onRefreshClick() {
loadLogFile()
}
private fun loadLogFile() {
disposable.add(loggerRepository.getLastLogLines()
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({
Timber.i("Loading last log file result: load ${it.size} lines")
view?.setLines(it)
}, {
Timber.i("Loading last log file result: An exception occurred")
errorHandler.dispatch(it)
}))
}
}

View File

@ -0,0 +1,13 @@
package io.github.wulkanowy.ui.modules.about.logviewer
import io.github.wulkanowy.ui.base.BaseView
import java.io.File
interface LogViewerView : BaseView {
fun initView()
fun setLines(lines: List<String>)
fun shareLogs(files: List<File>)
}

View File

@ -36,6 +36,8 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie
fun newInstance() = AttendanceSummaryFragment()
}
override val totalString get() = getString(R.string.attendance_summary_total)
override val titleStringId get() = R.string.attendance_title
override val isViewEmpty get() = attendanceSummaryAdapter.isEmpty

View File

@ -12,6 +12,7 @@ import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.getFormattedName
import org.threeten.bp.Month
import timber.log.Timber
import java.lang.String.format
import java.util.Locale.FRANCE
@ -144,8 +145,25 @@ class AttendanceSummaryPresenter @Inject constructor(
)
}
private fun createAttendanceSummaryTotalItem(attendanceSummary: List<AttendanceSummary>): AttendanceSummaryItem {
return AttendanceSummaryItem(
month = view?.totalString.orEmpty(),
percentage = formatPercentage(attendanceSummary.calculatePercentage()),
present = attendanceSummary.sumBy { it.presence }.toString(),
absence = attendanceSummary.sumBy { it.absence }.toString(),
excusedAbsence = attendanceSummary.sumBy { it.absenceExcused }.toString(),
schoolAbsence = attendanceSummary.sumBy { it.absenceForSchoolReasons }.toString(),
exemption = attendanceSummary.sumBy { it.exemption }.toString(),
lateness = attendanceSummary.sumBy { it.lateness }.toString(),
excusedLateness = attendanceSummary.sumBy { it.latenessExcused }.toString()
)
}
private fun createAttendanceSummaryItems(attendanceSummary: List<AttendanceSummary>): List<AttendanceSummaryItem> {
return attendanceSummary.sortedByDescending { it.id }.map {
if (attendanceSummary.isEmpty()) return emptyList()
return listOf(createAttendanceSummaryTotalItem(attendanceSummary)) + attendanceSummary.sortedByDescending {
if (it.month.value <= Month.JUNE.value) it.month.value + 12 else it.month.value
}.map {
AttendanceSummaryItem(
month = it.month.getFormattedName(),
percentage = formatPercentage(it.calculatePercentage()),

View File

@ -4,6 +4,8 @@ import io.github.wulkanowy.ui.base.BaseView
interface AttendanceSummaryView : BaseView {
val totalString: String
val isViewEmpty: Boolean
fun initView()

View File

@ -40,6 +40,8 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie
override val titleStringId get() = R.string.grade_title
override var subtitleString = ""
override val currentPageIndex get() = gradeViewPager.currentItem
override fun onCreate(savedInstanceState: Bundle?) {
@ -133,6 +135,11 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie
.show()
}
override fun setCurrentSemesterName(semester: Int, schoolYear: Int) {
subtitleString = getString(R.string.grade_subtitle, semester, schoolYear, schoolYear + 1)
(activity as MainView).setViewSubTitle(subtitleString)
}
fun onChildRefresh() {
presenter.onChildViewRefresh()
}

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