1
0

Compare commits

..

38 Commits

Author SHA1 Message Date
9f87b92937 Merge branch 'release/0.19.0' 2020-06-14 22:44:36 +02:00
c13f12f729 Version 0.19.0 2020-06-14 22:40:36 +02:00
dfe7981e7f New Crowdin translations (#874)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2020-06-14 22:37:58 +02:00
6e1ddb482e Message fuzzy search (#869) 2020-06-14 14:05:24 +02:00
924bcb0d64 Message sharing and printing (#866) 2020-06-14 00:50:09 +02:00
a6682c9b73 Add predicted and final grade notifications (#872) 2020-06-13 17:11:18 +02:00
a529836937 Fix lint errors (#873) 2020-06-13 14:12:01 +02:00
a05da2656a Add account headers in student picker (#871) 2020-06-12 21:35:51 +02:00
30af77614e Fix showing summary summary for subjects without partial grades (#877) 2020-06-11 14:38:04 +02:00
eedaa63771 Bump sonarqube-gradle-plugin from 2.8 to 3.0 (#882) 2020-06-11 12:05:54 +00:00
f151f7bd62 Bump about_libraries from 8.1.6 to 8.2.0 (#879) 2020-06-11 12:05:30 +00:00
00943717a2 Bump firebase-crashlytics from 17.0.0 to 17.0.1 (#880) 2020-06-11 12:02:38 +00:00
8cce81585a Bump firebase-analytics from 17.4.2 to 17.4.3 (#881) 2020-06-11 12:02:10 +00:00
5529ffcf73 Bump fragment-ktx from 1.2.4 to 1.2.5 (#878) 2020-06-11 12:01:52 +00:00
7e6f892e23 Merge tag '0.18.3' into develop
Version 0.18.3
2020-06-10 19:18:31 +02:00
d3a6ea5acf Merge branch 'release/0.18.3' 2020-06-10 19:18:23 +02:00
674a78b661 Version 0.18.3 2020-06-10 19:18:09 +02:00
5c84c8d5b1 Fix force average calc from two semesters (#870) 2020-06-10 17:28:49 +02:00
522a36d670 Fix message deleting (#875) 2020-06-10 16:26:45 +02:00
2d0cfc3e8e Merge tag '0.18.2' into develop
Version 0.18.2
2020-06-02 17:08:02 +02:00
4b6b722f87 Merge branch 'release/0.18.2' 2020-06-02 17:07:58 +02:00
419675066f Version 0.18.2 2020-06-02 17:07:52 +02:00
191b1ad022 Emulate summaries from grade list when summaries are empty (#855) 2020-06-02 15:51:15 +02:00
792e44a9d0 Fix login button state in student select login fragment (#863) 2020-06-02 15:50:32 +02:00
ff5a47b0df New Crowdin translations (#856) 2020-06-02 15:18:41 +02:00
7bf0acb703 Revert "Bump sonarqube-gradle-plugin from 2.8 to 3.0" (#864)
This reverts commit ab7d30c995.
2020-06-02 15:13:41 +02:00
ba5dbf90d8 Fixes in updating adapter items (#854) 2020-06-02 01:04:41 +02:00
54f41aaa63 Fix too many alarms on samsung devices (#859) 2020-06-02 01:04:02 +02:00
1db42210e8 Bump about_libraries from 8.1.4 to 8.1.6 (#861)
Bumps `about_libraries` from 8.1.4 to 8.1.6.

Updates `aboutlibraries-plugin` from 8.1.4 to 8.1.6
- [Release notes](https://github.com/mikepenz/AboutLibraries/releases)
- [Changelog](https://github.com/mikepenz/AboutLibraries/blob/develop/gradle-release.gradle)
- [Commits](https://github.com/mikepenz/AboutLibraries/compare/v8.1.4...v8.1.6)

Updates `aboutlibraries-core` from 8.1.4 to 8.1.6
- [Release notes](https://github.com/mikepenz/AboutLibraries/releases)
- [Changelog](https://github.com/mikepenz/AboutLibraries/blob/develop/gradle-release.gradle)
- [Commits](https://github.com/mikepenz/AboutLibraries/compare/v8.1.4...v8.1.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-02 01:02:16 +02:00
fb554a4a3b Fix capitalization in new message activity (#860) 2020-06-02 01:01:58 +02:00
d8d13c73fb Filter out empty items in grade summary (#857) 2020-06-02 01:01:02 +02:00
5c0160a24d Don't capture click on login student select checkbox (#862) 2020-06-02 00:57:22 +02:00
ab7d30c995 Bump sonarqube-gradle-plugin from 2.8 to 3.0 (#853) 2020-05-30 12:19:52 +00:00
1cfa1f15c0 Bump gradle from 3.6.3 to 4.0.0 (#852) 2020-05-30 11:33:43 +00:00
2149a4db9f Bump about_libraries from 8.1.3 to 8.1.4 (#851) 2020-05-30 11:17:22 +00:00
df57d16d21 Bump dagger from 2.27 to 2.28 (#850) 2020-05-30 11:16:58 +00:00
2ff031005e Fix displaying the feature disabled message in completed lessons (#849) 2020-05-30 13:15:28 +02:00
b9ab85ee55 Merge tag '0.18.1' into develop
Version 0.18.1
2020-05-24 21:20:28 +02:00
74 changed files with 3295 additions and 396 deletions

View File

@ -14,7 +14,7 @@ cache:
branches: branches:
only: only:
- develop - develop
- 0.18.1 - 0.19.0
android: android:
licenses: licenses:

View File

@ -17,8 +17,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 17 minSdkVersion 17
targetSdkVersion 29 targetSdkVersion 29
versionCode 60 versionCode 63
versionName "0.18.1" versionName "0.19.0"
multiDexEnabled true multiDexEnabled true
resValue "string", "app_name", "Wulkanowy" resValue "string", "app_name", "Wulkanowy"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -77,8 +77,8 @@ android {
} }
} }
viewBinding { buildFeatures {
enabled = true viewBinding = true
} }
lintOptions { lintOptions {
@ -114,7 +114,7 @@ play {
ext { ext {
work_manager = "2.3.4" work_manager = "2.3.4"
room = "2.2.5" room = "2.2.5"
dagger = "2.27" dagger = "2.28"
chucker = "3.2.0" chucker = "3.2.0"
mockk = "1.9.2" mockk = "1.9.2"
} }
@ -124,14 +124,14 @@ configurations.all {
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:sdk:0.18.1" implementation "io.github.wulkanowy:sdk:0.19.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.core:core-ktx:1.2.0" implementation "androidx.core:core-ktx:1.2.0"
implementation "androidx.activity:activity-ktx:1.1.0" implementation "androidx.activity:activity-ktx:1.1.0"
implementation "androidx.appcompat:appcompat:1.2.0-rc01" implementation "androidx.appcompat:appcompat:1.2.0-rc01"
implementation "androidx.appcompat:appcompat-resources:1.1.0" implementation "androidx.appcompat:appcompat-resources:1.1.0"
implementation "androidx.fragment:fragment-ktx:1.2.4" implementation "androidx.fragment:fragment-ktx:1.2.5"
implementation "androidx.annotation:annotation:1.1.0" implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.multidex:multidex:2.0.1" implementation "androidx.multidex:multidex:2.0.1"
@ -142,7 +142,7 @@ dependencies {
implementation "androidx.constraintlayout:constraintlayout:1.1.3" implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.1.0" implementation "com.google.android.material:material:1.1.0"
implementation "com.github.wulkanowy:material-chips-input:2.0.1" implementation "com.github.wulkanowy:material-chips-input:2.1.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation "me.zhanghai.android.materialprogressbar:library:1.6.1" implementation "me.zhanghai.android.materialprogressbar:library:1.6.1"
@ -180,12 +180,13 @@ dependencies {
implementation 'com.wdullaer:materialdatetimepicker:4.2.3' implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
implementation "io.coil-kt:coil:0.11.0" implementation "io.coil-kt:coil:0.11.0"
implementation "io.github.wulkanowy:AppKillerManager:3.0.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
playImplementation 'com.google.firebase:firebase-analytics:17.4.2' playImplementation 'com.google.firebase:firebase-analytics:17.4.3'
playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.7' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.7'
playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.7" playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.7"
playImplementation 'com.google.firebase:firebase-messaging:20.2.0' playImplementation 'com.google.firebase:firebase-messaging:20.2.0'
playImplementation 'com.google.firebase:firebase-crashlytics:17.0.0' playImplementation 'com.google.firebase:firebase-crashlytics:17.0.1'
playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"

View File

@ -1741,4 +1741,4 @@
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd101f5a26a024f62e6fee161e421b882')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd101f5a26a024f62e6fee161e421b882')"
] ]
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -120,23 +120,23 @@ class Migration13Test : AbstractMigrationTest() {
assertEquals(2, first.diaryId) assertEquals(2, first.diaryId)
} }
getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let { getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let { semesters ->
assertTrue { it.single { it.second }.second } assertTrue { semesters.single { it.second }.second }
assertEquals(1970, it[0].first.schoolYear) assertEquals(1970, semesters[0].first.schoolYear)
assertEquals(of(1970, 1, 1), it[0].first.end) assertEquals(of(1970, 1, 1), semesters[0].first.end)
assertEquals(of(1970, 1, 1), it[0].first.start) assertEquals(of(1970, 1, 1), semesters[0].first.start)
assertFalse(it[0].second) assertFalse(semesters[0].second)
assertFalse(it[1].second) assertFalse(semesters[1].second)
assertFalse(it[2].second) assertFalse(semesters[2].second)
assertTrue(it[3].second) assertTrue(semesters[3].second)
} }
getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let { getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let { semesters ->
assertTrue { it.single { it.second }.second } assertTrue { semesters.single { it.second }.second }
assertFalse(it[0].second) assertFalse(semesters[0].second)
assertFalse(it[1].second) assertFalse(semesters[1].second)
assertFalse(it[2].second) assertFalse(semesters[2].second)
assertTrue(it[3].second) assertTrue(semesters[3].second)
} }
} }

View File

@ -10,6 +10,7 @@ import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.of import org.threeten.bp.LocalDate.of
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -35,9 +36,18 @@ class AttendanceLocalTest {
@Test @Test
fun saveAndReadTest() { fun saveAndReadTest() {
attendanceLocal.saveAttendance(listOf( attendanceLocal.saveAttendance(listOf(
Attendance(1, 2, 3, of(2018, 9, 10), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name), getAttendanceEntity(
Attendance(1, 2, 3, of(2018, 9, 14), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.WAITING.name), of(2018, 9, 10),
Attendance(1, 2, 3, of(2018, 9, 17), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name) SentExcuseStatus.ACCEPTED
),
getAttendanceEntity(
of(2018, 9, 14),
SentExcuseStatus.WAITING
),
getAttendanceEntity(
of(2018, 9, 17),
SentExcuseStatus.ACCEPTED
)
)) ))
val attendance = attendanceLocal val attendance = attendanceLocal
@ -50,4 +60,25 @@ class AttendanceLocalTest {
assertEquals(attendance[0].date, of(2018, 9, 10)) assertEquals(attendance[0].date, of(2018, 9, 10))
assertEquals(attendance[1].date, of(2018, 9, 14)) assertEquals(attendance[1].date, of(2018, 9, 14))
} }
private fun getAttendanceEntity(
date: LocalDate,
excuseStatus: SentExcuseStatus
) = Attendance(
studentId = 1,
diaryId = 2,
timeId = 3,
date = date,
number = 0,
subject = "",
name = "",
presence = false,
absence = false,
exemption = false,
lateness = false,
excused = false,
deleted = false,
excusable = false,
excuseStatus = excuseStatus.name
)
} }

View File

@ -5,7 +5,6 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.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.data.db.entities.Student
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before

View File

@ -2,7 +2,6 @@
package io.github.wulkanowy.utils package io.github.wulkanowy.utils
import android.content.Context
import timber.log.Timber import timber.log.Timber
open class TimberTreeNoOp : Timber.Tree() { open class TimberTreeNoOp : Timber.Tree() {

View File

@ -0,0 +1,94 @@
<!doctype html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>%SUBJECT% | Wulkanowy</title>
<style>
@page {
margin: 2.5cm;
size: A4;
}
body {
margin: 0;
font-family: sans-serif;
}
.title {
line-height: 1.5;
letter-spacing: 1pt;
font-size: 24pt;
font-weight: 200;
margin: 0 0 0.5cm;
}
.info {
margin: 0.5cm 0;
}
.info div {
font-size: 14pt;
font-weight: 400;
margin: 0.5cm 0;
}
h4 {
font-weight: 200;
text-transform: uppercase;
letter-spacing: 1pt;
font-size: 10pt;
margin: 0;
margin-bottom: 0.25cm;
font-family: sans-serif;
}
.content {
margin-top: 0.5cm;
font-size: 14pt;
font-weight: 400;
text-align: justify;
font-family: serif;
line-height: 1.5;
}
.content p {
page-break-after: auto;
page-break-inside: auto;
margin-bottom: 0.6cm;
}
.footer {
font-size: 11pt;
font-weight: 200;
display: flex;
align-items: center;
color: rgba(0, 0, 0, 0.5)
margin: 0;
margin-bottom: 0.5cm;
}
.footer .logo {
height: 0.5cm;
width: 0.5cm;
display: block;
margin-right: 0.2cm;
}
</style>
</head>
<body>
<h1 class="title">%SUBJECT%</h1>
<hr>
<div class="info">
%INFO%
</div>
<div class="footer">
<img src="wulkanowy-logo-black.svg" class="logo">
Wulkanowy Dzienniczek
</div>
<hr>
<div class="content">
<h4>Treść wiadomości</h4>
%CONTENT%
</div>
</body>
</html>

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 1024 1024"
xml:space="preserve"
width="1024"
height="1024"><metadata
id="metadata15"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs13" /><style
type="text/css"
id="style2">
.st0{fill:#D32F2F;}
.st1{fill:#AD2A2A;}
.st2{fill:#FFFFFF;}
</style><g
id="layer4"
style="display:none;fill:#808080"><rect
id="XMLID_57_"
x="0"
y="0"
class="st0"
width="3584"
height="1024"
style="display:inline;fill:#808080;stroke-width:1.02195609" /></g><g
id="layer3"
style="display:none;fill:#808080"><path
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#808080;fill-opacity:1;stroke:none"
d="M 3046.8164,390.66602 3134.3164,542 v 91.33398 L 3524.9824,1024 H 3584 V 732.18359 L 3242.4824,390.66602 h -23.666 l -53.0352,94.63086 -94.6308,-94.63086 z"
id="path18992" /><path
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#808080;fill-opacity:1;stroke:none"
d="m 2746.9824,390.66602 62,242.66796 L 3199.6484,1024 H 3584 V 940.68359 L 3033.9824,390.66602 h -21 l -21.9043,90.92773 -90.9277,-90.92773 h -18.5 l -25.4043,88.26367 -88.2637,-88.26367 z"
id="path18990" /><path
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#808080;fill-opacity:1;stroke:none"
d="m 2620.8164,387.33398 c -18.6667,0 -35.1667,4.60982 -49.5,13.83204 -14.3333,9.11111 -25.4451,22.22287 -33.334,39.33398 -7.7778,17 -11.666,36.5549 -11.666,58.66602 v 25 c 0,34.44444 8.7216,61.83463 26.166,82.16796 L 2970.1484,1024 h 323.168 l -623.166,-623.16602 c -14.2222,-9 -30.6673,-13.5 -49.334,-13.5 z"
id="path18988" /><path
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#808080;fill-opacity:1;stroke:none"
d="M 2293.4824,390.66602 V 633.33398 L 2684.1484,1024 h 423.336 l -633.334,-633.33398 h -20.334 v 139.66601 l -139.666,-139.66601 z"
id="path18984" /><path
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#808080;fill-opacity:1;stroke:none"
d="M 1864.8164,390.66602 V 633.33398 L 2255.4824,1024 h 413.334 l -633.334,-633.33398 h -25.832 l -60.584,63.75 -63.75,-63.75 z"
id="path18978" /><path
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#808080;fill-opacity:1;stroke:none"
d="M 1684.8164,390.66602 V 633.33398 L 2075.4824,1024 h 263.334 l -633.334,-633.33398 z"
id="path18976" /><path
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#808080;fill-opacity:1;stroke:none"
d="m 1133.6504,390.66602 62,242.66796 L 1586.3164,1024 h 467.668 l -633.334,-633.33398 h -21 l -21.9043,90.92773 -90.9277,-90.92773 h -18.5 l -25.4043,88.26367 -88.2637,-88.26367 z"
id="path19059" /><path
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#808080;fill-opacity:1;stroke:none"
d="m 1456.4824,390.66602 v 167.16796 c 0.5556,24.66667 8.5007,44 23.834,58 L 1888.4824,1024 h 372.168 l -633.334,-633.33398 h -20.666 V 520.5 l -129.834,-129.83398 z"
id="path18966" /><path
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#808080;fill-opacity:1;stroke:none"
d="M 2146.3164,390.66602 2054.4824,633.33398 2445.1484,1024 h 354.002 l -633.334,-633.33398 z"
id="path18982" /><path
style="display:inline;fill:#808080;stroke-width:0.78179646"
d="M 637.15234,214.95703 487.75,364.35742 466.01562,386.0918 c 0.31273,0.31271 0.54872,0.54666 0.70508,0.85937 0.0782,0.23454 0.23432,0.54671 0.3125,0.78125 0.31272,0.54726 0.47071,1.17339 0.47071,1.79883 0.0782,0.54726 -0.0799,1.01725 -0.31446,1.48633 -0.23454,0.54725 -0.70285,1.40597 -1.09375,1.79687 l 150.8086,149.71485 -23.68946,23.6875 -12.74414,-12.74219 -13.44726,-13.44727 -78.80469,-78.80664 -11.17969,-11.17968 -7.5039,-7.50391 -35.41602,-35.17969 -3.08984,-0.98047 -4.33594,4.26367 v 0.46876 c 0,7.34888 0.38998,15.00865 -1.48633,22.20117 -0.85998,3.28355 -2.34444,6.25595 -4.14258,8.91406 -0.15636,0.15636 -0.23627,0.23426 -0.31445,0.39062 -1.87631,2.57993 -4.06471,4.84619 -6.48828,6.95704 -5.3944,4.53442 -11.25752,8.67896 -17.27734,12.50976 -0.15637,0.0782 -0.23427,0.1562 -0.39063,0.23438 -2.11085,1.40723 -4.3012,2.7354 -6.49023,4.06445 -8.91248,5.39439 -18.37192,10.08772 -28.37891,13.13672 -1.25087,0.31272 -2.42317,-0.001 -3.36133,-0.70508 l -6.01953,5.94141 c 1.25087,0.62543 2.03136,1.87776 1.875,3.51953 -10e-6,0.15636 -0.0762,0.23231 -0.0762,0.38867 0,0.0782 -0.0781,0.23628 -0.0781,0.31445 -1.32905,4.45624 -2.34505,8.98897 -3.2832,13.60156 -0.15636,0.70363 -0.23622,1.33154 -0.39258,2.03516 -0.85997,4.37806 -1.64209,8.83288 -2.3457,13.21094 0.23453,5.3944 0.39263,11.0234 0.31445,16.65234 v 0.39258 c -0.0782,7.66161 -0.78373,15.32114 -2.8164,22.51367 -2.26721,8.28704 -6.64376,15.63728 -10.55274,23.22071 -0.0782,0.15636 -0.15815,0.23426 -0.23633,0.39062 -1.25088,2.42357 -2.49924,4.92399 -3.59375,7.50391 -4.84714,11.33605 -7.42749,23.92328 -10.55468,35.88476 -0.23454,0.70362 -0.39046,1.48578 -0.625,2.26758 0,0.15636 -0.0801,0.23427 -0.0801,0.39063 -2.97082,11.10151 -6.09819,22.28173 -10.94532,32.75781 -1.40724,2.97082 -2.81531,5.86322 -4.3789,8.75586 -0.15636,0.23454 -0.23231,0.46858 -0.38867,0.70312 -0.62544,1.09451 -1.25152,2.26871 -1.87696,3.44141 -0.0782,0.15636 -0.15619,0.23426 -0.23437,0.39062 -3.51809,6.25438 -7.27098,12.43118 -10.78906,18.68555 -5.0035,8.8343 -8.99075,18.13635 -13.83789,27.04883 -0.0782,0.15636 -0.1562,0.23426 -0.23438,0.39062 -0.70362,1.32905 -1.48579,2.65728 -2.26758,3.98633 -5.0035,8.20887 -10.63256,16.0279 -16.57422,23.61133 -0.15635,0.15636 -0.23426,0.3124 -0.39062,0.46875 -0.7818,1.01634 -1.48578,1.95443 -2.26758,2.89258 -3.90898,4.92532 -7.97378,9.85009 -11.96094,14.77539 -0.0782,0.15637 -0.23432,0.23622 -0.3125,0.39258 -8.75612,10.71061 -17.35628,21.49761 -24.54883,33.30273 0,0.70362 -0.15602,1.33159 -0.46874,1.95703 -1.25087,2.42357 -2.65734,4.68971 -3.90821,7.11328 -0.0782,0.15636 0.62511,1.24989 0.46875,1.40625 L 429.86133,1024 H 1463.0215 L 661.85547,222.92969 c -0.93816,2.11087 -5.23681,1.40935 -7.34766,-0.23242 -1.71995,-1.32906 -3.12603,-3.05147 -4.45508,-4.84961 -0.62544,-0.31271 -1.25168,-0.62288 -1.64257,-0.85743 -2.89265,-1.40723 -6.09933,-1.48632 -9.30469,-1.48632 -0.7818,-0.0782 -1.40588,-0.23416 -1.95313,-0.54688 z m -206.12304,191.41992 0.11914,-0.11523 -0.23438,0.0781 z"
id="XMLID_64_" /></g><g
id="layer2"
style="display:inline;fill:#000000;fill-opacity:0.49803922"><path
id="XMLID_42_"
d="m 295.17362,965.05417 c 1.0692,3.47527 0.5346,7.21786 -1.3367,10.29214 l -25.7972,41.83679 c -2.5396,4.1436 -7.2178,6.8169 -12.297,6.8169 H 14.345318 C 3.1176178,1024 -3.6991822,1012.2376 2.3157178,1003.4158 L 157.76692,774.44928 c 0.9356,-1.33663 1.4704,-2.80694 1.8713,-4.27723 l 71.2428,-304.21933 c 0.8021,-3.60893 3.2081,-6.6832 6.6833,-8.55449 l 96.5054,-52.93096 c 3.4753,-1.8713 5.8812,-4.94557 6.6832,-8.68816 l 12.9654,-56.53988 c 2.6733,-11.76242 19.5151,-14.30205 26.1981,-4.00991 l 4.6783,7.48519 c 2.0049,3.20793 2.5396,7.21785 1.2031,10.82678 l -87.9511,254.22895 c -0.6683,2.00497 -0.9355,4.1436 -0.5346,6.28223 l 21.9209,121.63426 c 0.401,2.40595 0.1334,4.94556 -0.9357,7.21785 l -52.2625,117.357 c -1.203,2.80696 -1.4704,5.88123 -0.5347,8.68817 z M 1009.7413,1024 H 843.46322 c -4.8117,0 -9.2228,-2.4059 -11.8959,-6.1485 L 719.69042,860.52891 c -0.6683,-1.0693 -1.3366,-2.13861 -1.7375,-3.3416 l -55.4707,-162.00078 c -1.0692,-3.20793 -3.6088,-6.01489 -6.8169,-7.61886 l -135.8026,-68.56965 c -3.7426,-1.87127 -6.4159,-5.34655 -7.2179,-9.22281 l -20.0495,-99.44603 c -0.2674,-1.60396 -0.9357,-3.20793 -2.005,-4.67824 l -46.1141,-67.76766 c -2.5396,-3.74259 -2.9405,-8.28717 -1.0693,-12.2971 l 28.0694,-60.01513 c 2.1387,-4.54457 6.817,-7.61886 12.1634,-7.88619 l 52.129,-3.07427 c 3.0742,-0.1337 5.8812,-1.20296 8.1536,-3.07427 l 38.3615,-29.80707 c 7.2178,-5.61388 18.1784,-3.20794 22.0546,4.67824 l 132.1937,268.93201 c 0.5346,1.20297 0.9357,2.40595 1.2029,3.60894 l 16.3072,108.13418 c 0.4009,2.53963 1.4701,4.8119 3.2079,6.6832 l 263.31808,288.17958 c 7.7525,8.5545 1.203,22.0546 -10.8269,22.0546 z M 363.20852,182.58501 c 0,-30.60907 19.3812,-56.94088 47.1834,-69.23798 -2.005,-3.3416 -3.2079,-6.95052 -3.2079,-10.82678 0,-14.836705 17.109,-26.866465 38.0942,-26.866465 0.5346,0 0.9356,0 1.4704,0 8.688,-14.43572 25.2624,-24.19318 44.2426,-24.19318 1.3367,0 2.6733,0 4.01,0.1337 1.7377,0.13369 3.4753,-0.66833 4.4109,-2.00497 14.0347,-21.38624 49.5894,-36.62394 91.159,-36.62394 15.3712,0 29.9406,2.13863 42.906,5.74756 3.0744,-5.07924 9.8911,-8.5545 17.7773,-8.5545 8.9556,0 16.5744,4.54458 18.8466,10.82678 10.9606,-12.69809 29.5398,-20.98524 50.6587,-20.98524 33.6834,0 60.9508,21.25257 60.9508,47.45072 0,3.20793 -0.401,6.2822 -1.203,9.35647 -0.5346,2.13864 0.6683,4.27725 2.9407,5.07924 21.5199,7.88618 36.0893,22.85655 36.0893,39.965535 0,19.51495 -18.8466,36.22296 -45.4458,42.77249 -2.1387,0.53466 -3.4753,2.40595 -3.4753,4.41092 0,0.1337 0,0.26731 0,0.40098 0,15.10404 -14.9704,27.5348 -34.218,28.87144 0.1333,0.66833 0.1333,1.33663 0.1333,2.13862 0,29.00509 -55.2031,52.3963 -123.2382,52.3963 -14.7029,0 -28.7377,-1.06932 -41.7031,-3.07427 0,0.26733 0,0.40099 0,0.66832 0,12.02975 -15.5051,21.78723 -34.4854,21.78723 -1.0692,0 -2.0049,0 -2.9405,-0.13369 1.3367,2.9406 2.005,6.01487 2.005,9.22281 0,18.71296 -23.6586,33.81699 -52.9311,33.81699 -3.2079,0 -6.2821,-0.1337 -9.3563,-0.53466 -2.4061,-0.26731 -4.6783,1.20299 -5.2131,3.47529 -2.5396,9.35647 -10.693,16.17333 -20.4504,16.17333 -11.7625,0 -21.119,-10.0248 -21.119,-22.32189 0,-5.74755 2.005,-10.96045 5.3466,-14.83671 1.203,-1.33663 1.6039,-3.20793 0.8019,-4.81191 -1.8713,-3.47526 -2.6733,-7.08419 -2.6733,-10.96044 v 0 c 0,-2.13862 -1.7376,-3.87626 -3.8763,-4.4109 -36.2228,-8.01985 -63.4903,-38.22792 -63.4903,-74.3172 z m 306.8925,726.06294 c 0.5348,1.60398 0.6683,3.20796 0.6683,4.94558 l -7.7525,97.97577 c -0.5346,6.9505 -6.6832,12.4307 -14.1683,12.4307 h -250.219 c -5.3466,0 -10.2921,-3.0743 -12.6982,-7.4852 l -41.3021,-76.72312 c -0.2673,-0.401 -0.401,-0.80199 -0.5347,-1.20298 l -38.8962,-94.23313 c -1.4702,-3.3416 -1.203,-7.21785 0.4011,-10.42581 l 64.5596,-126.31249 c 1.604,-3.07427 1.8712,-6.6832 0.6683,-9.89114 l -31.5447,-87.41626 c -1.0693,-3.07428 -0.9356,-6.54955 0.4011,-9.49015 l 52.6636,-112.14412 c 5.3464,-11.22778 22.8565,-10.29212 26.5991,1.47031 l 16.4407,51.05965 50.124,134.19868 c 1.3367,3.7426 4.5446,6.6832 8.5545,8.01985 l 106.9312,36.49027 c 4.1435,1.47032 7.3516,4.54458 8.6881,8.42084 z"
style="fill:#000000;stroke-width:0.78179646;fill-opacity:0.49803922" /><g
aria-label="WULKANOWY"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:0.49803922;stroke:none"
id="text4752" /></g></svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,17 +1,15 @@
package io.github.wulkanowy.data package io.github.wulkanowy.data
import android.app.AlarmManager
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.AssetManager import android.content.res.AssetManager
import android.content.res.Resources import android.content.res.Resources
import androidx.core.content.getSystemService
import androidx.preference.PreferenceManager 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.chuckerteam.chucker.api.ChuckerCollector import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.chuckerteam.chucker.api.RetentionManager import com.chuckerteam.chucker.api.RetentionManager
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.WalledGardenInternetObservingStrategy
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.AppDatabase

View File

@ -68,6 +68,7 @@ import io.github.wulkanowy.data.db.migrations.Migration22
import io.github.wulkanowy.data.db.migrations.Migration23 import io.github.wulkanowy.data.db.migrations.Migration23
import io.github.wulkanowy.data.db.migrations.Migration24 import io.github.wulkanowy.data.db.migrations.Migration24
import io.github.wulkanowy.data.db.migrations.Migration25 import io.github.wulkanowy.data.db.migrations.Migration25
import io.github.wulkanowy.data.db.migrations.Migration26
import io.github.wulkanowy.data.db.migrations.Migration3 import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration4 import io.github.wulkanowy.data.db.migrations.Migration4
import io.github.wulkanowy.data.db.migrations.Migration5 import io.github.wulkanowy.data.db.migrations.Migration5
@ -110,7 +111,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 25 const val VERSION_SCHEMA = 26
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> { fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
return arrayOf( return arrayOf(
@ -137,7 +138,8 @@ abstract class AppDatabase : RoomDatabase() {
Migration22(), Migration22(),
Migration23(), Migration23(),
Migration24(), Migration24(),
Migration25() Migration25(),
Migration26()
) )
} }

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import org.threeten.bp.LocalDateTime
@Entity(tableName = "GradesSummary") @Entity(tableName = "GradesSummary")
data class GradeSummary( data class GradeSummary(
@ -36,4 +37,16 @@ data class GradeSummary(
) { ) {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0 var id: Long = 0
@ColumnInfo(name = "is_predicted_grade_notified")
var isPredictedGradeNotified: Boolean = true
@ColumnInfo(name = "is_final_grade_notified")
var isFinalGradeNotified: Boolean = true
@ColumnInfo(name = "predicted_grade_last_change")
var predictedGradeLastChange: LocalDateTime = LocalDateTime.now()
@ColumnInfo(name = "final_grade_last_change")
var finalGradeLastChange: LocalDateTime = LocalDateTime.now()
} }

View File

@ -0,0 +1,14 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration26 : Migration(25, 26) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_predicted_grade_notified INTEGER NOT NULL DEFAULT 1")
database.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_final_grade_notified INTEGER NOT NULL DEFAULT 1")
database.execSQL("ALTER TABLE GradesSummary ADD COLUMN predicted_grade_last_change INTEGER NOT NULL DEFAULT 0")
database.execSQL("ALTER TABLE GradesSummary ADD COLUMN final_grade_last_change INTEGER NOT NULL DEFAULT 0")
}
}

View File

@ -10,7 +10,7 @@ import javax.inject.Singleton
@Singleton @Singleton
class AppCreatorRepository @Inject constructor(private val assets: AssetManager) { class AppCreatorRepository @Inject constructor(private val assets: AssetManager) {
fun getAppCreators(): Single<List<Contributor>> { fun getAppCreators(): Single<List<Contributor>> {
return Single.fromCallable<List<Contributor>> { return Single.fromCallable {
Gson().fromJson( Gson().fromJson(
assets.open("contributors.json").bufferedReader().use { it.readText() }, assets.open("contributors.json").bufferedReader().use { it.readText() },
Array<Contributor>::class.java Array<Contributor>::class.java

View File

@ -13,7 +13,7 @@ class ExamLocal @Inject constructor(private val examDb: ExamDao) {
fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Exam>> { fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Exam>> {
return examDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) return examDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate)
.filter { !it.isEmpty() } .filter { it.isNotEmpty() }
} }
fun saveExams(exams: List<Exam>) { fun saveExams(exams: List<Exam>) {

View File

@ -27,6 +27,10 @@ class GradeLocal @Inject constructor(
gradeDb.updateAll(grades) gradeDb.updateAll(grades)
} }
fun updateGradesSummary(gradesSummary: List<GradeSummary>) {
gradeSummaryDb.updateAll(gradesSummary)
}
fun getGradesDetails(semester: Semester): Maybe<List<Grade>> { fun getGradesDetails(semester: Semester): Maybe<List<Grade>> {
return gradeDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() } return gradeDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() }
} }

View File

@ -9,6 +9,7 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Single import io.reactivex.Single
import org.threeten.bp.LocalDateTime
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -43,7 +44,31 @@ class GradeRepository @Inject constructor(
local.getGradesSummary(semester).toSingle(emptyList()) local.getGradesSummary(semester).toSingle(emptyList())
.doOnSuccess { old -> .doOnSuccess { old ->
local.deleteGradesSummary(old.uniqueSubtract(newSummary)) local.deleteGradesSummary(old.uniqueSubtract(newSummary))
local.saveGradesSummary(newSummary.uniqueSubtract(old)) local.saveGradesSummary(newSummary.uniqueSubtract(old)
.onEach { summary ->
val oldSummary = old.find { oldSummary -> oldSummary.subject == summary.subject }
summary.isPredictedGradeNotified = when {
summary.predictedGrade.isEmpty() -> true
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
else -> true
}
summary.isFinalGradeNotified = when {
summary.finalGrade.isEmpty() -> true
notify && oldSummary?.finalGrade != summary.finalGrade -> false
else -> true
}
summary.predictedGradeLastChange = when {
oldSummary == null -> LocalDateTime.now()
summary.predictedGrade != oldSummary.predictedGrade -> LocalDateTime.now()
else -> oldSummary.predictedGradeLastChange
}
summary.finalGradeLastChange = when {
oldSummary == null -> LocalDateTime.now()
summary.finalGrade != oldSummary.finalGrade -> LocalDateTime.now()
else -> oldSummary.finalGradeLastChange
}
})
} }
} }
}.flatMap { }.flatMap {
@ -63,6 +88,14 @@ class GradeRepository @Inject constructor(
return local.getGradesDetails(semester).map { it.filter { grade -> !grade.isNotified } }.toSingle(emptyList()) return local.getGradesDetails(semester).map { it.filter { grade -> !grade.isNotified } }.toSingle(emptyList())
} }
fun getNotNotifiedPredictedGrades(semester: Semester): Single<List<GradeSummary>> {
return local.getGradesSummary(semester).map { it.filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified } }.toSingle(emptyList())
}
fun getNotNotifiedFinalGrades(semester: Semester): Single<List<GradeSummary>> {
return local.getGradesSummary(semester).map { it.filter { gradeSummary -> !gradeSummary.isFinalGradeNotified } }.toSingle(emptyList())
}
fun updateGrade(grade: Grade): Completable { fun updateGrade(grade: Grade): Completable {
return Completable.fromCallable { local.updateGrades(listOf(grade)) } return Completable.fromCallable { local.updateGrades(listOf(grade)) }
} }
@ -70,4 +103,8 @@ class GradeRepository @Inject constructor(
fun updateGrades(grades: List<Grade>): Completable { fun updateGrades(grades: List<Grade>): Completable {
return Completable.fromCallable { local.updateGrades(grades) } return Completable.fromCallable { local.updateGrades(grades) }
} }
fun updateGradesSummary(gradesSummary: List<GradeSummary>): Completable {
return Completable.fromCallable { local.updateGradesSummary(gradesSummary) }
}
} }

View File

@ -75,6 +75,6 @@ class MessageRemote @Inject constructor(private val sdk: Sdk) {
} }
fun deleteMessage(student: Student, message: Message): Single<Boolean> { fun deleteMessage(student: Student, message: Message): Single<Boolean> {
return sdk.init(student).deleteMessages(listOf(Pair(message.realId, message.folderId))) return sdk.init(student).deleteMessages(listOf(message.messageId to message.folderId))
} }
} }

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.preferences
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -17,8 +18,8 @@ class PreferencesRepository @Inject constructor(
val isShowPresent: Boolean val isShowPresent: Boolean
get() = getBoolean(R.string.pref_key_attendance_present, R.bool.pref_default_attendance_present) get() = getBoolean(R.string.pref_key_attendance_present, R.bool.pref_default_attendance_present)
val gradeAverageMode: String val gradeAverageMode: GradeAverageMode
get() = getString(R.string.pref_key_grade_average_mode, R.string.pref_default_grade_average_mode) get() = GradeAverageMode.getByValue(getString(R.string.pref_key_grade_average_mode, R.string.pref_default_grade_average_mode))
val gradeAverageForceCalc: Boolean val gradeAverageForceCalc: Boolean
get() = getBoolean(R.string.pref_key_grade_average_force_calc, R.bool.pref_default_grade_average_force_calc) get() = getBoolean(R.string.pref_key_grade_average_force_calc, R.bool.pref_default_grade_average_force_calc)

View File

@ -12,7 +12,7 @@ import javax.inject.Singleton
class RecipientLocal @Inject constructor(private val recipientDb: RecipientDao) { class RecipientLocal @Inject constructor(private val recipientDb: RecipientDao) {
fun getRecipients(student: Student, role: Int, unit: ReportingUnit): Maybe<List<Recipient>> { fun getRecipients(student: Student, role: Int, unit: ReportingUnit): Maybe<List<Recipient>> {
return recipientDb.load(student.studentId, role, unit.realId).filter { !it.isEmpty() } return recipientDb.load(student.studentId, role, unit.realId).filter { it.isNotEmpty() }
} }
fun saveRecipients(recipients: List<Recipient>): List<Long> { fun saveRecipients(recipients: List<Recipient>): List<Long> {

View File

@ -11,7 +11,7 @@ import javax.inject.Singleton
class ReportingUnitLocal @Inject constructor(private val reportingUnitDb: ReportingUnitDao) { class ReportingUnitLocal @Inject constructor(private val reportingUnitDb: ReportingUnitDao) {
fun getReportingUnits(student: Student): Maybe<List<ReportingUnit>> { fun getReportingUnits(student: Student): Maybe<List<ReportingUnit>> {
return reportingUnitDb.load(student.studentId).filter { !it.isEmpty() } return reportingUnitDb.load(student.studentId).filter { it.isNotEmpty() }
} }
fun getReportingUnit(student: Student, unitId: Int): Maybe<ReportingUnit> { fun getReportingUnit(student: Student, unitId: Int): Maybe<ReportingUnit> {

View File

@ -14,7 +14,7 @@ class StudentRemote @Inject constructor(private val sdk: Sdk) {
private fun mapStudents(students: List<SdkStudent>, email: String, password: String): List<Student> { private fun mapStudents(students: List<SdkStudent>, email: String, password: String): List<Student> {
return students.map { student -> return students.map { student ->
Student( Student(
email = email, email = email.ifBlank { student.email },
password = password, password = password,
isParent = student.isParent, isParent = student.isParent,
symbol = student.symbol, symbol = student.symbol,

View File

@ -3,7 +3,7 @@ package io.github.wulkanowy.services.alarm
import android.app.AlarmManager import android.app.AlarmManager
import android.app.AlarmManager.RTC_WAKEUP import android.app.AlarmManager.RTC_WAKEUP
import android.app.PendingIntent import android.app.PendingIntent
import android.app.PendingIntent.FLAG_CANCEL_CURRENT import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.core.app.AlarmManagerCompat import androidx.core.app.AlarmManagerCompat
@ -55,7 +55,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) { private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
if (now() in range) cancelNotification() if (now() in range) cancelNotification()
alarmManager.cancel(PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_CANCEL_CURRENT)) alarmManager.cancel(PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT))
} }
fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id) fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
@ -102,7 +102,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also { PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id) it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
it.putExtra(LESSON_TYPE, notificationType) it.putExtra(LESSON_TYPE, notificationType)
}, FLAG_CANCEL_CURRENT) }, FLAG_UPDATE_CURRENT)
) )
Timber.d("TimetableNotification scheduled: type: $notificationType, subject: ${intent.getStringExtra(LESSON_TITLE)}, start: $time, student: $studentId") Timber.d("TimetableNotification scheduled: type: $notificationType, subject: ${intent.getStringExtra(LESSON_TITLE)}, start: $time, student: $studentId")
} }

View File

@ -9,7 +9,7 @@ import javax.inject.Inject
class GradeStatisticsWork @Inject constructor(private val gradeStatisticsRepository: GradeStatisticsRepository) : Work { class GradeStatisticsWork @Inject constructor(private val gradeStatisticsRepository: GradeStatisticsRepository) : Work {
override fun create(student: Student, semester: Semester): Completable { override fun create(student: Student, semester: Semester): Completable {
return gradeStatisticsRepository.getGradesStatistics(student, semester, "Wszystkie", false, true) return gradeStatisticsRepository.getGradesStatistics(student, semester, "Wszystkie", false, forceRefresh = true)
.ignoreElement() .ignoreElement()
} }
} }

View File

@ -9,6 +9,7 @@ import androidx.core.app.NotificationCompat.PRIORITY_HIGH
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.grade.GradeRepository import io.github.wulkanowy.data.repositories.grade.GradeRepository
@ -30,17 +31,21 @@ class GradeWork @Inject constructor(
override fun create(student: Student, semester: Semester): Completable { override fun create(student: Student, semester: Semester): Completable {
return gradeRepository.getGrades(student, semester, true, preferencesRepository.isNotificationsEnable) return gradeRepository.getGrades(student, semester, true, preferencesRepository.isNotificationsEnable)
.flatMap { gradeRepository.getNotNotifiedGrades(semester) } .ignoreElement()
.flatMapCompletable { .concatWith(Completable.concatArray(gradeRepository.getNotNotifiedGrades(semester).flatMapCompletable {
if (it.isNotEmpty()) notify(it) if (it.isNotEmpty()) notifyDetails(it)
gradeRepository.updateGrades(it.onEach { grade -> grade.isNotified = true }) gradeRepository.updateGrades(it.onEach { grade -> grade.isNotified = true })
} }, gradeRepository.getNotNotifiedPredictedGrades(semester).flatMapCompletable {
if (it.isNotEmpty()) notifyPredicted(it)
gradeRepository.updateGradesSummary(it.onEach { grade -> grade.isPredictedGradeNotified = true })
}, gradeRepository.getNotNotifiedFinalGrades(semester).flatMapCompletable {
if (it.isNotEmpty()) notifyFinal(it)
gradeRepository.updateGradesSummary(it.onEach { grade -> grade.isFinalGradeNotified = true })
}))
} }
private fun notify(grades: List<Grade>) { private fun getNotificationBuilder(): NotificationCompat.Builder {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewGradesChannel.CHANNEL_ID) return 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) .setSmallIcon(R.drawable.ic_stat_grade)
.setAutoCancel(true) .setAutoCancel(true)
.setPriority(PRIORITY_HIGH) .setPriority(PRIORITY_HIGH)
@ -49,6 +54,12 @@ class GradeWork @Inject constructor(
.setContentIntent( .setContentIntent(
PendingIntent.getActivity(context, MainView.Section.GRADE.id, PendingIntent.getActivity(context, MainView.Section.GRADE.id,
MainActivity.getStartIntent(context, MainView.Section.GRADE, true), FLAG_UPDATE_CURRENT)) MainActivity.getStartIntent(context, MainView.Section.GRADE, true), FLAG_UPDATE_CURRENT))
}
private fun notifyDetails(grades: List<Grade>) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), getNotificationBuilder()
.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))
.setStyle(NotificationCompat.InboxStyle().run { .setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, grades.size, grades.size)) setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, grades.size, grades.size))
grades.forEach { addLine("${it.subject}: ${it.entry}") } grades.forEach { addLine("${it.subject}: ${it.entry}") }
@ -57,4 +68,30 @@ class GradeWork @Inject constructor(
.build() .build()
) )
} }
private fun notifyPredicted(gradesSummary: List<GradeSummary>) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), getNotificationBuilder()
.setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items_predicted, gradesSummary.size, gradesSummary.size))
.setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items_predicted, gradesSummary.size, gradesSummary.size))
.setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, gradesSummary.size, gradesSummary.size))
gradesSummary.forEach { addLine("${it.subject}: ${it.predictedGrade}") }
this
})
.build()
)
}
private fun notifyFinal(gradesSummary: List<GradeSummary>) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), getNotificationBuilder()
.setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items_final, gradesSummary.size, gradesSummary.size))
.setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items_final, gradesSummary.size, gradesSummary.size))
.setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, gradesSummary.size, gradesSummary.size))
gradesSummary.forEach { addLine("${it.subject}: ${it.finalGrade}") }
this
})
.build()
)
}
} }

View File

@ -25,9 +25,9 @@ class LogViewerPresenter @Inject constructor(
disposable.add(loggerRepository.getLogFiles() disposable.add(loggerRepository.getLogFiles()
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.subscribe({ .subscribe({ files ->
Timber.i("Loading logs files result: ${it.joinToString { it.name }}") Timber.i("Loading logs files result: ${files.joinToString { it.name }}")
view?.shareLogs(it) view?.shareLogs(files)
}, { }, {
Timber.i("Loading logs files result: An exception occurred") Timber.i("Loading logs files result: An exception occurred")
errorHandler.dispatch(it) errorHandler.dispatch(it)

View File

@ -0,0 +1,3 @@
package io.github.wulkanowy.ui.modules.account
data class Account(val email: String, val isParent: Boolean)

View File

@ -3,33 +3,72 @@ package io.github.wulkanowy.ui.modules.account
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.databinding.HeaderAccountBinding
import io.github.wulkanowy.databinding.ItemAccountBinding import io.github.wulkanowy.databinding.ItemAccountBinding
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject import javax.inject.Inject
class AccountAdapter @Inject constructor() : RecyclerView.Adapter<AccountAdapter.ItemViewHolder>() { class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var items = emptyList<Student>() var items = emptyList<AccountItem<*>>()
var onClickListener: (Student) -> Unit = {} var onClickListener: (Student) -> Unit = {}
override fun getItemCount() = items.size override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( override fun getItemViewType(position: Int) = items[position].viewType.id
ItemAccountBinding.inflate(LayoutInflater.from(parent.context), parent, false)
) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
AccountItem.ViewType.HEADER.id -> HeaderViewHolder(HeaderAccountBinding.inflate(inflater, parent, false))
AccountItem.ViewType.ITEM.id -> ItemViewHolder(ItemAccountBinding.inflate(inflater, parent, false))
else -> throw IllegalStateException()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is HeaderViewHolder -> bindHeaderViewHolder(holder.binding, items[position].value as Account)
is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position].value as Student)
}
}
private fun bindHeaderViewHolder(binding: HeaderAccountBinding, account: Account) {
with(binding) {
accountHeaderEmail.text = account.email
accountHeaderType.setText(if (account.isParent) R.string.account_type_parent else R.string.account_type_student)
}
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { private fun bindItemViewHolder(binding: ItemAccountBinding, student: Student) {
val student = items[position] with(binding) {
with(holder.binding) {
accountItemName.text = "${student.studentName} ${student.className}" accountItemName.text = "${student.studentName} ${student.className}"
accountItemSchool.text = student.schoolName accountItemSchool.text = student.schoolName
with(accountItemLoginMode) {
visibility = when (Sdk.Mode.valueOf(student.loginMode)) {
Sdk.Mode.API -> {
setText(R.string.account_login_mobile_api)
VISIBLE
}
Sdk.Mode.HYBRID -> {
setText(R.string.account_login_hybrid)
VISIBLE
}
Sdk.Mode.SCRAPPER -> {
GONE
}
}
}
with(accountItemImage) { with(accountItemImage) {
val colorImage = if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary) val colorImage = if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
@ -42,5 +81,8 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter<AccountAdapter
} }
} }
class HeaderViewHolder(val binding: HeaderAccountBinding) :
RecyclerView.ViewHolder(binding.root)
class ItemViewHolder(val binding: ItemAccountBinding) : RecyclerView.ViewHolder(binding.root) class ItemViewHolder(val binding: ItemAccountBinding) : RecyclerView.ViewHolder(binding.root)
} }

View File

@ -9,7 +9,6 @@ import android.widget.Toast.LENGTH_LONG
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.databinding.DialogAccountBinding import io.github.wulkanowy.databinding.DialogAccountBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
@ -54,7 +53,7 @@ class AccountDialog : BaseDialogFragment<DialogAccountBinding>(), AccountView {
} }
} }
override fun updateData(data: List<Student>) { override fun updateData(data: List<AccountItem<*>>) {
with(accountAdapter) { with(accountAdapter) {
items = data items = data
notifyDataSetChanged() notifyDataSetChanged()

View File

@ -0,0 +1,9 @@
package io.github.wulkanowy.ui.modules.account
data class AccountItem<out T>(val value: T, val viewType: ViewType) {
enum class ViewType(val id: Int) {
HEADER(1),
ITEM(2)
}
}

View File

@ -83,11 +83,20 @@ class AccountPresenter @Inject constructor(
} }
} }
private fun createAccountItems(items: List<Student>): List<AccountItem<*>> {
return items.groupBy { Account(it.email, it.isParent) }.map { (account, students) ->
listOf(AccountItem(account, AccountItem.ViewType.HEADER)) + students.map { student ->
AccountItem(student, AccountItem.ViewType.ITEM)
}
}.flatten()
}
private fun loadData() { private fun loadData() {
Timber.i("Loading account data started") Timber.i("Loading account data started")
disposable.add(studentRepository.getSavedStudents(false) disposable.add(studentRepository.getSavedStudents(false)
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.map { createAccountItems(it) }
.subscribe({ .subscribe({
Timber.i("Loading account result: Success") Timber.i("Loading account result: Success")
view?.updateData(it) view?.updateData(it)

View File

@ -1,13 +1,12 @@
package io.github.wulkanowy.ui.modules.account package io.github.wulkanowy.ui.modules.account
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface AccountView : BaseView { interface AccountView : BaseView {
fun initView() fun initView()
fun updateData(data: List<Student>) fun updateData(data: List<AccountItem<*>>)
fun dismissView() fun dismissView()

View File

@ -20,7 +20,6 @@ import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.ofEpochDay import org.threeten.bp.LocalDate.ofEpochDay
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject import javax.inject.Inject
class AttendancePresenter @Inject constructor( class AttendancePresenter @Inject constructor(

View File

@ -0,0 +1,11 @@
package io.github.wulkanowy.ui.modules.grade
enum class GradeAverageMode(val value: String) {
ALL_YEAR("all_year"),
ONE_SEMESTER("only_one_semester"),
BOTH_SEMESTERS("both_semesters");
companion object {
fun getByValue(value: String) = values().firstOrNull { it.value == value } ?: ONE_SEMESTER
}
}

View File

@ -1,11 +1,16 @@
package io.github.wulkanowy.ui.modules.grade package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.grade.GradeRepository import io.github.wulkanowy.data.repositories.grade.GradeRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ALL_YEAR
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.BOTH_SEMESTERS
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ONE_SEMESTER
import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.calcAverage
import io.github.wulkanowy.utils.changeModifier import io.github.wulkanowy.utils.changeModifier
import io.reactivex.Single import io.reactivex.Single
@ -17,21 +22,21 @@ class GradeAverageProvider @Inject constructor(
private val preferencesRepository: PreferencesRepository private val preferencesRepository: PreferencesRepository
) { ) {
private val plusModifier = preferencesRepository.gradePlusModifier private val plusModifier get() = preferencesRepository.gradePlusModifier
private val minusModifier = preferencesRepository.gradeMinusModifier private val minusModifier get() = preferencesRepository.gradeMinusModifier
fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean = false): Single<List<GradeDetailsWithAverage>> { fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean = false): Single<List<GradeDetailsWithAverage>> {
return semesterRepository.getSemesters(student).flatMap { semesters -> return semesterRepository.getSemesters(student).flatMap { semesters ->
when (preferencesRepository.gradeAverageMode) { when (preferencesRepository.gradeAverageMode) {
"only_one_semester" -> getSemesterDetailsWithAverage(student, semesters.single { it.semesterId == semesterId }, forceRefresh) ONE_SEMESTER -> getSemesterDetailsWithAverage(student, semesters.single { it.semesterId == semesterId }, forceRefresh)
"all_year" -> calculateWholeYearAverage(student, semesters, semesterId, forceRefresh) BOTH_SEMESTERS -> calculateBothSemestersAverage(student, semesters, semesterId, forceRefresh)
else -> throw IllegalArgumentException("Incorrect grade average mode: ${preferencesRepository.gradeAverageMode} ") ALL_YEAR -> calculateAllYearAverage(student, semesters, semesterId, forceRefresh)
} }
} }
} }
private fun calculateWholeYearAverage(student: Student, semesters: List<Semester>, semesterId: Int, forceRefresh: Boolean): Single<List<GradeDetailsWithAverage>> { private fun calculateBothSemestersAverage(student: Student, semesters: List<Semester>, semesterId: Int, forceRefresh: Boolean): Single<List<GradeDetailsWithAverage>> {
val selectedSemester = semesters.single { it.semesterId == semesterId } val selectedSemester = semesters.single { it.semesterId == semesterId }
val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 } val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
@ -42,11 +47,30 @@ class GradeAverageProvider @Inject constructor(
getSemesterDetailsWithAverage(student, firstSemester, forceRefresh).map { secondDetails -> getSemesterDetailsWithAverage(student, firstSemester, forceRefresh).map { secondDetails ->
selectedDetails.map { selected -> selectedDetails.map { selected ->
val second = secondDetails.singleOrNull { it.subject == selected.subject } val second = secondDetails.singleOrNull { it.subject == selected.subject }
selected.copy( selected.copy(average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) { val selectedGrades = selected.grades.updateModifiers(student).calcAverage()
(selected.grades + second?.grades.orEmpty()).calcAverage() (selectedGrades + (second?.grades?.updateModifiers(student)?.calcAverage() ?: selectedGrades)) / 2
} else (selected.average + (second?.average ?: selected.average)) / 2 } else (selected.average + (second?.average ?: selected.average)) / 2)
) }
}
} else Single.just(selectedDetails)
}
}
private fun calculateAllYearAverage(student: Student, semesters: List<Semester>, semesterId: Int, forceRefresh: Boolean): Single<List<GradeDetailsWithAverage>> {
val selectedSemester = semesters.single { it.semesterId == semesterId }
val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
return getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh).flatMap { selectedDetails ->
val isAnyAverage = selectedDetails.any { it.average != .0 }
if (selectedSemester != firstSemester) {
getSemesterDetailsWithAverage(student, firstSemester, forceRefresh).map { secondDetails ->
selectedDetails.map { selected ->
val second = secondDetails.singleOrNull { it.subject == selected.subject }
selected.copy(average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
(selected.grades.updateModifiers(student) + second?.grades?.updateModifiers(student).orEmpty()).calcAverage()
} else selected.average)
} }
} }
} else Single.just(selectedDetails) } else Single.just(selectedDetails)
@ -58,15 +82,12 @@ class GradeAverageProvider @Inject constructor(
val isAnyAverage = summaries.any { it.average != .0 } val isAnyAverage = summaries.any { it.average != .0 }
val allGrades = details.groupBy { it.subject } val allGrades = details.groupBy { it.subject }
summaries.map { summary -> summaries.emulateEmptySummaries(student, semester, allGrades.toList(), isAnyAverage).map { summary ->
val grades = allGrades[summary.subject].orEmpty() val grades = allGrades[summary.subject].orEmpty()
GradeDetailsWithAverage( GradeDetailsWithAverage(
subject = summary.subject, subject = summary.subject,
average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) { average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
grades.map { grades.updateModifiers(student).calcAverage()
if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier)
else it
}.calcAverage()
} else summary.average, } else summary.average,
points = summary.pointsSum, points = summary.pointsSum,
summary = summary, summary = summary,
@ -75,4 +96,30 @@ class GradeAverageProvider @Inject constructor(
} }
} }
} }
private fun List<GradeSummary>.emulateEmptySummaries(student: Student, semester: Semester, grades: List<Pair<String, List<Grade>>>, calcAverage: Boolean): List<GradeSummary> {
if (isNotEmpty() && size > grades.size) return this
return grades.mapIndexed { i, (subject, details) ->
singleOrNull { it.subject == subject }?.let { return@mapIndexed it }
GradeSummary(
studentId = student.studentId,
semesterId = semester.semesterId,
position = i,
subject = subject,
predictedGrade = "",
finalGrade = "",
proposedPoints = "",
finalPoints = "",
pointsSum = "",
average = if (calcAverage) details.updateModifiers(student).calcAverage() else .0
)
}
}
private fun List<Grade>.updateModifiers(student: Student): List<Grade> {
return if (student.loginMode == Sdk.Mode.SCRAPPER.name) {
map { it.changeModifier(plusModifier, minusModifier) }
} else this
}
} }

View File

@ -7,6 +7,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.databinding.HeaderGradeDetailsBinding import io.github.wulkanowy.databinding.HeaderGradeDetailsBinding
@ -23,7 +24,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
private var items = mutableListOf<GradeDetailsItem>() private var items = mutableListOf<GradeDetailsItem>()
private var expandedPosition = RecyclerView.NO_POSITION private var expandedPosition = NO_POSITION
private var isExpandable = false private var isExpandable = false
@ -35,7 +36,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
headers = data.filter { it.viewType == ViewType.HEADER }.toMutableList() headers = data.filter { it.viewType == ViewType.HEADER }.toMutableList()
items = if (isExpanded) headers else data.toMutableList() items = if (isExpanded) headers else data.toMutableList()
isExpandable = isExpanded isExpandable = isExpanded
expandedPosition = RecyclerView.NO_POSITION expandedPosition = NO_POSITION
} }
fun updateDetailsItem(position: Int, grade: Grade) { fun updateDetailsItem(position: Int, grade: Grade) {
@ -48,37 +49,40 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
} }
fun getHeaderItem(subject: String): GradeDetailsItem { fun getHeaderItem(subject: String): GradeDetailsItem {
if (headers.any { it.value !is GradeDetailsHeader }) {
Timber.e("Headers contains no-header items! $headers")
}
val candidates = headers.filter { (it.value as GradeDetailsHeader).subject == subject } val candidates = headers.filter { (it.value as GradeDetailsHeader).subject == subject }
if (candidates.size > 1) { if (candidates.size > 1) {
Timber.e("Header with subject $subject found ${candidates.size} times! Items: $candidates, expanded: $expandedPosition") Timber.e("Header with subject $subject found ${candidates.size} times! Expanded: $expandedPosition. Items: $candidates")
} }
return candidates.first() return candidates.first()
} }
fun updateHeaderItem(item: GradeDetailsItem) { fun updateHeaderItem(item: GradeDetailsItem) {
headers[headers.indexOf(item)] = item val headerPosition = headers.indexOf(item)
items[items.indexOf(item)] = item val itemPosition = items.indexOf(item)
notifyItemChanged(items.indexOf(item))
if (headerPosition == NO_POSITION || itemPosition == NO_POSITION) {
Timber.e("Invalid update header positions! Header: $headerPosition, item: $itemPosition")
}
headers[headerPosition] = item
items[itemPosition] = item
notifyItemChanged(itemPosition)
} }
fun collapseAll() { fun collapseAll() {
if (expandedPosition != -1) { if (expandedPosition != -1) {
refreshList(headers) refreshList(headers)
expandedPosition = RecyclerView.NO_POSITION expandedPosition = NO_POSITION
} }
} }
@Synchronized @Synchronized
private fun refreshList(newItems: List<GradeDetailsItem>) { private fun refreshList(newItems: MutableList<GradeDetailsItem>) {
val diffCallback = GradeDetailsDiffUtil(items, newItems) val diffCallback = GradeDetailsDiffUtil(items, newItems)
val diffResult = DiffUtil.calculateDiff(diffCallback) val diffResult = DiffUtil.calculateDiff(diffCallback)
items = newItems.toMutableList() items = newItems
diffResult.dispatchUpdatesTo(this) diffResult.dispatchUpdatesTo(this)
} }
@ -99,23 +103,24 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) { when (holder) {
is HeaderViewHolder -> bindHeaderViewHolder( is HeaderViewHolder -> bindHeaderViewHolder(
binding = holder.binding, holder = holder,
header = items[position].value as GradeDetailsHeader, header = items[position].value as GradeDetailsHeader,
headerPosition = headers.indexOf(items[position]), position = position
adapterPosition = position
) )
is ItemViewHolder -> bindItemViewHolder( is ItemViewHolder -> bindItemViewHolder(
binding = holder.binding, holder = holder,
grade = items[position].value as Grade, grade = items[position].value as Grade
position = holder.adapterPosition
) )
} }
} }
private fun bindHeaderViewHolder(binding: HeaderGradeDetailsBinding, header: GradeDetailsHeader, headerPosition: Int, adapterPosition: Int) { private fun bindHeaderViewHolder(holder: HeaderViewHolder, header: GradeDetailsHeader, position: Int) {
with(binding) { val headerPosition = headers.indexOf(items[position])
val adapterPosition = holder.adapterPosition
with(holder.binding) {
gradeHeaderDivider.visibility = if (adapterPosition == 0) View.GONE else View.VISIBLE gradeHeaderDivider.visibility = if (adapterPosition == 0) View.GONE else View.VISIBLE
gradeHeaderSubject.apply { with(gradeHeaderSubject) {
text = header.subject text = header.subject
maxLines = if (headerPosition == expandedPosition) 2 else 1 maxLines = if (headerPosition == expandedPosition) 2 else 1
} }
@ -130,7 +135,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
gradeHeaderContainer.setOnClickListener { gradeHeaderContainer.setOnClickListener {
expandedPosition = if (expandedPosition == adapterPosition) -1 else adapterPosition expandedPosition = if (expandedPosition == adapterPosition) -1 else adapterPosition
if (expandedPosition != RecyclerView.NO_POSITION) { if (expandedPosition != NO_POSITION) {
refreshList(headers.toMutableList().apply { refreshList(headers.toMutableList().apply {
addAll(headerPosition + 1, header.grades) addAll(headerPosition + 1, header.grades)
}) })
@ -148,8 +153,8 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun bindItemViewHolder(binding: ItemGradeDetailsBinding, grade: Grade, position: Int) { private fun bindItemViewHolder(holder: ItemViewHolder, grade: Grade) {
with(binding) { with(holder.binding) {
gradeItemValue.run { gradeItemValue.run {
text = grade.entry text = grade.entry
setBackgroundResource(grade.getBackgroundColor(colorTheme)) setBackgroundResource(grade.getBackgroundColor(colorTheme))
@ -163,7 +168,9 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
gradeItemWeight.text = "${root.context.getString(R.string.grade_weight)}: ${grade.weight}" gradeItemWeight.text = "${root.context.getString(R.string.grade_weight)}: ${grade.weight}"
gradeItemNote.visibility = if (!grade.isRead) View.VISIBLE else View.GONE gradeItemNote.visibility = if (!grade.isRead) View.VISIBLE else View.GONE
root.setOnClickListener { onClickListener(grade, position) } root.setOnClickListener {
holder.adapterPosition.let { if (it != NO_POSITION) onClickListener(grade, it) }
}
} }
} }

View File

@ -109,7 +109,17 @@ class GradeSummaryPresenter @Inject constructor(
private fun createGradeSummaryItems(items: List<GradeDetailsWithAverage>): List<GradeSummary> { private fun createGradeSummaryItems(items: List<GradeDetailsWithAverage>): List<GradeSummary> {
return items return items
.filter { !checkEmpty(it) }
.sortedBy { it.subject } .sortedBy { it.subject }
.map { it.summary.copy(average = it.average) } .map { it.summary.copy(average = it.average) }
} }
private fun checkEmpty(gradeSummary: GradeDetailsWithAverage): Boolean {
return gradeSummary.run {
summary.finalGrade.isBlank()
&& summary.predictedGrade.isBlank()
&& average == .0
&& points.isBlank()
}
}
} }

View File

@ -12,7 +12,13 @@ import javax.inject.Inject
class LoginStudentSelectAdapter @Inject constructor() : class LoginStudentSelectAdapter @Inject constructor() :
RecyclerView.Adapter<LoginStudentSelectAdapter.ItemViewHolder>() { RecyclerView.Adapter<LoginStudentSelectAdapter.ItemViewHolder>() {
private val checkedList = mutableMapOf<Int, Boolean>()
var items = emptyList<Pair<Student, Boolean>>() var items = emptyList<Pair<Student, Boolean>>()
set(value) {
field = value
checkedList.clear()
}
var onClickListener: (Student, alreadySaved: Boolean) -> Unit = { _, _ -> } var onClickListener: (Student, alreadySaved: Boolean) -> Unit = { _, _ -> }
@ -31,15 +37,21 @@ class LoginStudentSelectAdapter @Inject constructor() :
loginItemSchool.text = student.schoolName loginItemSchool.text = student.schoolName
loginItemName.isEnabled = !alreadySaved loginItemName.isEnabled = !alreadySaved
loginItemSchool.isEnabled = !alreadySaved loginItemSchool.isEnabled = !alreadySaved
loginItemCheck.isEnabled = !alreadySaved
loginItemSignedIn.visibility = if (alreadySaved) View.VISIBLE else View.GONE loginItemSignedIn.visibility = if (alreadySaved) View.VISIBLE else View.GONE
with(loginItemCheck) {
isEnabled = !alreadySaved
keyListener = null
isChecked = checkedList[position] ?: false
}
root.setOnClickListener { root.setOnClickListener {
onClickListener(student, alreadySaved) onClickListener(student, alreadySaved)
with(loginItemCheck) { with(loginItemCheck) {
if (isEnabled) { if (isEnabled) {
isChecked = !isChecked isChecked = !isChecked
checkedList[position] = isChecked
} }
} }
} }

View File

@ -22,7 +22,7 @@ class LoginStudentSelectPresenter @Inject constructor(
var students = emptyList<Student>() var students = emptyList<Student>()
private var selectedStudents = mutableListOf<Student>() private val selectedStudents = mutableListOf<Student>()
fun onAttachView(view: LoginStudentSelectView, students: Serializable?) { fun onAttachView(view: LoginStudentSelectView, students: Serializable?) {
super.onAttachView(view) super.onAttachView(view)
@ -69,6 +69,7 @@ class LoginStudentSelectPresenter @Inject constructor(
} }
private fun loadData(students: List<Student>) { private fun loadData(students: List<Student>) {
resetSelectedState()
this.students = students this.students = students
disposable.add(studentRepository.getSavedStudents() disposable.add(studentRepository.getSavedStudents()
.map { savedStudents -> .map { savedStudents ->
@ -88,6 +89,11 @@ class LoginStudentSelectPresenter @Inject constructor(
) )
} }
private fun resetSelectedState() {
selectedStudents.clear()
view?.enableSignIn(false)
}
private fun registerStudents(students: List<Student>) { private fun registerStudents(students: List<Student>) {
disposable.add(studentRepository.saveStudents(students) disposable.add(studentRepository.saveStudents(students)
.map { students.first().apply { id = it.first() } } .map { students.first().apply { id = it.first() } }

View File

@ -63,7 +63,7 @@ class MessagePreviewAdapter @Inject constructor() :
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun bindMessage(holder: MessageViewHolder, message: Message) { private fun bindMessage(holder: MessageViewHolder, message: Message) {
with(holder.binding) { with(holder.binding) {
messagePreviewSubject.text = if (message.subject.isNotBlank()) message.subject else root.context.getString(R.string.message_no_subject) messagePreviewSubject.text = message.subject.ifBlank { root.context.getString(R.string.message_no_subject) }
messagePreviewDate.text = root.context.getString(R.string.message_date, message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")) messagePreviewDate.text = root.context.getString(R.string.message_date, message.date.toFormattedString("yyyy-MM-dd HH:mm:ss"))
messagePreviewContent.text = message.content messagePreviewContent.text = message.content
messagePreviewAuthor.text = if (message.folderId == MessageFolder.SENT.id) "${root.context.getString(R.string.message_to)} ${message.recipient}" messagePreviewAuthor.text = if (message.folderId == MessageFolder.SENT.id) "${root.context.getString(R.string.message_to)} ${message.recipient}"

View File

@ -1,12 +1,20 @@
package io.github.wulkanowy.ui.modules.message.preview package io.github.wulkanowy.ui.modules.message.preview
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.print.PrintAttributes
import android.print.PrintManager
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
@ -17,6 +25,8 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.shareText
import javax.inject.Inject import javax.inject.Inject
class MessagePreviewFragment : class MessagePreviewFragment :
@ -29,18 +39,31 @@ class MessagePreviewFragment :
@Inject @Inject
lateinit var previewAdapter: MessagePreviewAdapter lateinit var previewAdapter: MessagePreviewAdapter
@Inject
lateinit var appInfo: AppInfo
private var menuReplyButton: MenuItem? = null private var menuReplyButton: MenuItem? = null
private var menuForwardButton: MenuItem? = null private var menuForwardButton: MenuItem? = null
private var menuDeleteButton: MenuItem? = null private var menuDeleteButton: MenuItem? = null
private var menuShareButton: MenuItem? = null
private var menuPrintButton: MenuItem? = null
override val titleStringId: Int override val titleStringId: Int
get() = R.string.message_title get() = R.string.message_title
override val deleteMessageSuccessString: String override val deleteMessageSuccessString: String
get() = getString(R.string.message_delete_success) get() = getString(R.string.message_delete_success)
override val messageNoSubjectString: String
get() = getString(R.string.message_no_subject)
override val printHTML: String
get() = requireContext().assets.open("message-print-page.html").bufferedReader().use { it.readText() }
companion object { companion object {
const val MESSAGE_ID_KEY = "message_id" const val MESSAGE_ID_KEY = "message_id"
@ -77,6 +100,8 @@ class MessagePreviewFragment :
menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply) menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply)
menuForwardButton = menu.findItem(R.id.messagePreviewMenuForward) menuForwardButton = menu.findItem(R.id.messagePreviewMenuForward)
menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete) menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete)
menuShareButton = menu.findItem(R.id.messagePreviewMenuShare)
menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint)
presenter.onCreateOptionsMenu() presenter.onCreateOptionsMenu()
} }
@ -85,6 +110,8 @@ class MessagePreviewFragment :
R.id.messagePreviewMenuReply -> presenter.onReply() R.id.messagePreviewMenuReply -> presenter.onReply()
R.id.messagePreviewMenuForward -> presenter.onForward() R.id.messagePreviewMenuForward -> presenter.onForward()
R.id.messagePreviewMenuDelete -> presenter.onMessageDelete() R.id.messagePreviewMenuDelete -> presenter.onMessageDelete()
R.id.messagePreviewMenuShare -> presenter.onShare()
R.id.messagePreviewMenuPrint -> presenter.onPrint()
else -> false else -> false
} }
} }
@ -108,6 +135,8 @@ class MessagePreviewFragment :
menuReplyButton?.isVisible = show menuReplyButton?.isVisible = show
menuForwardButton?.isVisible = show menuForwardButton?.isVisible = show
menuDeleteButton?.isVisible = show menuDeleteButton?.isVisible = show
menuShareButton?.isVisible = show
menuPrintButton?.isVisible = show && appInfo.systemVersion >= Build.VERSION_CODES.LOLLIPOP
} }
override fun setDeletedOptionsLabels() { override fun setDeletedOptionsLabels() {
@ -138,6 +167,38 @@ class MessagePreviewFragment :
context?.let { it.startActivity(SendMessageActivity.getStartIntent(it, message)) } context?.let { it.startActivity(SendMessageActivity.getStartIntent(it, message)) }
} }
override fun shareText(text: String, subject: String) {
context?.shareText(text, subject)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun printDocument(html: String, jobName: String) {
val webView = WebView(activity)
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest) = false
override fun onPageFinished(view: WebView, url: String) {
createWebPrintJob(view, jobName)
}
}
webView.loadDataWithBaseURL("file:///android_asset/", html, "text/HTML", "UTF-8", null)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun createWebPrintJob(webView: WebView, jobName: String) {
activity?.getSystemService<PrintManager>()?.let { printManager ->
val printAdapter = webView.createPrintDocumentAdapter(jobName)
printManager.print(
jobName,
printAdapter,
PrintAttributes.Builder().build()
)
}
}
override fun popView() { override fun popView() {
(activity as MainActivity).popView() (activity as MainActivity).popView()
} }

View File

@ -1,12 +1,17 @@
package io.github.wulkanowy.ui.modules.message.preview package io.github.wulkanowy.ui.modules.message.preview
import android.annotation.SuppressLint
import android.os.Build
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageAttachment
import io.github.wulkanowy.data.repositories.message.MessageRepository import io.github.wulkanowy.data.repositories.message.MessageRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.toFormattedString
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -15,11 +20,14 @@ class MessagePreviewPresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val messageRepository: MessageRepository, private val messageRepository: MessageRepository,
private val analytics: FirebaseAnalyticsHelper private val analytics: FirebaseAnalyticsHelper,
private var appInfo: AppInfo
) : BasePresenter<MessagePreviewView>(errorHandler, studentRepository, schedulers) { ) : BasePresenter<MessagePreviewView>(errorHandler, studentRepository, schedulers) {
var message: Message? = null var message: Message? = null
var attachments: List<MessageAttachment>? = null
private lateinit var lastError: Throwable private lateinit var lastError: Throwable
private var retryCallback: () -> Unit = {} private var retryCallback: () -> Unit = {}
@ -56,6 +64,7 @@ class MessagePreviewPresenter @Inject constructor(
.subscribe({ message -> .subscribe({ message ->
Timber.i("Loading message ${message.message.messageId} preview result: Success ") Timber.i("Loading message ${message.message.messageId} preview result: Success ")
this@MessagePreviewPresenter.message = message.message this@MessagePreviewPresenter.message = message.message
this@MessagePreviewPresenter.attachments = message.attachments
view?.apply { view?.apply {
setMessageWithAttachment(message) setMessageWithAttachment(message)
initOptions() initOptions()
@ -87,6 +96,60 @@ class MessagePreviewPresenter @Inject constructor(
} else false } else false
} }
fun onShare(): Boolean {
message?.let {
var text = "Temat: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }}\n" + when (it.sender.isNotEmpty()) {
true -> "Od: ${it.sender}\n"
false -> "Do: ${it.recipient}\n"
} + "Data: ${it.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}\n\n${it.content}"
attachments?.let { attachments ->
if (attachments.isNotEmpty()) {
text += "\n\nZałączniki:"
attachments.forEach { attachment ->
text += "\n${attachment.filename}: ${attachment.url}"
}
}
}
view?.shareText(text, "FW: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }}")
return true
}
return false
}
@SuppressLint("NewApi")
fun onPrint(): Boolean {
if (appInfo.systemVersion < Build.VERSION_CODES.LOLLIPOP) return false
message?.let {
val dateString = it.date.toFormattedString("yyyy-MM-dd HH:mm:ss")
val infoContent = "<div><h4>Data wysłania</h4>$dateString</div>" + when {
it.sender.isNotEmpty() -> "<div><h4>Od</h4>${it.sender}</div>"
else -> "<div><h4>Do</h4>${it.recipient}</div>"
}
val messageContent = "<p>${it.content}</p>"
.replace(Regex("[\\n\\r]{2,}"), "</p><p>")
.replace(Regex("[\\n\\r]"), "<br>")
val jobName = "Wiadomość " + when {
it.sender.isNotEmpty() -> "od ${it.sender}"
else -> "do ${it.recipient}"
} + " $dateString: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }} | Wulkanowy"
view?.apply {
val html = printHTML
.replace("%SUBJECT%", it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() })
.replace("%CONTENT%", messageContent)
.replace("%INFO%", infoContent)
printDocument(html, jobName)
}
return true
}
return false
}
private fun deleteMessage() { private fun deleteMessage() {
message?.let { message -> message?.let { message ->
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()

View File

@ -1,5 +1,7 @@
package io.github.wulkanowy.ui.modules.message.preview package io.github.wulkanowy.ui.modules.message.preview
import android.os.Build
import androidx.annotation.RequiresApi
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.data.db.entities.MessageWithAttachment
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
@ -8,6 +10,10 @@ interface MessagePreviewView : BaseView {
val deleteMessageSuccessString: String val deleteMessageSuccessString: String
val messageNoSubjectString: String
val printHTML: String
fun initView() fun initView()
fun setMessageWithAttachment(item: MessageWithAttachment) fun setMessageWithAttachment(item: MessageWithAttachment)
@ -34,5 +40,10 @@ interface MessagePreviewView : BaseView {
fun openMessageForward(message: Message?) fun openMessageForward(message: Message?)
fun shareText(text: String, subject: String)
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun printDocument(html: String, jobName: String)
fun popView() fun popView()
} }

View File

@ -4,9 +4,9 @@ import android.graphics.Typeface
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList import androidx.recyclerview.widget.RecyclerView.NO_POSITION
import androidx.recyclerview.widget.SortedListAdapterCallback
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.repositories.message.MessageFolder import io.github.wulkanowy.data.repositories.message.MessageFolder
@ -19,39 +19,23 @@ class MessageTabAdapter @Inject constructor() :
var onClickListener: (Message, position: Int) -> Unit = { _, _ -> } var onClickListener: (Message, position: Int) -> Unit = { _, _ -> }
private val items = SortedList(Message::class.java, object : private var items = mutableListOf<Message>()
SortedListAdapterCallback<Message>(this) {
override fun compare(item1: Message, item2: Message): Int { fun setDataItems(data: List<Message>) {
return item2.date.compareTo(item1.date) val diffResult = DiffUtil.calculateDiff(MessageTabDiffUtil(items, data))
} items = data.toMutableList()
diffResult.dispatchUpdatesTo(this)
override fun areContentsTheSame(oldItem: Message?, newItem: Message?): Boolean {
return oldItem == newItem
}
override fun areItemsTheSame(item1: Message, item2: Message): Boolean {
return item1 == item2
}
})
fun replaceAll(models: List<Message>) {
items.beginBatchedUpdates()
for (i in items.size() - 1 downTo 0) {
val model = items.get(i)
if (model !in models) {
items.remove(model)
}
}
items.addAll(models)
items.endBatchedUpdates()
} }
fun updateItem(position: Int, item: Message) { fun updateItem(position: Int, item: Message) {
items.updateItemAt(position, item) val currentItem = items[position]
items[position] = item
if (item != currentItem) {
notifyItemChanged(position)
}
} }
override fun getItemCount() = items.size() override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemMessageBinding.inflate(LayoutInflater.from(parent.context), parent, false) ItemMessageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
@ -77,9 +61,26 @@ class MessageTabAdapter @Inject constructor() :
} }
messageItemAttachmentIcon.visibility = if (item.hasAttachments) View.VISIBLE else View.GONE messageItemAttachmentIcon.visibility = if (item.hasAttachments) View.VISIBLE else View.GONE
root.setOnClickListener { onClickListener(item, holder.adapterPosition) } root.setOnClickListener {
holder.adapterPosition.let { if (it != NO_POSITION) onClickListener(item, it) }
}
} }
} }
class ItemViewHolder(val binding: ItemMessageBinding) : RecyclerView.ViewHolder(binding.root) class ItemViewHolder(val binding: ItemMessageBinding) : RecyclerView.ViewHolder(binding.root)
private class MessageTabDiffUtil(private val old: List<Message>, private val new: List<Message>) :
DiffUtil.Callback() {
override fun getOldListSize(): Int = old.size
override fun getNewListSize(): Int = new.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return old[oldItemPosition].id == new[newItemPosition].id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return old[oldItemPosition] == new[newItemPosition]
}
}
} }

View File

@ -90,7 +90,7 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
} }
override fun updateData(data: List<Message>) { override fun updateData(data: List<Message>) {
tabAdapter.replaceAll(data) tabAdapter.setDataItems(data)
} }
override fun updateItem(item: Message, position: Int) { override fun updateItem(item: Message, position: Int) {

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.message.tab package io.github.wulkanowy.ui.modules.message.tab
import android.annotation.SuppressLint
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.repositories.message.MessageFolder import io.github.wulkanowy.data.repositories.message.MessageFolder
import io.github.wulkanowy.data.repositories.message.MessageRepository import io.github.wulkanowy.data.repositories.message.MessageRepository
@ -11,8 +10,13 @@ import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import io.reactivex.subjects.PublishSubject
import me.xdrop.fuzzywuzzy.FuzzySearch
import timber.log.Timber import timber.log.Timber
import java.util.Locale
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.pow
class MessageTabPresenter @Inject constructor( class MessageTabPresenter @Inject constructor(
schedulers: SchedulersProvider, schedulers: SchedulersProvider,
@ -31,9 +35,12 @@ class MessageTabPresenter @Inject constructor(
private var messages = emptyList<Message>() private var messages = emptyList<Message>()
private val searchQuery = PublishSubject.create<String>()
fun onAttachView(view: MessageTabView, folder: MessageFolder) { fun onAttachView(view: MessageTabView, folder: MessageFolder) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()
initializeSearchStream()
errorHandler.showErrorMessage = ::showErrorViewOnError errorHandler.showErrorMessage = ::showErrorViewOnError
this.folder = folder this.folder = folder
} }
@ -76,38 +83,35 @@ class MessageTabPresenter @Inject constructor(
private fun loadData(forceRefresh: Boolean) { private fun loadData(forceRefresh: Boolean) {
Timber.i("Loading $folder message data started") Timber.i("Loading $folder message data started")
disposable.apply { disposable.add(studentRepository.getCurrentStudent()
clear() .flatMap { student ->
add(studentRepository.getCurrentStudent() semesterRepository.getCurrentSemester(student)
.flatMap { student -> .flatMap { messageRepository.getMessages(student, it, folder, forceRefresh) }
semesterRepository.getCurrentSemester(student) }
.flatMap { messageRepository.getMessages(student, it, folder, forceRefresh) } .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doFinally {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
notifyParentDataLoaded()
} }
.subscribeOn(schedulers.backgroundThread) }
.observeOn(schedulers.mainThread) .subscribe({
.doFinally { Timber.i("Loading $folder message result: Success")
view?.run { messages = it
showRefresh(false) view?.updateData(getFilteredData(lastSearchQuery))
showProgress(false) analytics.logEvent(
enableSwipe(true) "load_data",
notifyParentDataLoaded() "type" to "messages",
} "items" to it.size,
} "folder" to folder.name
.subscribe({ )
Timber.i("Loading $folder message result: Success") }) {
messages = it Timber.i("Loading $folder message result: An exception occurred")
onSearchQueryTextChange(lastSearchQuery) errorHandler.dispatch(it)
analytics.logEvent( })
"load_data",
"type" to "messages",
"items" to it.size,
"folder" to folder.name
)
}) {
Timber.i("Loading $folder message result: An exception occurred")
errorHandler.dispatch(it)
})
}
} }
private fun showErrorViewOnError(message: String, error: Throwable) { private fun showErrorViewOnError(message: String, error: Throwable) {
@ -121,25 +125,36 @@ class MessageTabPresenter @Inject constructor(
} }
} }
@SuppressLint("DefaultLocale")
fun onSearchQueryTextChange(query: String) { fun onSearchQueryTextChange(query: String) {
lastSearchQuery = query if (query != searchQuery.toString())
searchQuery.onNext(query)
}
val lowerCaseQuery = query.toLowerCase() private fun initializeSearchStream() {
val filteredList = mutableListOf<Message>() disposable.add(searchQuery
messages.forEach { .debounce(250, TimeUnit.MILLISECONDS)
if (lowerCaseQuery in it.subject.toLowerCase() || .map { query ->
lowerCaseQuery in it.sender.toLowerCase() || lastSearchQuery = query
lowerCaseQuery in it.recipient.toLowerCase() || getFilteredData(query)
lowerCaseQuery in it.date.toFormattedString()
) {
filteredList.add(it)
} }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({
Timber.d("Applying filter. Full list: ${messages.size}, filtered: ${it.size}")
updateData(it)
}) { Timber.e(it) })
}
private fun getFilteredData(query: String): List<Message> {
return if (query.trim().isEmpty()) {
messages.sortedByDescending { it.date }
} else {
messages
.map { it to calculateMatchRatio(it, query) }
.sortedByDescending { it.second }
.filter { it.second > 5000 }
.map { it.first }
} }
Timber.d("Applying filter. Full list: ${messages.size}, filtered: ${filteredList.size}")
updateData(filteredList)
} }
private fun updateData(data: List<Message>) { private fun updateData(data: List<Message>) {
@ -151,4 +166,42 @@ class MessageTabPresenter @Inject constructor(
resetListPosition() resetListPosition()
} }
} }
private fun calculateMatchRatio(message: Message, query: String): Int {
val subjectRatio = FuzzySearch.tokenSortPartialRatio(
query.toLowerCase(Locale.getDefault()),
message.subject
)
val senderOrRecipientRatio = FuzzySearch.tokenSortPartialRatio(
query.toLowerCase(Locale.getDefault()),
if (message.sender.isNotEmpty()) message.sender.toLowerCase(Locale.getDefault())
else message.recipient.toLowerCase(Locale.getDefault())
)
val dateRatio = listOf(
FuzzySearch.ratio(
query.toLowerCase(Locale.getDefault()),
message.date.toFormattedString("dd.MM").toLowerCase(Locale.getDefault())
),
FuzzySearch.ratio(
query.toLowerCase(Locale.getDefault()),
message.date.toFormattedString("dd.MM.yyyy").toLowerCase(Locale.getDefault())
),
FuzzySearch.ratio(
query.toLowerCase(Locale.getDefault()),
message.date.toFormattedString("d MMMM").toLowerCase(Locale.getDefault())
),
FuzzySearch.ratio(
query.toLowerCase(Locale.getDefault()),
message.date.toFormattedString("d MMMM yyyy").toLowerCase(Locale.getDefault())
)
).max() ?: 0
return (subjectRatio.toDouble().pow(2)
+ senderOrRecipientRatio.toDouble().pow(2)
+ dateRatio.toDouble().pow(2) * 2
).toInt()
}
} }

View File

@ -43,6 +43,7 @@ class CompletedLessonsPresenter @Inject constructor(
completedLessonsErrorHandler.showErrorMessage = ::showErrorViewOnError completedLessonsErrorHandler.showErrorMessage = ::showErrorViewOnError
completedLessonsErrorHandler.onFeatureDisabled = { completedLessonsErrorHandler.onFeatureDisabled = {
this.view?.showFeatureDisabled() this.view?.showFeatureDisabled()
this.view?.showEmpty(true)
Timber.i("Completed lessons feature disabled by school") Timber.i("Completed lessons feature disabled by school")
} }
loadData(ofEpochDay(date ?: baseDate.toEpochDay())) loadData(ofEpochDay(date ?: baseDate.toEpochDay()))

View File

@ -71,4 +71,17 @@ fun Context.openDialer(phone: String) {
startActivity(intent) startActivity(intent)
} }
fun Context.shareText(text: String, subject: String?) {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, text)
if (subject != null) {
putExtra(Intent.EXTRA_SUBJECT, subject)
}
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
startActivity(shareIntent)
}
fun Context.dpToPx(dp: Float) = dp * resources.displayMetrics.densityDpi / DENSITY_DEFAULT fun Context.dpToPx(dp: Float) = dp * resources.displayMetrics.densityDpi / DENSITY_DEFAULT

View File

@ -52,4 +52,4 @@ class LifecycleAwareVariableActivity<T : Any> : ReadWriteProperty<AppCompatActiv
@Suppress("unused") @Suppress("unused")
fun <T : Any> Fragment.lifecycleAwareVariable() = LifecycleAwareVariable<T>() fun <T : Any> Fragment.lifecycleAwareVariable() = LifecycleAwareVariable<T>()
fun <T : Any> AppCompatActivity.lifecycleAwareVariable() = LifecycleAwareVariableActivity<T>() fun <T : Any> lifecycleAwareVariable() = LifecycleAwareVariableActivity<T>()

View File

@ -23,6 +23,8 @@ fun Sdk.init(student: Student): Sdk {
certKey = student.certificateKey certKey = student.certificateKey
privateKey = student.privateKey privateKey = student.privateKey
emptyCookieJarInterceptor = true
Timber.d("Sdk in ${student.loginMode} mode reinitialized") Timber.d("Sdk in ${student.loginMode} mode reinitialized")
return this return this

View File

@ -1,8 +1,8 @@
Wersja 0.18.1 Wersja 0.19.0
- naprawiliśmy sortowanie w ocenach - naprawiliśmy pokazywanie brakujących przedmiotów na liście podsumowania ocen
- naprawilismy wiele problemów ze stabilnością - ulepszyliśmy wygląd menadżera kont
- nazwy opcji w ustawieniach nie są już ucięte - ulepszyliśmy wyszukiwarkę wiadomości
- w zadaniach domowych wyświetlają się teraz pozycje na weekend - dodaliśmy powiadomienia o proponowanych i końcowych ocenach
- wyłączyliśmy logowanie przez token (bo nie działa i nie wiadomo kiedy będzie działać) - dodaliśmy opcję udostępniania i drukowania wiadomości
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M19,8h-1L18,3L6,3v5L5,8c-1.66,0 -3,1.34 -3,3v6h4v4h12v-4h4v-6c0,-1.66 -1.34,-3 -3,-3zM8,5h8v3L8,8L8,5zM16,17v2L8,19v-4h8v2zM18,15v-2L6,13v2L4,15v-4c0,-0.55 0.45,-1 1,-1h14c0.55,0 1,0.45 1,1v4h-2z" />
<path
android:fillColor="@android:color/white"
android:pathData="M18,11.5m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92s2.92,-1.31 2.92,-2.92c0,-1.61 -1.31,-2.92 -2.92,-2.92zM18,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM6,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM18,20.02c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z" />
</vector>

View File

@ -148,7 +148,7 @@
android:hint="@string/message_content" android:hint="@string/message_content"
android:imeOptions="flagNoExtractUi" android:imeOptions="flagNoExtractUi"
android:importantForAutofill="no" android:importantForAutofill="no"
android:inputType="textMultiLine" android:inputType="textMultiLine|textCapSentences"
android:minHeight="58dp" android:minHeight="58dp"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingLeft="16dp" android:paddingLeft="16dp"

View File

@ -5,45 +5,60 @@
android:layout_width="300dp" android:layout_width="300dp"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<TextView <LinearLayout
android:id="@+id/accountDialogTitle"
android:layout_width="match_parent"
android:layout_height="64dp"
android:paddingStart="24dp"
android:paddingLeft="24dp"
android:paddingEnd="24dp"
android:paddingRight="24dp"
android:text="@string/account_title"
android:textSize="20sp"
android:textStyle="bold"
app:firstBaselineToTopHeight="40dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/accountDialogRecycler"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/accountDialogTitle" android:orientation="vertical">
android:overScrollMode="never"
tools:itemCount="3"
tools:listitem="@layout/item_account" />
<com.google.android.material.button.MaterialButton <TextView
android:id="@+id/accountDialogAdd" android:id="@+id/accountDialogTitle"
style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:paddingStart="24dp"
android:layout_below="@id/accountDialogRecycler" android:paddingLeft="24dp"
android:layout_margin="8dp" android:paddingEnd="24dp"
android:text="@string/account_add_new" android:paddingRight="24dp"
android:textColor="?android:textColorPrimary" /> android:text="@string/account_title"
android:textSize="20sp"
android:textStyle="bold"
app:firstBaselineToTopHeight="40dp" />
<com.google.android.material.button.MaterialButton <androidx.recyclerview.widget.RecyclerView
android:id="@+id/accountDialogRemove" android:id="@+id/accountDialogRecycler"
style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="0dp"
android:layout_height="wrap_content" android:layout_marginTop="8dp"
android:layout_below="@id/accountDialogRecycler" android:layout_weight="1"
android:layout_alignParentEnd="true" android:overScrollMode="never"
android:layout_margin="8dp" tools:itemCount="3"
android:text="@string/account_logout" /> tools:listitem="@layout/item_account" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/accountDialogAdd"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/account_add_new"
android:textColor="?android:textColorPrimary" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/accountDialogRemove"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/account_logout" />
</LinearLayout>
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -24,10 +24,10 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"
tools:visibility="visible"> tools:visibility="visible">
<View <View
@ -42,9 +42,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="32dp" android:layout_marginStart="32dp"
android:layout_marginLeft="32dp" android:layout_marginLeft="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
android:layout_marginRight="32dp" android:layout_marginRight="32dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:text="@string/login_contact_header" android:text="@string/login_contact_header"
@ -55,28 +55,29 @@
android:id="@+id/loginStudentSelectContactButtons" android:id="@+id/loginStudentSelectContactButtons"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"> android:layout_marginRight="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:id="@+id/loginStudentSelectContactEmail" android:id="@+id/loginStudentSelectContactEmail"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/login_contact_email" android:text="@string/login_contact_email"
app:icon="@drawable/ic_more_messages" /> app:icon="@drawable/ic_more_messages" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:id="@+id/loginStudentSelectContactDiscord" android:id="@+id/loginStudentSelectContactDiscord"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_weight="1"
android:text="@string/login_contact_discord" android:text="@string/login_contact_discord"
app:icon="@drawable/ic_about_discord" /> app:icon="@drawable/ic_about_discord" />
</LinearLayout> </LinearLayout>
@ -95,9 +96,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="32dp" android:layout_marginStart="32dp"
android:layout_marginLeft="32dp" android:layout_marginLeft="32dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
android:layout_marginRight="32dp" android:layout_marginRight="32dp"
android:layout_marginTop="32dp"
android:layout_marginBottom="32dp" android:layout_marginBottom="32dp"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:text="@string/login_select_student" android:text="@string/login_select_student"
@ -129,8 +130,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
android:layout_marginEnd="24dp" android:layout_marginEnd="24dp"
android:layout_marginRight="24dp"
android:layout_marginBottom="32dp" android:layout_marginBottom="32dp"
android:enabled="false"
android:text="@string/login_sign_in" android:text="@string/login_sign_in"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
tools:context=".ui.modules.account.AccountAdapter">
<TextView
android:id="@+id/accountHeaderEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?android:textColorPrimary"
android:textSize="14sp"
tools:text="jan@fakelog.cf" />
<TextView
android:id="@+id/accountHeaderType"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
tools:text="Konto ucznia" />
</LinearLayout>

View File

@ -3,20 +3,17 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="56dp" android:layout_height="wrap_content"
android:background="?selectableItemBackground" android:background="?selectableItemBackground"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="24dp" android:paddingVertical="8dp"
android:paddingLeft="24dp" android:paddingHorizontal="16dp"
android:paddingEnd="24dp"
android:paddingRight="24dp"
tools:context=".ui.modules.account.AccountAdapter"> tools:context=".ui.modules.account.AccountAdapter">
<ImageView <ImageView
android:id="@+id/accountItemImage" android:id="@+id/accountItemImage"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_all_account" app:srcCompat="@drawable/ic_all_account"
@ -27,7 +24,7 @@
android:id="@+id/accountItemName" android:id="@+id/accountItemName"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="20dp" android:layout_marginStart="16dp"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textSize="16sp" android:textSize="16sp"
@ -40,7 +37,7 @@
android:id="@+id/accountItemSchool" android:id="@+id/accountItemSchool"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="20dp" android:layout_marginStart="16dp"
android:layout_marginTop="3dp" android:layout_marginTop="3dp"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
@ -50,4 +47,21 @@
app:layout_constraintStart_toEndOf="@id/accountItemImage" app:layout_constraintStart_toEndOf="@id/accountItemImage"
app:layout_constraintTop_toBottomOf="@id/accountItemName" app:layout_constraintTop_toBottomOf="@id/accountItemName"
tools:text="@tools:sample/lorem/random" /> tools:text="@tools:sample/lorem/random" />
<TextView
android:id="@+id/accountItemLoginMode"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="3dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/accountItemImage"
app:layout_constraintTop_toBottomOf="@id/accountItemSchool"
tools:text="Tryb API mobilne"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -22,4 +22,18 @@
android:title="@string/message_delete" android:title="@string/message_delete"
app:iconTint="@color/material_on_surface_emphasis_medium" app:iconTint="@color/material_on_surface_emphasis_medium"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item
android:id="@+id/messagePreviewMenuShare"
android:icon="@drawable/ic_menu_message_share"
android:orderInCategory="1"
android:title="@string/message_share"
app:iconTint="@color/material_on_surface_emphasis_medium"
app:showAsAction="ifRoom" />
<item
android:id="@+id/messagePreviewMenuPrint"
android:icon="@drawable/ic_menu_message_print"
android:orderInCategory="1"
android:title="@string/message_print"
app:iconTint="@color/material_on_surface_emphasis_medium"
app:showAsAction="ifRoom" />
</menu> </menu>

View File

@ -36,7 +36,8 @@
</string-array> </string-array>
<string-array name="grade_average_mode_entries"> <string-array name="grade_average_mode_entries">
<item>Durchschnittsnote für das 2. Semester</item> <item>Durchschnittsnote für das 2. Semester</item>
<item>Durchschnitt der Bewertungen für das ganze Jahr</item> <item>Durchschnitt der Noten aus beiden Semestern</item>
<item>Durchschnitt der Noten aus dem ganzen Jahr</item>
</string-array> </string-array>
<string-array name="timetable_show_whole_class_entries"> <string-array name="timetable_show_whole_class_entries">
<item>Nicht zeigen</item> <item>Nicht zeigen</item>

View File

@ -103,10 +103,26 @@
<item quantity="one">Neue Note</item> <item quantity="one">Neue Note</item>
<item quantity="other">Neue Noten</item> <item quantity="other">Neue Noten</item>
</plurals> </plurals>
<plurals name="grade_new_items_predicted">
<item quantity="one">New predicted grade</item>
<item quantity="other">New predicted grades</item>
</plurals>
<plurals name="grade_new_items_final">
<item quantity="one">New final grade</item>
<item quantity="other">New final grades</item>
</plurals>
<plurals name="grade_notify_new_items"> <plurals name="grade_notify_new_items">
<item quantity="one">Du hast %1$d Note bekommen</item> <item quantity="one">Du hast %1$d Note bekommen</item>
<item quantity="other">Du hast %1$d Noten bekommen</item> <item quantity="other">Du hast %1$d Noten bekommen</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items_predicted">
<item quantity="one">You received %1$d predicted grade</item>
<item quantity="other">You received %1$d predicted grades</item>
</plurals>
<plurals name="grade_notify_new_items_final">
<item quantity="one">You received %1$d final grade</item>
<item quantity="other">You received %1$d final grades</item>
</plurals>
<!--Timetable--> <!--Timetable-->
<string name="timetable_lesson">Lektion</string> <string name="timetable_lesson">Lektion</string>
<string name="timetable_room">Klassenzimmer</string> <string name="timetable_room">Klassenzimmer</string>
@ -172,6 +188,8 @@
<string name="message_move_to_bin">In den Korb wandern</string> <string name="message_move_to_bin">In den Korb wandern</string>
<string name="message_delete_forever">Dauerhaft löschen</string> <string name="message_delete_forever">Dauerhaft löschen</string>
<string name="message_delete_success">Nachricht erfolgreich gelöscht</string> <string name="message_delete_success">Nachricht erfolgreich gelöscht</string>
<string name="message_share">Share</string>
<string name="message_print">Print</string>
<string name="message_subject">Thema</string> <string name="message_subject">Thema</string>
<string name="message_content">Inhalt</string> <string name="message_content">Inhalt</string>
<string name="message_send_successful">Nachricht erfolgreich gesendet</string> <string name="message_send_successful">Nachricht erfolgreich gesendet</string>
@ -214,7 +232,7 @@
<string name="lucky_number_header">Die heutige Glücksnummer ist </string> <string name="lucky_number_header">Die heutige Glücksnummer ist </string>
<string name="lucky_number_empty">Keine Information über die Glücksnummer.</string> <string name="lucky_number_empty">Keine Information über die Glücksnummer.</string>
<string name="lucky_number_notify_new_item_title">Glücksnummer für heute</string> <string name="lucky_number_notify_new_item_title">Glücksnummer für heute</string>
<string name="lucky_number_notify_new_item">Die heutige Glücksnummer ist: </string> <string name="lucky_number_notify_new_item">Die heutige Glücksnummer ist: %d</string>
<!--Mobile devices--> <!--Mobile devices-->
<string name="mobile_devices_title">Mobile Geräte</string> <string name="mobile_devices_title">Mobile Geräte</string>
<string name="mobile_devices_no_items">Keine Geräte</string> <string name="mobile_devices_no_items">Keine Geräte</string>
@ -245,6 +263,10 @@
<string name="account_logout">Abmelden</string> <string name="account_logout">Abmelden</string>
<string name="account_confirm">Wollen Sie sich von einem aktiven Studenten abmelden?</string> <string name="account_confirm">Wollen Sie sich von einem aktiven Studenten abmelden?</string>
<string name="account_logout_student">Abmeldung von Student</string> <string name="account_logout_student">Abmeldung von Student</string>
<string name="account_type_student">Student account</string>
<string name="account_type_parent">Parent account</string>
<string name="account_login_mobile_api">Mobile API mode</string>
<string name="account_login_hybrid">Hybrid mode</string>
<!--About--> <!--About-->
<string name="about_version">Version der App</string> <string name="about_version">Version der App</string>
<string name="about_contributor">Mitarbeiter</string> <string name="about_contributor">Mitarbeiter</string>
@ -270,8 +292,8 @@
<string name="logviewer_share">Logs teilen</string> <string name="logviewer_share">Logs teilen</string>
<string name="logviewer_refresh">Aktualisieren</string> <string name="logviewer_refresh">Aktualisieren</string>
<!--Error dialog--> <!--Error dialog-->
<string name="dialog_error_check_update">Check for updates</string> <string name="dialog_error_check_update">Auf Updates prüfen</string>
<string name="dialog_error_check_update_message">Before reporting a bug, check first if an update with the bug fix is available</string> <string name="dialog_error_check_update_message">Bevor Sie einen Fehler melden, prüfen Sie zuerst, ob ein Update mit der Fehlerbehebung verfügbar ist</string>
<!--Generic--> <!--Generic-->
<string name="all_content">Inhalt</string> <string name="all_content">Inhalt</string>
<string name="all_retry">Wiederhol</string> <string name="all_retry">Wiederhol</string>
@ -289,7 +311,7 @@
<string name="all_prev">Zurück</string> <string name="all_prev">Zurück</string>
<string name="all_next">Nächste</string> <string name="all_next">Nächste</string>
<string name="all_search">Suchen</string> <string name="all_search">Suchen</string>
<string name="all_search_hint">Suchen...</string> <string name="all_search_hint">Suchen</string>
<!--Timetable Widget--> <!--Timetable Widget-->
<string name="widget_timetable_no_items">Keine Lektionen</string> <string name="widget_timetable_no_items">Keine Lektionen</string>
<string name="widget_timetable_theme_title">Thema wählen</string> <string name="widget_timetable_theme_title">Thema wählen</string>

View File

@ -35,8 +35,9 @@
<item>Kolory ocen w dzienniku</item> <item>Kolory ocen w dzienniku</item>
</string-array> </string-array>
<string-array name="grade_average_mode_entries"> <string-array name="grade_average_mode_entries">
<item>Średnia ocen z 2 semestru</item> <item>Średnia ocen z drugiego semestru</item>
<item>Średnia ocen z całego roku</item> <item>Średnia średnich z obu semestrów</item>
<item>Średnia wszystkich ocen z całego roku</item>
</string-array> </string-array>
<string-array name="timetable_show_whole_class_entries"> <string-array name="timetable_show_whole_class_entries">
<item>Nie pokazuj</item> <item>Nie pokazuj</item>

View File

@ -107,12 +107,36 @@
<item quantity="many">Nowe oceny</item> <item quantity="many">Nowe oceny</item>
<item quantity="other">Nowe oceny</item> <item quantity="other">Nowe oceny</item>
</plurals> </plurals>
<plurals name="grade_new_items_predicted">
<item quantity="one">Nowa ocena przewidywana</item>
<item quantity="few">Nowe oceny przewidywane</item>
<item quantity="many">Nowe oceny przewidywane</item>
<item quantity="other">Nowe oceny przewidywane</item>
</plurals>
<plurals name="grade_new_items_final">
<item quantity="one">Nowa ocena końcowa</item>
<item quantity="few">Nowe oceny końcowe</item>
<item quantity="many">Nowe oceny końcowe</item>
<item quantity="other">Nowe oceny końcowe</item>
</plurals>
<plurals name="grade_notify_new_items"> <plurals name="grade_notify_new_items">
<item quantity="one">Masz %1$d nową ocenę</item> <item quantity="one">Masz %1$d nową ocenę</item>
<item quantity="few">Masz %1$d nowe oceny</item> <item quantity="few">Masz %1$d nowe oceny</item>
<item quantity="many">Masz %1$d nowych ocen</item> <item quantity="many">Masz %1$d nowych ocen</item>
<item quantity="other">Masz %1$d nowych ocen</item> <item quantity="other">Masz %1$d nowych ocen</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items_predicted">
<item quantity="one">Masz %1$d nową przewidywaną ocenę</item>
<item quantity="few">Masz %1$d nowe przewidywane oceny</item>
<item quantity="many">Masz %1$d nowych przewidywanych ocen</item>
<item quantity="other">Masz %1$d nowych przewidywanych ocen</item>
</plurals>
<plurals name="grade_notify_new_items_final">
<item quantity="one">Masz %1$d nową końcową ocenę</item>
<item quantity="few">Masz %1$d nowe końcowe oceny</item>
<item quantity="many">Masz %1$d nowych końcowych ocen</item>
<item quantity="other">Masz %1$d nowych końcowych ocen</item>
</plurals>
<!--Timetable--> <!--Timetable-->
<string name="timetable_lesson">Lekcja</string> <string name="timetable_lesson">Lekcja</string>
<string name="timetable_room">Sala</string> <string name="timetable_room">Sala</string>
@ -180,6 +204,8 @@
<string name="message_move_to_bin">Przenieś do kosza</string> <string name="message_move_to_bin">Przenieś do kosza</string>
<string name="message_delete_forever">Usuń trwale</string> <string name="message_delete_forever">Usuń trwale</string>
<string name="message_delete_success">Wiadomość usunięta pomyślnie</string> <string name="message_delete_success">Wiadomość usunięta pomyślnie</string>
<string name="message_share">Udostępnij</string>
<string name="message_print">Drukuj</string>
<string name="message_subject">Temat</string> <string name="message_subject">Temat</string>
<string name="message_content">Treść</string> <string name="message_content">Treść</string>
<string name="message_send_successful">Wiadomość wysłana pomyślnie</string> <string name="message_send_successful">Wiadomość wysłana pomyślnie</string>
@ -265,6 +291,10 @@
<string name="account_logout">Wyloguj</string> <string name="account_logout">Wyloguj</string>
<string name="account_confirm">Czy chcesz wylogować aktualnego ucznia?</string> <string name="account_confirm">Czy chcesz wylogować aktualnego ucznia?</string>
<string name="account_logout_student">Wylogowanie ucznia</string> <string name="account_logout_student">Wylogowanie ucznia</string>
<string name="account_type_student">Konto ucznia</string>
<string name="account_type_parent">Konto rodzica</string>
<string name="account_login_mobile_api">Tryb API mobilne</string>
<string name="account_login_hybrid">Tryb hybrydowy</string>
<!--About--> <!--About-->
<string name="about_version">Wersja aplikacji</string> <string name="about_version">Wersja aplikacji</string>
<string name="about_contributor">Twórcy</string> <string name="about_contributor">Twórcy</string>
@ -309,7 +339,7 @@
<string name="all_prev">Poprzedni</string> <string name="all_prev">Poprzedni</string>
<string name="all_next">Następny</string> <string name="all_next">Następny</string>
<string name="all_search">Szukaj</string> <string name="all_search">Szukaj</string>
<string name="all_search_hint">Szukaj...</string> <string name="all_search_hint">Szukaj</string>
<!--Timetable Widget--> <!--Timetable Widget-->
<string name="widget_timetable_no_items">Brak lekcji</string> <string name="widget_timetable_no_items">Brak lekcji</string>
<string name="widget_timetable_theme_title">Wybierz motyw</string> <string name="widget_timetable_theme_title">Wybierz motyw</string>

View File

@ -36,7 +36,8 @@
</string-array> </string-array>
<string-array name="grade_average_mode_entries"> <string-array name="grade_average_mode_entries">
<item>Средняя оценка со 2 семестра</item> <item>Средняя оценка со 2 семестра</item>
<item>Средняя оценка с целого года</item> <item>Average of grades from both semesters</item>
<item>Average of grades from the whole year</item>
</string-array> </string-array>
<string-array name="timetable_show_whole_class_entries"> <string-array name="timetable_show_whole_class_entries">
<item>Не показывать</item> <item>Не показывать</item>

View File

@ -107,12 +107,36 @@
<item quantity="many">Новые оценки</item> <item quantity="many">Новые оценки</item>
<item quantity="other">Новые оценки</item> <item quantity="other">Новые оценки</item>
</plurals> </plurals>
<plurals name="grade_new_items_predicted">
<item quantity="one">New predicted grade</item>
<item quantity="few">New predicted grades</item>
<item quantity="many">New predicted grades</item>
<item quantity="other">New predicted grades</item>
</plurals>
<plurals name="grade_new_items_final">
<item quantity="one">New final grade</item>
<item quantity="few">New final grades</item>
<item quantity="many">New final grades</item>
<item quantity="other">New final grades</item>
</plurals>
<plurals name="grade_notify_new_items"> <plurals name="grade_notify_new_items">
<item quantity="one">Вы получили %1$d оценку</item> <item quantity="one">Вы получили %1$d оценку</item>
<item quantity="few">Вы получили %1$d оценки</item> <item quantity="few">Вы получили %1$d оценки</item>
<item quantity="many">Вы получили %1$d оценок</item> <item quantity="many">Вы получили %1$d оценок</item>
<item quantity="other">Вы получили %1$d оценок</item> <item quantity="other">Вы получили %1$d оценок</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items_predicted">
<item quantity="one">You received %1$d predicted grade</item>
<item quantity="few">You received %1$d predicted grades</item>
<item quantity="many">You received %1$d predicted grades</item>
<item quantity="other">You received %1$d predicted grades</item>
</plurals>
<plurals name="grade_notify_new_items_final">
<item quantity="one">You received %1$d final grade</item>
<item quantity="few">You received %1$d final grades</item>
<item quantity="many">You received %1$d final grades</item>
<item quantity="other">You received %1$d final grades</item>
</plurals>
<!--Timetable--> <!--Timetable-->
<string name="timetable_lesson">Урок</string> <string name="timetable_lesson">Урок</string>
<string name="timetable_room">Аудитория</string> <string name="timetable_room">Аудитория</string>
@ -180,6 +204,8 @@
<string name="message_move_to_bin">Перенести в корзину</string> <string name="message_move_to_bin">Перенести в корзину</string>
<string name="message_delete_forever">Удалить навсегда</string> <string name="message_delete_forever">Удалить навсегда</string>
<string name="message_delete_success">Сообщение успешно удалено</string> <string name="message_delete_success">Сообщение успешно удалено</string>
<string name="message_share">Share</string>
<string name="message_print">Print</string>
<string name="message_subject">Тема</string> <string name="message_subject">Тема</string>
<string name="message_content">Текст</string> <string name="message_content">Текст</string>
<string name="message_send_successful">Сообщение успешно отправлено</string> <string name="message_send_successful">Сообщение успешно отправлено</string>
@ -265,6 +291,10 @@
<string name="account_logout">Выйти</string> <string name="account_logout">Выйти</string>
<string name="account_confirm">Вы точно хотите выйти из данного аккаунта?</string> <string name="account_confirm">Вы точно хотите выйти из данного аккаунта?</string>
<string name="account_logout_student">Выйти</string> <string name="account_logout_student">Выйти</string>
<string name="account_type_student">Student account</string>
<string name="account_type_parent">Parent account</string>
<string name="account_login_mobile_api">Mobile API mode</string>
<string name="account_login_hybrid">Hybrid mode</string>
<!--About--> <!--About-->
<string name="about_version">Версия приложения</string> <string name="about_version">Версия приложения</string>
<string name="about_contributor">Разработчики</string> <string name="about_contributor">Разработчики</string>
@ -290,8 +320,8 @@
<string name="logviewer_share">Поделиться логами</string> <string name="logviewer_share">Поделиться логами</string>
<string name="logviewer_refresh">Обновить</string> <string name="logviewer_refresh">Обновить</string>
<!--Error dialog--> <!--Error dialog-->
<string name="dialog_error_check_update">Check for updates</string> <string name="dialog_error_check_update">Проверить наличие обновлений</string>
<string name="dialog_error_check_update_message">Before reporting a bug, check first if an update with the bug fix is available</string> <string name="dialog_error_check_update_message">Прежде чем сообщать об ошибке, проверьте наличие обновлений</string>
<!--Generic--> <!--Generic-->
<string name="all_content">Содержание</string> <string name="all_content">Содержание</string>
<string name="all_retry">Повторить</string> <string name="all_retry">Повторить</string>
@ -309,7 +339,7 @@
<string name="all_prev">Предыдущий</string> <string name="all_prev">Предыдущий</string>
<string name="all_next">Следующий</string> <string name="all_next">Следующий</string>
<string name="all_search">Поиск</string> <string name="all_search">Поиск</string>
<string name="all_search_hint">Поиск...</string> <string name="all_search_hint">Поиск</string>
<!--Timetable Widget--> <!--Timetable Widget-->
<string name="widget_timetable_no_items">Нет уроков</string> <string name="widget_timetable_no_items">Нет уроков</string>
<string name="widget_timetable_theme_title">Выбрать тему</string> <string name="widget_timetable_theme_title">Выбрать тему</string>

View File

@ -36,7 +36,8 @@
</string-array> </string-array>
<string-array name="grade_average_mode_entries"> <string-array name="grade_average_mode_entries">
<item>Середня оцінка з 2 семестру</item> <item>Середня оцінка з 2 семестру</item>
<item>Середня оцінка за весь рік</item> <item>Average of grades from both semesters</item>
<item>Average of grades from the whole year</item>
</string-array> </string-array>
<string-array name="timetable_show_whole_class_entries"> <string-array name="timetable_show_whole_class_entries">
<item>Не показувати</item> <item>Не показувати</item>

View File

@ -107,12 +107,36 @@
<item quantity="many">Нові оцінки</item> <item quantity="many">Нові оцінки</item>
<item quantity="other">Нові оцінки</item> <item quantity="other">Нові оцінки</item>
</plurals> </plurals>
<plurals name="grade_new_items_predicted">
<item quantity="one">New predicted grade</item>
<item quantity="few">New predicted grades</item>
<item quantity="many">New predicted grades</item>
<item quantity="other">New predicted grades</item>
</plurals>
<plurals name="grade_new_items_final">
<item quantity="one">New final grade</item>
<item quantity="few">New final grades</item>
<item quantity="many">New final grades</item>
<item quantity="other">New final grades</item>
</plurals>
<plurals name="grade_notify_new_items"> <plurals name="grade_notify_new_items">
<item quantity="one">Ви отримали %1$d оцінку</item> <item quantity="one">Ви отримали %1$d оцінку</item>
<item quantity="few">Ви отримали %1$d оцінки</item> <item quantity="few">Ви отримали %1$d оцінки</item>
<item quantity="many">Ви отримали %1$d оцінок</item> <item quantity="many">Ви отримали %1$d оцінок</item>
<item quantity="other">Ви отримали %1$d оцінок</item> <item quantity="other">Ви отримали %1$d оцінок</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items_predicted">
<item quantity="one">You received %1$d predicted grade</item>
<item quantity="few">You received %1$d predicted grades</item>
<item quantity="many">You received %1$d predicted grades</item>
<item quantity="other">You received %1$d predicted grades</item>
</plurals>
<plurals name="grade_notify_new_items_final">
<item quantity="one">You received %1$d final grade</item>
<item quantity="few">You received %1$d final grades</item>
<item quantity="many">You received %1$d final grades</item>
<item quantity="other">You received %1$d final grades</item>
</plurals>
<!--Timetable--> <!--Timetable-->
<string name="timetable_lesson">Урок</string> <string name="timetable_lesson">Урок</string>
<string name="timetable_room">Аудиторія</string> <string name="timetable_room">Аудиторія</string>
@ -120,14 +144,14 @@
<string name="timetable_time">Години</string> <string name="timetable_time">Години</string>
<string name="timetable_changes">Зміни</string> <string name="timetable_changes">Зміни</string>
<string name="timetable_no_items">Брак уроків у цей день</string> <string name="timetable_no_items">Брак уроків у цей день</string>
<string name="timetable_minutes">%s min</string> <string name="timetable_minutes">%s хвилин</string>
<string name="timetable_seconds">%s sec</string> <string name="timetable_seconds">%s сек</string>
<string name="timetable_time_left">%1$s left</string> <string name="timetable_time_left">%1$s лишилося</string>
<string name="timetable_time_until">in %1$s</string> <string name="timetable_time_until">через %1$s</string>
<string name="timetable_finished">Finished</string> <string name="timetable_finished">Завершено</string>
<string name="timetable_now">Now: %s</string> <string name="timetable_now">Зараз: %s</string>
<string name="timetable_next">Next: %s</string> <string name="timetable_next">Наступний: %s</string>
<string name="timetable_later">Later: %s</string> <string name="timetable_later">Пізніше: %s</string>
<!--Completed lessons--> <!--Completed lessons-->
<string name="completed_lessons_title">Уроки, що відбулися</string> <string name="completed_lessons_title">Уроки, що відбулися</string>
<string name="completed_lessons_button">Показати уроки, що відбулися</string> <string name="completed_lessons_button">Показати уроки, що відбулися</string>
@ -180,6 +204,8 @@
<string name="message_move_to_bin">Перемістити у кошик</string> <string name="message_move_to_bin">Перемістити у кошик</string>
<string name="message_delete_forever">Видалити назавжди</string> <string name="message_delete_forever">Видалити назавжди</string>
<string name="message_delete_success">Повідомлення було успішно видалено</string> <string name="message_delete_success">Повідомлення було успішно видалено</string>
<string name="message_share">Share</string>
<string name="message_print">Print</string>
<string name="message_subject">Тема</string> <string name="message_subject">Тема</string>
<string name="message_content">Зміст</string> <string name="message_content">Зміст</string>
<string name="message_send_successful">Повідомлення було успішно відправлено</string> <string name="message_send_successful">Повідомлення було успішно відправлено</string>
@ -265,6 +291,10 @@
<string name="account_logout">Вийти</string> <string name="account_logout">Вийти</string>
<string name="account_confirm">Ви впевнені, що хочете вийти з цього аккаунту?</string> <string name="account_confirm">Ви впевнені, що хочете вийти з цього аккаунту?</string>
<string name="account_logout_student">Вийти з аккаунту учня</string> <string name="account_logout_student">Вийти з аккаунту учня</string>
<string name="account_type_student">Student account</string>
<string name="account_type_parent">Parent account</string>
<string name="account_login_mobile_api">Mobile API mode</string>
<string name="account_login_hybrid">Hybrid mode</string>
<!--About--> <!--About-->
<string name="about_version">Версія додатка</string> <string name="about_version">Версія додатка</string>
<string name="about_contributor">Розробники</string> <string name="about_contributor">Розробники</string>
@ -290,8 +320,8 @@
<string name="logviewer_share">Поділитися логами</string> <string name="logviewer_share">Поділитися логами</string>
<string name="logviewer_refresh">Оновити</string> <string name="logviewer_refresh">Оновити</string>
<!--Error dialog--> <!--Error dialog-->
<string name="dialog_error_check_update">Check for updates</string> <string name="dialog_error_check_update">Провірити наявність оновлень</string>
<string name="dialog_error_check_update_message">Before reporting a bug, check first if an update with the bug fix is available</string> <string name="dialog_error_check_update_message">Перед тим, як повідомлювати о помілці, перевірте наявність оновлень</string>
<!--Generic--> <!--Generic-->
<string name="all_content">Зміст</string> <string name="all_content">Зміст</string>
<string name="all_retry">Повторити</string> <string name="all_retry">Повторити</string>
@ -308,8 +338,8 @@
<string name="all_subject">Предмет</string> <string name="all_subject">Предмет</string>
<string name="all_prev">Попередній</string> <string name="all_prev">Попередній</string>
<string name="all_next">Наступний</string> <string name="all_next">Наступний</string>
<string name="all_search">Search</string> <string name="all_search">Пошук</string>
<string name="all_search_hint">Search...</string> <string name="all_search_hint">Пошук…</string>
<!--Timetable Widget--> <!--Timetable Widget-->
<string name="widget_timetable_no_items">Брак уроків</string> <string name="widget_timetable_no_items">Брак уроків</string>
<string name="widget_timetable_theme_title">Увібрати тему</string> <string name="widget_timetable_theme_title">Увібрати тему</string>
@ -324,17 +354,17 @@
<string name="pref_view_present">Показувати присутність у відвідуваності</string> <string name="pref_view_present">Показувати присутність у відвідуваності</string>
<string name="pref_view_app_theme">Тема додатку</string> <string name="pref_view_app_theme">Тема додатку</string>
<string name="pref_view_expand_grade">Більше оцінок</string> <string name="pref_view_expand_grade">Більше оцінок</string>
<string name="pref_view_timetable_show_timers">Mark current lesson in timetable</string> <string name="pref_view_timetable_show_timers">Позначити поточний урок у розкладі</string>
<string name="pref_view_grade_statistics_list">Show chart list in class grades</string> <string name="pref_view_grade_statistics_list">Показувати діаграми в оцінках класу</string>
<string name="pref_view_timetable_show_whole_class">Показати уроки всього класу</string> <string name="pref_view_timetable_show_whole_class">Показати уроки всього класу</string>
<string name="pref_view_grade_color_scheme">Схема кольорів оцінок</string> <string name="pref_view_grade_color_scheme">Схема кольорів оцінок</string>
<string name="pref_view_app_language">Мова додатку</string> <string name="pref_view_app_language">Мова додатку</string>
<string name="pref_notify_header">Повідомлення</string> <string name="pref_notify_header">Повідомлення</string>
<string name="pref_notify_switch">Показувати повідомлення</string> <string name="pref_notify_switch">Показувати повідомлення</string>
<string name="pref_notify_upcoming_lessons_switch">Show upcoming lesson notifications</string> <string name="pref_notify_upcoming_lessons_switch">Показувати повідомлення о наступних уроках</string>
<string name="pref_notify_fix_sync_issues">Fix synchronization &amp; notifications issues</string> <string name="pref_notify_fix_sync_issues">Виправити помилки з синхронізацією і повідомленнями</string>
<string name="pref_notify_fix_sync_issues_message">Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings.</string> <string name="pref_notify_fix_sync_issues_message">На вашому пристрої можуть бути помилки з синхронізацією і повідомленнями\n\nЩоб виправити іх, вам необхідно додати Wulkanowy в авто-старт и вимкнути оптимізацію/экономію батареї в налаштуваннях пристрою.</string>
<string name="pref_notify_fix_sync_issues_settings_button">Go to settings</string> <string name="pref_notify_fix_sync_issues_settings_button">Перейти до налаштувань</string>
<string name="pref_notify_debug_switch">Показувати дебаг-повідомлення</string> <string name="pref_notify_debug_switch">Показувати дебаг-повідомлення</string>
<string name="pref_services_header">Синхронізація</string> <string name="pref_services_header">Синхронізація</string>
<string name="pref_services_switch">Автоматична синхронізація</string> <string name="pref_services_switch">Автоматична синхронізація</string>
@ -360,7 +390,7 @@
<string name="channel_new_message">Нові повідомлення</string> <string name="channel_new_message">Нові повідомлення</string>
<string name="channel_new_notes">Нові нотатки</string> <string name="channel_new_notes">Нові нотатки</string>
<string name="channel_push">Показувати push-повідомлення</string> <string name="channel_push">Показувати push-повідомлення</string>
<string name="channel_upcoming_lessons">Upcoming lessons</string> <string name="channel_upcoming_lessons">Наступні уроки</string>
<string name="channel_debug">Дебаг</string> <string name="channel_debug">Дебаг</string>
<!--Colors--> <!--Colors-->
<string name="all_black">Чорний</string> <string name="all_black">Чорний</string>

View File

@ -88,10 +88,12 @@
<string-array name="grade_average_mode_entries"> <string-array name="grade_average_mode_entries">
<item>Average of grades only from the 2nd semester</item> <item>Average of grades only from the 2nd semester</item>
<item>Average of grades from both semesters</item>
<item>Average of grades from the whole year</item> <item>Average of grades from the whole year</item>
</string-array> </string-array>
<string-array name="grade_average_mode_values" translatable="false"> <string-array name="grade_average_mode_values" translatable="false">
<item>only_one_semester</item> <item>only_one_semester</item>
<item>both_semesters</item>
<item>all_year</item> <item>all_year</item>
</string-array> </string-array>

View File

@ -113,10 +113,26 @@
<item quantity="one">New grade</item> <item quantity="one">New grade</item>
<item quantity="other">New grades</item> <item quantity="other">New grades</item>
</plurals> </plurals>
<plurals name="grade_new_items_predicted">
<item quantity="one">New predicted grade</item>
<item quantity="other">New predicted grades</item>
</plurals>
<plurals name="grade_new_items_final">
<item quantity="one">New final grade</item>
<item quantity="other">New final grades</item>
</plurals>
<plurals name="grade_notify_new_items"> <plurals name="grade_notify_new_items">
<item quantity="one">You received %1$d grade</item> <item quantity="one">You received %1$d grade</item>
<item quantity="other">You received %1$d grades</item> <item quantity="other">You received %1$d grades</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items_predicted">
<item quantity="one">You received %1$d predicted grade</item>
<item quantity="other">You received %1$d predicted grades</item>
</plurals>
<plurals name="grade_notify_new_items_final">
<item quantity="one">You received %1$d final grade</item>
<item quantity="other">You received %1$d final grades</item>
</plurals>
<!--Timetable--> <!--Timetable-->
@ -194,6 +210,8 @@
<string name="message_move_to_bin">Move to trash</string> <string name="message_move_to_bin">Move to trash</string>
<string name="message_delete_forever">Delete permanently</string> <string name="message_delete_forever">Delete permanently</string>
<string name="message_delete_success">Message deleted successfully</string> <string name="message_delete_success">Message deleted successfully</string>
<string name="message_share">Share</string>
<string name="message_print">Print</string>
<string name="message_subject">Subject</string> <string name="message_subject">Subject</string>
<string name="message_content">Content</string> <string name="message_content">Content</string>
<string name="message_send_successful">Message sent successfully</string> <string name="message_send_successful">Message sent successfully</string>
@ -282,6 +300,10 @@
<string name="account_logout">Logout</string> <string name="account_logout">Logout</string>
<string name="account_confirm">Do you want to log out of an active student?</string> <string name="account_confirm">Do you want to log out of an active student?</string>
<string name="account_logout_student">Student logout</string> <string name="account_logout_student">Student logout</string>
<string name="account_type_student">Student account</string>
<string name="account_type_parent">Parent account</string>
<string name="account_login_mobile_api">Mobile API mode</string>
<string name="account_login_hybrid">Hybrid mode</string>
<!--About--> <!--About-->
@ -339,7 +361,7 @@
<string name="all_prev">Prev</string> <string name="all_prev">Prev</string>
<string name="all_next">Next</string> <string name="all_next">Next</string>
<string name="all_search">Search</string> <string name="all_search">Search</string>
<string name="all_search_hint">Search...</string> <string name="all_search_hint">Search</string>
<!--Timetable Widget--> <!--Timetable Widget-->

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy package io.github.wulkanowy
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
@ -72,3 +73,25 @@ fun getTimetableEntity(
teacher = "", teacher = "",
teacherOld = "" teacherOld = ""
) )
fun getMessageEntity(
messageId: Int,
content: String,
unread: Boolean
) = Message(
studentId = 1,
realId = 1,
messageId = messageId,
sender = "",
senderId = 1,
recipient = "",
subject = "",
content = content,
date = now(),
folderId = 1,
unread = unread,
unreadBy = 1,
readBy = 1,
removed = false,
hasAttachments = false
)

View File

@ -2,10 +2,10 @@ package io.github.wulkanowy.data.repositories.message
import androidx.room.EmptyResultSetException import androidx.room.EmptyResultSetException
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.data.db.entities.MessageWithAttachment
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.UnitTestInternetObservingStrategy import io.github.wulkanowy.data.repositories.UnitTestInternetObservingStrategy
import io.github.wulkanowy.getMessageEntity
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.observers.TestObserver import io.reactivex.observers.TestObserver
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@ -15,7 +15,6 @@ import org.mockito.Mock
import org.mockito.Mockito.`when` import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import org.threeten.bp.LocalDateTime.now
import java.net.UnknownHostException import java.net.UnknownHostException
class MessageRepositoryTest { class MessageRepositoryTest {
@ -44,7 +43,7 @@ class MessageRepositoryTest {
@Test @Test
fun `throw error when message is not in the db`() { fun `throw error when message is not in the db`() {
val testMessage = Message(1, 1, 1, "", 1, "", "", "", now(), 1, false, 1, 1, false, false) val testMessage = getMessageEntity(1, "", false)
`when`(local.getMessageWithAttachment(student, testMessage)).thenReturn(Single.error(EmptyResultSetException("No message in database"))) `when`(local.getMessageWithAttachment(student, testMessage)).thenReturn(Single.error(EmptyResultSetException("No message in database")))
val message = repo.getMessage(student, testMessage) val message = repo.getMessage(student, testMessage)
@ -55,7 +54,7 @@ class MessageRepositoryTest {
@Test @Test
fun `get message when content already in db`() { fun `get message when content already in db`() {
val testMessage = Message(1, 1, 123, "", 1, "", "", "Test", now(), 1, false, 1, 1, false, false) val testMessage = getMessageEntity(123, "Test", false)
val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) val messageWithAttachment = MessageWithAttachment(testMessage, emptyList())
`when`(local.getMessageWithAttachment(student, testMessage)).thenReturn(Single.just(messageWithAttachment)) `when`(local.getMessageWithAttachment(student, testMessage)).thenReturn(Single.just(messageWithAttachment))
@ -67,7 +66,7 @@ class MessageRepositoryTest {
@Test @Test
fun `get message when content in db is empty`() { fun `get message when content in db is empty`() {
val testMessage = Message(1, 1, 123, "", 1, "", "", "", now(), 1, true, 1, 1, false, false) val testMessage = getMessageEntity(123, "", true)
val testMessageWithContent = testMessage.copy(content = "Test") val testMessageWithContent = testMessage.copy(content = "Test")
val mWa = MessageWithAttachment(testMessage, emptyList()) val mWa = MessageWithAttachment(testMessage, emptyList())
@ -86,7 +85,7 @@ class MessageRepositoryTest {
@Test @Test
fun `get message when content in db is empty and there is no internet connection`() { fun `get message when content in db is empty and there is no internet connection`() {
val testMessage = Message(1, 1, 123, "", 1, "", "", "", now(), 1, false, 1, 1, false, false) val testMessage = getMessageEntity(123, "", false)
val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) val messageWithAttachment = MessageWithAttachment(testMessage, emptyList())
testObservingStrategy.isInternetConnection = false testObservingStrategy.isInternetConnection = false
@ -100,7 +99,7 @@ class MessageRepositoryTest {
@Test @Test
fun `get message when content in db is empty, unread and there is no internet connection`() { fun `get message when content in db is empty, unread and there is no internet connection`() {
val testMessage = Message(1, 1, 123, "", 1, "", "", "", now(), 1, true, 1, 1, false, false) val testMessage = getMessageEntity(123, "", true)
val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) val messageWithAttachment = MessageWithAttachment(testMessage, emptyList())
testObservingStrategy.isInternetConnection = false testObservingStrategy.isInternetConnection = false

View File

@ -1,11 +1,11 @@
package io.github.wulkanowy.ui.modules.grade package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.createSemesterEntity
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.grade.GradeRepository import io.github.wulkanowy.data.repositories.grade.GradeRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.createSemesterEntity
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.reactivex.Single import io.reactivex.Single
@ -84,117 +84,119 @@ class GradeAverageProviderTest {
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
`when`(preferencesRepository.gradeMinusModifier).thenReturn(.33)
`when`(preferencesRepository.gradePlusModifier).thenReturn(.33)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(preferencesRepository.gradeMinusModifier).thenReturn(.33)
`when`(preferencesRepository.gradePlusModifier).thenReturn(.33)
gradeAverageProvider = GradeAverageProvider(semesterRepository, gradeRepository, preferencesRepository) gradeAverageProvider = GradeAverageProvider(semesterRepository, gradeRepository, preferencesRepository)
} }
@Test @Test
fun onlyOneSemesterTest() { fun `force calc current semester average with default modifiers in scraper mode`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ONE_SEMESTER)
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) // from details and after set custom plus/minus
}
@Test
fun `force calc current semester average with custom modifiers in scraper mode`() {
val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeMinusModifier).thenReturn(.33)
`when`(preferencesRepository.gradePlusModifier).thenReturn(.33)
`when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ONE_SEMESTER)
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) // from details and after set custom plus/minus
}
@Test
fun `force calc current semester average with custom modifiers in api mode`() {
val student = student.copy(loginMode = Sdk.Mode.API.name)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) // useless in this mode
`when`(preferencesRepository.gradePlusModifier).thenReturn(.33)
`when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ONE_SEMESTER)
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) // (from details): 3.375
}
@Test
fun `force calc current semester average with custom modifiers in hybrid mode`() {
val student = student.copy(loginMode = Sdk.Mode.HYBRID.name)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) // useless in this mode
`when`(preferencesRepository.gradePlusModifier).thenReturn(.33)
`when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ONE_SEMESTER)
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) // (from details): 3.375
}
@Test
fun `calc current semester average`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ONE_SEMESTER)
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries)) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size) assertEquals(2, items.size)
assertEquals(2.5, items.single { it.subject == "Matematyka" }.average, .0) assertEquals(2.9, items.single { it.subject == "Matematyka" }.average, .0) // from summary: 2,9
assertEquals(3.0, items.single { it.subject == "Fizyka" }.average, .0) assertEquals(3.4, items.single { it.subject == "Fizyka" }.average, .0) // from details: 3,4
} }
@Test @Test
fun onlyOneSemester_gradesWithModifiers_default() { fun `force calc current semester average`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ONE_SEMESTER)
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier)) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) assertEquals(2, items.size)
assertEquals(2.5, items.single { it.subject == "Matematyka" }.average, .0) // from details: 2,5
assertEquals(3.0, items.single { it.subject == "Fizyka" }.average, .0) // from details: 3,0
} }
@Test @Test
fun onlyOneSemester_gradesWithModifiers_api() { fun `force calc full year average when current is first`() {
val student = student.copy(loginMode = Sdk.Mode.API.name)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ALL_YEAR)
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0)
}
@Test
fun onlyOneSemester_gradesWithModifiers_scrapper() {
val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0)
}
@Test
fun onlyOneSemester_gradesWithModifiers_hybrid() {
val student = student.copy(loginMode = Sdk.Mode.HYBRID.name)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0)
}
@Test
fun allYearFirstSemesterTest() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries)) `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId).blockingGet() val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId).blockingGet()
assertEquals(2, items.size) assertEquals(2, items.size)
assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) // (from summary): 3,5
assertEquals(3.5, items.single { it.subject == "Fizyka" }.average, .0) assertEquals(3.5, items.single { it.subject == "Fizyka" }.average, .0) // (from summary): 3,5
} }
@Test @Test
fun allYearSecondSemesterTest() { fun `calc both semesters average`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) `when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.BOTH_SEMESTERS)
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0)
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0)
}
@Test(expected = IllegalArgumentException::class)
fun incorrectAverageModeTest() {
`when`(preferencesRepository.gradeAverageMode).thenReturn("test_mode")
gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).blockingGet()
}
@Test
fun allYearSemester_averageFromSummary() {
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to listOf( `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to listOf(
getSummary(22, "Matematyka", 3.0), getSummary(22, "Matematyka", 3.0),
@ -208,14 +210,14 @@ class GradeAverageProviderTest {
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size) assertEquals(2, items.size)
assertEquals(3.25, items.single { it.subject == "Matematyka" }.average, .0) assertEquals(3.25, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 3,0 + 3,5 → 3,25
assertEquals(3.75, items.single { it.subject == "Fizyka" }.average, .0) assertEquals(3.75, items.single { it.subject == "Fizyka" }.average, .0) // (from summaries ↑): 3,5 + 4,0 → 3,75
} }
@Test @Test
fun onlyOneSemester_averageFromSummary_forceCalc() { fun `force calc full year average`() {
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ALL_YEAR)
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries)) `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to listOf( `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to listOf(
getSummary(22, "Matematyka", 1.1), getSummary(22, "Matematyka", 1.1),
@ -225,18 +227,228 @@ class GradeAverageProviderTest {
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size) assertEquals(2, items.size)
assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25
} }
private fun getGrade(semesterId: Int, subject: String, value: Double, modifier: Double = 0.0): Grade { @Test
fun `calc both semesters average when no summaries`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.BOTH_SEMESTERS)
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to emptyList()))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to emptyList()))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25
}
@Test
fun `force calc full year average when no summaries`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ALL_YEAR)
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to emptyList()))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to emptyList()))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25
}
@Test
fun `calc both semesters average when missing summaries in both semesters`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.BOTH_SEMESTERS)
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to listOf(
getSummary(22, "Matematyka", 4.0)
)))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to listOf(
getSummary(23, "Matematyka", 3.0)
)))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 4,0 + 3,0 → 3,5
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25
}
@Test
fun `calc both semesters average when missing summary in second semester`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.BOTH_SEMESTERS)
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries.dropLast(1)))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.4, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries): 3,9 + 2,9 → 3,4
assertEquals(3.05, items.single { it.subject == "Fizyka" }.average, .0) // 3,1 (from summary) + 3,0 (from details) → 3,05
}
@Test
fun `calc both semesters average when missing summary in first semester`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.BOTH_SEMESTERS)
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries.dropLast(1)))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.4, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries): 3,9 + 2,9 → 3,4
assertEquals(3.45, items.single { it.subject == "Fizyka" }.average, .0) // 3,5 (from details) + 3,4 (from summary) → 3,45
}
@Test
fun `force calc full year average when missing summary in first semester`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ALL_YEAR)
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries.dropLast(1)))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25
}
@Test
fun `force calc both semesters average with different average from all grades and from two semesters`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.BOTH_SEMESTERS)
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(listOf(
getGrade(22, "Fizyka", 5.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0),
getGrade(22, "Fizyka", 5.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0)
) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(listOf(
getGrade(23, "Fizyka", 5.0, weight = 1.0),
getGrade(23, "Fizyka", 5.0, weight = 2.0),
getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0)
) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(5.2296, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,732 → 5.229636363636364
}
@Test
fun `force calc full year average with different average from all grades and from two semesters`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ALL_YEAR)
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(listOf(
getGrade(22, "Fizyka", 5.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0),
getGrade(22, "Fizyka", 5.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0)
) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(listOf(
getGrade(23, "Fizyka", 5.0, weight = 1.0),
getGrade(23, "Fizyka", 5.0, weight = 2.0),
getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0)
) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(5.5429, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,732 → .average()
}
@Test
fun `force calc both semesters average with different average from all grades and from two semesters with custom modifiers`() {
val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeMinusModifier).thenReturn(.33)
`when`(preferencesRepository.gradePlusModifier).thenReturn(.5)
`when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.BOTH_SEMESTERS)
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(listOf(
getGrade(22, "Fizyka", 5.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0),
getGrade(22, "Fizyka", 5.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0)
) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(listOf(
getGrade(23, "Fizyka", 5.0, weight = 1.0),
getGrade(23, "Fizyka", 5.0, weight = 2.0),
getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0)
) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(5.2636, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,8 → 5.26363636
}
@Test
fun `force calc full year average with different average from all grades and from two semesters with custom modifiers`() {
val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeMinusModifier).thenReturn(.33)
`when`(preferencesRepository.gradePlusModifier).thenReturn(.5)
`when`(preferencesRepository.gradeAverageMode).thenReturn(GradeAverageMode.ALL_YEAR)
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(listOf(
getGrade(22, "Fizyka", 5.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0),
getGrade(22, "Fizyka", 5.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0)
) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0))))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(listOf(
getGrade(23, "Fizyka", 5.0, weight = 1.0),
getGrade(23, "Fizyka", 5.0, weight = 2.0),
getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0)
) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0))))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(5.5555, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,8 → .average()
}
private fun getGrade(semesterId: Int, subject: String, value: Double, modifier: Double = 0.0, weight: Double = 1.0): Grade {
return Grade( return Grade(
studentId = 101, studentId = 101,
semesterId = semesterId, semesterId = semesterId,
subject = subject, subject = subject,
value = value, value = value,
modifier = modifier, modifier = modifier,
weightValue = 1.0, weightValue = weight,
teacher = "", teacher = "",
date = now(), date = now(),
weight = "", weight = "",

View File

@ -1,6 +1,6 @@
buildscript { buildscript {
ext.kotlin_version = '1.3.72' ext.kotlin_version = '1.3.72'
ext.about_libraries = '8.1.3' ext.about_libraries = '8.2.0'
repositories { repositories {
mavenCentral() mavenCentral()
google() google()
@ -9,11 +9,11 @@ buildscript {
} }
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools.build:gradle:3.6.3' classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.google.gms:google-services:4.3.3' classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1'
classpath "com.github.triplet.gradle:play-publisher:2.7.5" classpath "com.github.triplet.gradle:play-publisher:2.7.5"
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0"
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${about_libraries}" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${about_libraries}"
} }