Compare commits
62 Commits
Author | SHA1 | Date | |
---|---|---|---|
9f87b92937 | |||
c13f12f729 | |||
dfe7981e7f | |||
6e1ddb482e | |||
924bcb0d64 | |||
a6682c9b73 | |||
a529836937 | |||
a05da2656a | |||
30af77614e | |||
eedaa63771 | |||
f151f7bd62 | |||
00943717a2 | |||
8cce81585a | |||
5529ffcf73 | |||
7e6f892e23 | |||
d3a6ea5acf | |||
674a78b661 | |||
5c84c8d5b1 | |||
522a36d670 | |||
2d0cfc3e8e | |||
4b6b722f87 | |||
419675066f | |||
191b1ad022 | |||
792e44a9d0 | |||
ff5a47b0df | |||
7bf0acb703 | |||
ba5dbf90d8 | |||
54f41aaa63 | |||
1db42210e8 | |||
fb554a4a3b | |||
d8d13c73fb | |||
5c0160a24d | |||
ab7d30c995 | |||
1cfa1f15c0 | |||
2149a4db9f | |||
df57d16d21 | |||
2ff031005e | |||
b9ab85ee55 | |||
064998129e | |||
4044cdd9a5 | |||
1ee10a5902 | |||
27b1d076c7 | |||
c8b32fdb3b | |||
0b4434fdb6 | |||
699fbff082 | |||
4c295f2ab4 | |||
dcbaa170db | |||
c71b533645 | |||
63f2576ff1 | |||
b744a4182b | |||
0c4364609b | |||
3308d7fe6f | |||
2cdde78c54 | |||
428b599be0 | |||
3541ab81b8 | |||
7fa14e5077 | |||
cec1068f2e | |||
f737018548 | |||
9c01316178 | |||
c3a6f8253a | |||
d558c4db66 | |||
722886aaf2 |
@ -14,7 +14,7 @@ cache:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- 0.18.0
|
||||
- 0.19.0
|
||||
|
||||
android:
|
||||
licenses:
|
||||
|
@ -17,9 +17,10 @@ android {
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 17
|
||||
targetSdkVersion 29
|
||||
versionCode 59
|
||||
versionName "0.18.0"
|
||||
versionCode 63
|
||||
versionName "0.19.0"
|
||||
multiDexEnabled true
|
||||
resValue "string", "app_name", "Wulkanowy"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
manifestPlaceholders = [
|
||||
@ -56,6 +57,7 @@ android {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
debug {
|
||||
resValue "string", "app_name", "Wulkanowy DEV " + defaultConfig.versionCode
|
||||
applicationIdSuffix ".dev"
|
||||
versionNameSuffix "-dev"
|
||||
testCoverageEnabled = project.hasProperty('coverage')
|
||||
@ -75,8 +77,8 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
viewBinding {
|
||||
enabled = true
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
@ -112,7 +114,7 @@ play {
|
||||
ext {
|
||||
work_manager = "2.3.4"
|
||||
room = "2.2.5"
|
||||
dagger = "2.27"
|
||||
dagger = "2.28"
|
||||
chucker = "3.2.0"
|
||||
mockk = "1.9.2"
|
||||
}
|
||||
@ -122,14 +124,14 @@ configurations.all {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "io.github.wulkanowy:sdk:0.18.0"
|
||||
implementation "io.github.wulkanowy:sdk:0.19.0"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "androidx.core:core-ktx:1.2.0"
|
||||
implementation "androidx.activity:activity-ktx:1.1.0"
|
||||
implementation "androidx.appcompat:appcompat:1.2.0-rc01"
|
||||
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.multidex:multidex:2.0.1"
|
||||
|
||||
@ -140,7 +142,7 @@ dependencies {
|
||||
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout: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 "me.zhanghai.android.materialprogressbar:library:1.6.1"
|
||||
|
||||
@ -178,12 +180,13 @@ dependencies {
|
||||
implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
|
||||
implementation "io.coil-kt:coil:0.11.0"
|
||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
|
||||
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
||||
|
||||
playImplementation 'com.google.firebase:firebase-analytics:17.4.1'
|
||||
playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.6'
|
||||
playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.6"
|
||||
playImplementation 'com.google.firebase:firebase-messaging:20.1.7'
|
||||
playImplementation 'com.google.firebase:firebase-crashlytics:17.0.0'
|
||||
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-ktx:19.0.7"
|
||||
playImplementation 'com.google.firebase:firebase-messaging:20.2.0'
|
||||
playImplementation 'com.google.firebase:firebase-crashlytics:17.0.1'
|
||||
playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
|
||||
|
||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
||||
|
@ -1741,4 +1741,4 @@
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd101f5a26a024f62e6fee161e421b882')"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1768
app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json
Normal file
1768
app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -120,23 +120,23 @@ class Migration13Test : AbstractMigrationTest() {
|
||||
assertEquals(2, first.diaryId)
|
||||
}
|
||||
|
||||
getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let {
|
||||
assertTrue { it.single { it.second }.second }
|
||||
assertEquals(1970, it[0].first.schoolYear)
|
||||
assertEquals(of(1970, 1, 1), it[0].first.end)
|
||||
assertEquals(of(1970, 1, 1), it[0].first.start)
|
||||
assertFalse(it[0].second)
|
||||
assertFalse(it[1].second)
|
||||
assertFalse(it[2].second)
|
||||
assertTrue(it[3].second)
|
||||
getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let { semesters ->
|
||||
assertTrue { semesters.single { it.second }.second }
|
||||
assertEquals(1970, semesters[0].first.schoolYear)
|
||||
assertEquals(of(1970, 1, 1), semesters[0].first.end)
|
||||
assertEquals(of(1970, 1, 1), semesters[0].first.start)
|
||||
assertFalse(semesters[0].second)
|
||||
assertFalse(semesters[1].second)
|
||||
assertFalse(semesters[2].second)
|
||||
assertTrue(semesters[3].second)
|
||||
}
|
||||
|
||||
getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let {
|
||||
assertTrue { it.single { it.second }.second }
|
||||
assertFalse(it[0].second)
|
||||
assertFalse(it[1].second)
|
||||
assertFalse(it[2].second)
|
||||
assertTrue(it[3].second)
|
||||
getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let { semesters ->
|
||||
assertTrue { semesters.single { it.second }.second }
|
||||
assertFalse(semesters[0].second)
|
||||
assertFalse(semesters[1].second)
|
||||
assertFalse(semesters[2].second)
|
||||
assertTrue(semesters[3].second)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.threeten.bp.LocalDate
|
||||
import org.threeten.bp.LocalDate.now
|
||||
import org.threeten.bp.LocalDate.of
|
||||
import kotlin.test.assertEquals
|
||||
@ -35,9 +36,18 @@ class AttendanceLocalTest {
|
||||
@Test
|
||||
fun saveAndReadTest() {
|
||||
attendanceLocal.saveAttendance(listOf(
|
||||
Attendance(1, 2, 3, of(2018, 9, 10), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name),
|
||||
Attendance(1, 2, 3, of(2018, 9, 14), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.WAITING.name),
|
||||
Attendance(1, 2, 3, of(2018, 9, 17), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name)
|
||||
getAttendanceEntity(
|
||||
of(2018, 9, 10),
|
||||
SentExcuseStatus.ACCEPTED
|
||||
),
|
||||
getAttendanceEntity(
|
||||
of(2018, 9, 14),
|
||||
SentExcuseStatus.WAITING
|
||||
),
|
||||
getAttendanceEntity(
|
||||
of(2018, 9, 17),
|
||||
SentExcuseStatus.ACCEPTED
|
||||
)
|
||||
))
|
||||
|
||||
val attendance = attendanceLocal
|
||||
@ -50,4 +60,25 @@ class AttendanceLocalTest {
|
||||
assertEquals(attendance[0].date, of(2018, 9, 10))
|
||||
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
|
||||
)
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.github.wulkanowy.data.db.AppDatabase
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
|
@ -1,3 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">Wulkanowy DEV</string>
|
||||
</resources>
|
@ -2,11 +2,8 @@
|
||||
|
||||
package io.github.wulkanowy.utils
|
||||
|
||||
import android.content.Context
|
||||
import timber.log.Timber
|
||||
|
||||
fun initCrashlytics(context: Context, appInfo: AppInfo) {}
|
||||
|
||||
open class TimberTreeNoOp : Timber.Tree() {
|
||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {}
|
||||
}
|
||||
|
@ -1,13 +1,18 @@
|
||||
package io.github.wulkanowy.utils
|
||||
|
||||
import android.app.Activity
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
class FirebaseAnalyticsHelper @Inject constructor() {
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun logEvent(name: String, vararg params: Pair<String, Any?>) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
fun setCurrentScreen(activity: Activity, name: String?) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
94
app/src/main/assets/message-print-page.html
Normal file
94
app/src/main/assets/message-print-page.html
Normal 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>
|
74
app/src/main/assets/wulkanowy-logo-black.svg
Normal file
74
app/src/main/assets/wulkanowy-logo-black.svg
Normal 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 |
@ -1,17 +1,15 @@
|
||||
package io.github.wulkanowy.data
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.AssetManager
|
||||
import android.content.res.Resources
|
||||
import androidx.core.content.getSystemService
|
||||
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.ChuckerInterceptor
|
||||
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.Provides
|
||||
import io.github.wulkanowy.data.db.AppDatabase
|
||||
|
@ -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.Migration24
|
||||
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.Migration4
|
||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||
@ -110,7 +111,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 25
|
||||
const val VERSION_SCHEMA = 26
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
|
||||
return arrayOf(
|
||||
@ -137,7 +138,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration22(),
|
||||
Migration23(),
|
||||
Migration24(),
|
||||
Migration25()
|
||||
Migration25(),
|
||||
Migration26()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.entities
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import org.threeten.bp.LocalDateTime
|
||||
|
||||
@Entity(tableName = "GradesSummary")
|
||||
data class GradeSummary(
|
||||
@ -36,4 +37,16 @@ data class GradeSummary(
|
||||
) {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
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()
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class AppCreatorRepository @Inject constructor(private val assets: AssetManager) {
|
||||
fun getAppCreators(): Single<List<Contributor>> {
|
||||
return Single.fromCallable<List<Contributor>> {
|
||||
return Single.fromCallable {
|
||||
Gson().fromJson(
|
||||
assets.open("contributors.json").bufferedReader().use { it.readText() },
|
||||
Array<Contributor>::class.java
|
||||
|
@ -5,7 +5,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.utils.friday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import io.reactivex.Single
|
||||
@ -22,19 +22,19 @@ class AttendanceRepository @Inject constructor(
|
||||
) {
|
||||
|
||||
fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean): Single<List<Attendance>> {
|
||||
return local.getAttendance(semester, start.monday, end.friday).filter { !forceRefresh }
|
||||
return local.getAttendance(semester, start.monday, end.sunday).filter { !forceRefresh }
|
||||
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap {
|
||||
if (it) remote.getAttendance(student, semester, start.monday, end.friday)
|
||||
if (it) remote.getAttendance(student, semester, start.monday, end.sunday)
|
||||
else Single.error(UnknownHostException())
|
||||
}.flatMap { newAttendance ->
|
||||
local.getAttendance(semester, start.monday, end.friday)
|
||||
local.getAttendance(semester, start.monday, end.sunday)
|
||||
.toSingle(emptyList())
|
||||
.doOnSuccess { oldAttendance ->
|
||||
local.deleteAttendance(oldAttendance.uniqueSubtract(newAttendance))
|
||||
local.saveAttendance(newAttendance.uniqueSubtract(oldAttendance))
|
||||
}
|
||||
}.flatMap {
|
||||
local.getAttendance(semester, start.monday, end.friday)
|
||||
local.getAttendance(semester, start.monday, end.sunday)
|
||||
.toSingle(emptyList())
|
||||
}).map { list -> list.filter { it.date in start..end } }
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter
|
||||
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.utils.friday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import io.reactivex.Single
|
||||
@ -22,20 +22,20 @@ class CompletedLessonsRepository @Inject constructor(
|
||||
) {
|
||||
|
||||
fun getCompletedLessons(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single<List<CompletedLesson>> {
|
||||
return local.getCompletedLessons(semester, start.monday, end.friday).filter { !forceRefresh }
|
||||
return local.getCompletedLessons(semester, start.monday, end.sunday).filter { !forceRefresh }
|
||||
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
|
||||
.flatMap {
|
||||
if (it) remote.getCompletedLessons(student, semester, start.monday, end.friday)
|
||||
if (it) remote.getCompletedLessons(student, semester, start.monday, end.sunday)
|
||||
else Single.error(UnknownHostException())
|
||||
}.flatMap { new ->
|
||||
local.getCompletedLessons(semester, start.monday, end.friday)
|
||||
local.getCompletedLessons(semester, start.monday, end.sunday)
|
||||
.toSingle(emptyList())
|
||||
.doOnSuccess { old ->
|
||||
local.deleteCompleteLessons(old.uniqueSubtract(new))
|
||||
local.saveCompletedLessons(new.uniqueSubtract(old))
|
||||
}
|
||||
}.flatMap {
|
||||
local.getCompletedLessons(semester, start.monday, end.friday)
|
||||
local.getCompletedLessons(semester, start.monday, end.sunday)
|
||||
.toSingle(emptyList())
|
||||
}).map { list -> list.filter { it.date in start..end } }
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ class ExamLocal @Inject constructor(private val examDb: ExamDao) {
|
||||
|
||||
fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Exam>> {
|
||||
return examDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate)
|
||||
.filter { !it.isEmpty() }
|
||||
.filter { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
fun saveExams(exams: List<Exam>) {
|
||||
|
@ -5,7 +5,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.utils.friday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import io.reactivex.Single
|
||||
@ -22,20 +22,20 @@ class ExamRepository @Inject constructor(
|
||||
) {
|
||||
|
||||
fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single<List<Exam>> {
|
||||
return local.getExams(semester, start.monday, end.friday).filter { !forceRefresh }
|
||||
return local.getExams(semester, start.monday, end.sunday).filter { !forceRefresh }
|
||||
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
|
||||
.flatMap {
|
||||
if (it) remote.getExams(student, semester, start.monday, end.friday)
|
||||
if (it) remote.getExams(student, semester, start.monday, end.sunday)
|
||||
else Single.error(UnknownHostException())
|
||||
}.flatMap { new ->
|
||||
local.getExams(semester, start.monday, end.friday)
|
||||
local.getExams(semester, start.monday, end.sunday)
|
||||
.toSingle(emptyList())
|
||||
.doOnSuccess { old ->
|
||||
local.deleteExams(old.uniqueSubtract(new))
|
||||
local.saveExams(new.uniqueSubtract(old))
|
||||
}
|
||||
}.flatMap {
|
||||
local.getExams(semester, start.monday, end.friday)
|
||||
local.getExams(semester, start.monday, end.sunday)
|
||||
.toSingle(emptyList())
|
||||
}).map { list -> list.filter { it.date in start..end } }
|
||||
}
|
||||
|
@ -27,6 +27,10 @@ class GradeLocal @Inject constructor(
|
||||
gradeDb.updateAll(grades)
|
||||
}
|
||||
|
||||
fun updateGradesSummary(gradesSummary: List<GradeSummary>) {
|
||||
gradeSummaryDb.updateAll(gradesSummary)
|
||||
}
|
||||
|
||||
fun getGradesDetails(semester: Semester): Maybe<List<Grade>> {
|
||||
return gradeDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() }
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Single
|
||||
import org.threeten.bp.LocalDateTime
|
||||
import java.net.UnknownHostException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -43,7 +44,31 @@ class GradeRepository @Inject constructor(
|
||||
local.getGradesSummary(semester).toSingle(emptyList())
|
||||
.doOnSuccess { old ->
|
||||
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 {
|
||||
@ -63,6 +88,14 @@ class GradeRepository @Inject constructor(
|
||||
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 {
|
||||
return Completable.fromCallable { local.updateGrades(listOf(grade)) }
|
||||
}
|
||||
@ -70,4 +103,8 @@ class GradeRepository @Inject constructor(
|
||||
fun updateGrades(grades: List<Grade>): Completable {
|
||||
return Completable.fromCallable { local.updateGrades(grades) }
|
||||
}
|
||||
|
||||
fun updateGradesSummary(gradesSummary: List<GradeSummary>): Completable {
|
||||
return Completable.fromCallable { local.updateGradesSummary(gradesSummary) }
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter
|
||||
import io.github.wulkanowy.data.db.entities.Homework
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.utils.friday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import io.reactivex.Completable
|
||||
@ -23,7 +23,7 @@ class HomeworkRepository @Inject constructor(
|
||||
) {
|
||||
|
||||
fun getHomework(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single<List<Homework>> {
|
||||
return Single.fromCallable { start.monday to end.friday }.flatMap { (monday, friday) ->
|
||||
return Single.fromCallable { start.monday to end.sunday }.flatMap { (monday, friday) ->
|
||||
local.getHomework(semester, monday, friday).filter { !forceRefresh }
|
||||
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
|
||||
.flatMap {
|
||||
|
@ -75,6 +75,6 @@ class MessageRemote @Inject constructor(private val sdk: Sdk) {
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.preferences
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -17,8 +18,8 @@ class PreferencesRepository @Inject constructor(
|
||||
val isShowPresent: Boolean
|
||||
get() = getBoolean(R.string.pref_key_attendance_present, R.bool.pref_default_attendance_present)
|
||||
|
||||
val gradeAverageMode: String
|
||||
get() = getString(R.string.pref_key_grade_average_mode, R.string.pref_default_grade_average_mode)
|
||||
val gradeAverageMode: GradeAverageMode
|
||||
get() = GradeAverageMode.getByValue(getString(R.string.pref_key_grade_average_mode, R.string.pref_default_grade_average_mode))
|
||||
|
||||
val gradeAverageForceCalc: Boolean
|
||||
get() = getBoolean(R.string.pref_key_grade_average_force_calc, R.bool.pref_default_grade_average_force_calc)
|
||||
|
@ -12,7 +12,7 @@ import javax.inject.Singleton
|
||||
class RecipientLocal @Inject constructor(private val recipientDb: RecipientDao) {
|
||||
|
||||
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> {
|
||||
|
@ -11,7 +11,7 @@ import javax.inject.Singleton
|
||||
class ReportingUnitLocal @Inject constructor(private val reportingUnitDb: ReportingUnitDao) {
|
||||
|
||||
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> {
|
||||
|
@ -14,7 +14,7 @@ class StudentRemote @Inject constructor(private val sdk: Sdk) {
|
||||
private fun mapStudents(students: List<SdkStudent>, email: String, password: String): List<Student> {
|
||||
return students.map { student ->
|
||||
Student(
|
||||
email = email,
|
||||
email = email.ifBlank { student.email },
|
||||
password = password,
|
||||
isParent = student.isParent,
|
||||
symbol = student.symbol,
|
||||
@ -39,7 +39,7 @@ class StudentRemote @Inject constructor(private val sdk: Sdk) {
|
||||
}
|
||||
|
||||
fun getStudentsMobileApi(token: String, pin: String, symbol: String): Single<List<Student>> {
|
||||
return sdk.getStudentsFromMobileApi(token, pin, symbol).map { mapStudents(it, "", "") }
|
||||
return sdk.getStudentsFromMobileApi(token, pin, symbol, "").map { mapStudents(it, "", "") }
|
||||
}
|
||||
|
||||
fun getStudentsScrapper(email: String, password: String, scrapperBaseUrl: String, symbol: String): Single<List<Student>> {
|
||||
@ -47,6 +47,6 @@ class StudentRemote @Inject constructor(private val sdk: Sdk) {
|
||||
}
|
||||
|
||||
fun getStudentsHybrid(email: String, password: String, scrapperBaseUrl: String, symbol: String): Single<List<Student>> {
|
||||
return sdk.getStudentsHybrid(email, password, scrapperBaseUrl, symbol).map { mapStudents(it, email, password) }
|
||||
return sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).map { mapStudents(it, email, password) }
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
|
||||
import io.github.wulkanowy.utils.friday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import io.reactivex.Single
|
||||
@ -24,13 +24,13 @@ class TimetableRepository @Inject constructor(
|
||||
) {
|
||||
|
||||
fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single<List<Timetable>> {
|
||||
return Single.fromCallable { start.monday to end.friday }.flatMap { (monday, friday) ->
|
||||
local.getTimetable(semester, monday, friday).filter { !forceRefresh }
|
||||
return Single.fromCallable { start.monday to end.sunday }.flatMap { (monday, sunday) ->
|
||||
local.getTimetable(semester, monday, sunday).filter { !forceRefresh }
|
||||
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap {
|
||||
if (it) remote.getTimetable(student, semester, monday, friday)
|
||||
if (it) remote.getTimetable(student, semester, monday, sunday)
|
||||
else Single.error(UnknownHostException())
|
||||
}.flatMap { new ->
|
||||
local.getTimetable(semester, monday, friday)
|
||||
local.getTimetable(semester, monday, sunday)
|
||||
.toSingle(emptyList())
|
||||
.doOnSuccess { old ->
|
||||
local.deleteTimetable(old.uniqueSubtract(new).also { schedulerHelper.cancelScheduled(it) })
|
||||
@ -46,7 +46,7 @@ class TimetableRepository @Inject constructor(
|
||||
})
|
||||
}
|
||||
}.flatMap {
|
||||
local.getTimetable(semester, monday, friday).toSingle(emptyList())
|
||||
local.getTimetable(semester, monday, sunday).toSingle(emptyList())
|
||||
}).map { list -> list.filter { it.date in start..end }.also { schedulerHelper.scheduleNotifications(it, student) } }
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package io.github.wulkanowy.services.alarm
|
||||
import android.app.AlarmManager
|
||||
import android.app.AlarmManager.RTC_WAKEUP
|
||||
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.Intent
|
||||
import androidx.core.app.AlarmManagerCompat
|
||||
@ -55,7 +55,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
|
||||
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
|
||||
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)
|
||||
@ -102,7 +102,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||
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")
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class DebugChannel @Inject constructor(
|
||||
}
|
||||
|
||||
override fun create() {
|
||||
if (appInfo.isDebug) return
|
||||
if (!appInfo.isDebug) return
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_debug), IMPORTANCE_DEFAULT)
|
||||
.apply {
|
||||
|
@ -3,7 +3,7 @@ package io.github.wulkanowy.services.sync.works
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository
|
||||
import io.github.wulkanowy.utils.friday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.reactivex.Completable
|
||||
import org.threeten.bp.LocalDate.now
|
||||
@ -12,7 +12,7 @@ import javax.inject.Inject
|
||||
class AttendanceWork @Inject constructor(private val attendanceRepository: AttendanceRepository) : Work {
|
||||
|
||||
override fun create(student: Student, semester: Semester): Completable {
|
||||
return attendanceRepository.getAttendance(student, semester, now().monday, now().friday, true)
|
||||
return attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true)
|
||||
.ignoreElement()
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package io.github.wulkanowy.services.sync.works
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.repositories.completedlessons.CompletedLessonsRepository
|
||||
import io.github.wulkanowy.utils.friday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.reactivex.Completable
|
||||
import org.threeten.bp.LocalDate.now
|
||||
@ -14,7 +14,7 @@ class CompletedLessonWork @Inject constructor(
|
||||
) : Work {
|
||||
|
||||
override fun create(student: Student, semester: Semester): Completable {
|
||||
return completedLessonsRepository.getCompletedLessons(student, semester, now().monday, now().friday, true)
|
||||
return completedLessonsRepository.getCompletedLessons(student, semester, now().monday, now().sunday, true)
|
||||
.ignoreElement()
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package io.github.wulkanowy.services.sync.works
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.repositories.exam.ExamRepository
|
||||
import io.github.wulkanowy.utils.friday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.reactivex.Completable
|
||||
import org.threeten.bp.LocalDate.now
|
||||
@ -12,6 +12,6 @@ import javax.inject.Inject
|
||||
class ExamWork @Inject constructor(private val examRepository: ExamRepository) : Work {
|
||||
|
||||
override fun create(student: Student, semester: Semester): Completable {
|
||||
return examRepository.getExams(student, semester, now().monday, now().friday, true).ignoreElement()
|
||||
return examRepository.getExams(student, semester, now().monday, now().sunday, true).ignoreElement()
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import javax.inject.Inject
|
||||
class GradeStatisticsWork @Inject constructor(private val gradeStatisticsRepository: GradeStatisticsRepository) : Work {
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import androidx.core.app.NotificationCompat.PRIORITY_HIGH
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.repositories.grade.GradeRepository
|
||||
@ -30,17 +31,21 @@ class GradeWork @Inject constructor(
|
||||
|
||||
override fun create(student: Student, semester: Semester): Completable {
|
||||
return gradeRepository.getGrades(student, semester, true, preferencesRepository.isNotificationsEnable)
|
||||
.flatMap { gradeRepository.getNotNotifiedGrades(semester) }
|
||||
.flatMapCompletable {
|
||||
if (it.isNotEmpty()) notify(it)
|
||||
.ignoreElement()
|
||||
.concatWith(Completable.concatArray(gradeRepository.getNotNotifiedGrades(semester).flatMapCompletable {
|
||||
if (it.isNotEmpty()) notifyDetails(it)
|
||||
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>) {
|
||||
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewGradesChannel.CHANNEL_ID)
|
||||
.setContentTitle(context.resources.getQuantityString(R.plurals.grade_new_items, grades.size, grades.size))
|
||||
.setContentText(context.resources.getQuantityString(R.plurals.grade_notify_new_items, grades.size, grades.size))
|
||||
private fun getNotificationBuilder(): NotificationCompat.Builder {
|
||||
return NotificationCompat.Builder(context, NewGradesChannel.CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_stat_grade)
|
||||
.setAutoCancel(true)
|
||||
.setPriority(PRIORITY_HIGH)
|
||||
@ -49,6 +54,12 @@ class GradeWork @Inject constructor(
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(context, MainView.Section.GRADE.id,
|
||||
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 {
|
||||
setSummaryText(context.resources.getQuantityString(R.plurals.grade_number_item, grades.size, grades.size))
|
||||
grades.forEach { addLine("${it.subject}: ${it.entry}") }
|
||||
@ -57,4 +68,30 @@ class GradeWork @Inject constructor(
|
||||
.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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package io.github.wulkanowy.services.sync.works
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.repositories.homework.HomeworkRepository
|
||||
import io.github.wulkanowy.utils.friday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.reactivex.Completable
|
||||
import org.threeten.bp.LocalDate.now
|
||||
@ -12,6 +12,6 @@ import javax.inject.Inject
|
||||
class HomeworkWork @Inject constructor(private val homeworkRepository: HomeworkRepository) : Work {
|
||||
|
||||
override fun create(student: Student, semester: Semester): Completable {
|
||||
return homeworkRepository.getHomework(student, semester, now().monday, now().friday, true).ignoreElement()
|
||||
return homeworkRepository.getHomework(student, semester, now().monday, now().sunday, true).ignoreElement()
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package io.github.wulkanowy.services.sync.works
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.repositories.timetable.TimetableRepository
|
||||
import io.github.wulkanowy.utils.friday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.reactivex.Completable
|
||||
import org.threeten.bp.LocalDate.now
|
||||
@ -12,7 +12,7 @@ import javax.inject.Inject
|
||||
class TimetableWork @Inject constructor(private val timetableRepository: TimetableRepository) : Work {
|
||||
|
||||
override fun create(student: Student, semester: Semester): Completable {
|
||||
return timetableRepository.getTimetable(student, semester, now().monday, now().friday, true)
|
||||
return timetableRepository.getTimetable(student, semester, now().monday, now().sunday, true)
|
||||
.ignoreElement()
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import android.view.ViewGroup
|
||||
import android.widget.HorizontalScrollView
|
||||
import android.widget.Toast
|
||||
import android.widget.Toast.LENGTH_LONG
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.getSystemService
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.databinding.DialogErrorBinding
|
||||
@ -17,6 +18,7 @@ import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
|
||||
import io.github.wulkanowy.sdk.exception.ServiceUnavailableException
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.getString
|
||||
import io.github.wulkanowy.utils.openAppInMarket
|
||||
import io.github.wulkanowy.utils.openEmailClient
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import java.io.InterruptedIOException
|
||||
@ -74,7 +76,9 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
|
||||
Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show()
|
||||
}
|
||||
errorDialogCancel.setOnClickListener { dismiss() }
|
||||
errorDialogReport.setOnClickListener { openEmailClient(stringWriter.toString()) }
|
||||
errorDialogReport.setOnClickListener {
|
||||
openConfirmDialog { openEmailClient(stringWriter.toString()) }
|
||||
}
|
||||
errorDialogMessage.text = resources.getString(error)
|
||||
errorDialogReport.isEnabled = when (error) {
|
||||
is UnknownHostException,
|
||||
@ -88,6 +92,17 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun openConfirmDialog(callback: () -> Unit) {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.dialog_error_check_update)
|
||||
.setMessage(R.string.dialog_error_check_update_message)
|
||||
.setNeutralButton(R.string.about_feedback) { _, _ -> callback() }
|
||||
.setPositiveButton(R.string.dialog_error_check_update) { _, _ ->
|
||||
requireContext().openAppInMarket(::showMessage)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun openEmailClient(content: String) {
|
||||
requireContext().openEmailClient(
|
||||
chooserTitle = getString(R.string.about_feedback),
|
||||
|
@ -14,6 +14,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.getCompatDrawable
|
||||
import io.github.wulkanowy.utils.openAppInMarket
|
||||
import io.github.wulkanowy.utils.openEmailClient
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import javax.inject.Inject
|
||||
@ -98,8 +99,12 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about
|
||||
}
|
||||
}
|
||||
|
||||
override fun openAppInMarket() {
|
||||
context?.openAppInMarket(::showMessage)
|
||||
}
|
||||
|
||||
override fun openLogViewer() {
|
||||
if (appInfo.isDebug) (activity as? MainActivity)?.pushView(LogViewerFragment.newInstance())
|
||||
(activity as? MainActivity)?.pushView(LogViewerFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun openDiscordInvite() {
|
||||
@ -115,7 +120,7 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about
|
||||
chooserTitle = getString(R.string.about_feedback),
|
||||
email = "wulkanowyinc@gmail.com",
|
||||
subject = "Zgłoszenie błędu",
|
||||
body = requireContext().getString(R.string.about_feedback_template,
|
||||
body = getString(R.string.about_feedback_template,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), appInfo.versionName
|
||||
),
|
||||
onActivityNotFound = {
|
||||
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.about
|
||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import timber.log.Timber
|
||||
@ -12,6 +13,7 @@ class AboutPresenter @Inject constructor(
|
||||
schedulers: SchedulersProvider,
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val appInfo: AppInfo,
|
||||
private val analytics: FirebaseAnalyticsHelper
|
||||
) : BasePresenter<AboutView>(errorHandler, studentRepository, schedulers) {
|
||||
|
||||
@ -27,7 +29,8 @@ class AboutPresenter @Inject constructor(
|
||||
when (name) {
|
||||
versionRes?.first -> {
|
||||
Timber.i("Opening log viewer")
|
||||
openLogViewer()
|
||||
if (appInfo.isDebug) openLogViewer()
|
||||
else openAppInMarket()
|
||||
analytics.logEvent("about_open", "name" to "log_viewer")
|
||||
}
|
||||
feedbackRes?.first -> {
|
||||
|
@ -25,6 +25,8 @@ interface AboutView : BaseView {
|
||||
|
||||
fun updateData(data: List<Triple<String, String, Drawable?>>)
|
||||
|
||||
fun openAppInMarket()
|
||||
|
||||
fun openLogViewer()
|
||||
|
||||
fun openDiscordInvite()
|
||||
|
@ -25,9 +25,9 @@ class LogViewerPresenter @Inject constructor(
|
||||
disposable.add(loggerRepository.getLogFiles()
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.subscribe({
|
||||
Timber.i("Loading logs files result: ${it.joinToString { it.name }}")
|
||||
view?.shareLogs(it)
|
||||
.subscribe({ files ->
|
||||
Timber.i("Loading logs files result: ${files.joinToString { it.name }}")
|
||||
view?.shareLogs(files)
|
||||
}, {
|
||||
Timber.i("Loading logs files result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
|
@ -0,0 +1,3 @@
|
||||
package io.github.wulkanowy.ui.modules.account
|
||||
|
||||
data class Account(val email: String, val isParent: Boolean)
|
@ -3,33 +3,72 @@ package io.github.wulkanowy.ui.modules.account
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.PorterDuff
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.databinding.HeaderAccountBinding
|
||||
import io.github.wulkanowy.databinding.ItemAccountBinding
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
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 = {}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
||||
ItemAccountBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
)
|
||||
override fun getItemViewType(position: Int) = items[position].viewType.id
|
||||
|
||||
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")
|
||||
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
|
||||
val student = items[position]
|
||||
|
||||
with(holder.binding) {
|
||||
private fun bindItemViewHolder(binding: ItemAccountBinding, student: Student) {
|
||||
with(binding) {
|
||||
accountItemName.text = "${student.studentName} ${student.className}"
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import android.widget.Toast.LENGTH_LONG
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.databinding.DialogAccountBinding
|
||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||
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) {
|
||||
items = data
|
||||
notifyDataSetChanged()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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() {
|
||||
Timber.i("Loading account data started")
|
||||
disposable.add(studentRepository.getSavedStudents(false)
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.map { createAccountItems(it) }
|
||||
.subscribe({
|
||||
Timber.i("Loading account result: Success")
|
||||
view?.updateData(it)
|
||||
|
@ -1,13 +1,12 @@
|
||||
package io.github.wulkanowy.ui.modules.account
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface AccountView : BaseView {
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<Student>)
|
||||
fun updateData(data: List<AccountItem<*>>)
|
||||
|
||||
fun dismissView()
|
||||
|
||||
|
@ -20,7 +20,6 @@ import org.threeten.bp.LocalDate
|
||||
import org.threeten.bp.LocalDate.now
|
||||
import org.threeten.bp.LocalDate.ofEpochDay
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||
import javax.inject.Inject
|
||||
|
||||
class AttendancePresenter @Inject constructor(
|
||||
@ -215,7 +214,12 @@ class AttendancePresenter @Inject constructor(
|
||||
showContent(it.isNotEmpty())
|
||||
showExcuseButton(it.any { item -> item.excusable })
|
||||
}
|
||||
analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "attendance",
|
||||
"items" to it.size,
|
||||
"force_refresh" to forceRefresh
|
||||
)
|
||||
}) {
|
||||
Timber.i("Loading attendance result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
|
@ -99,7 +99,13 @@ class AttendanceSummaryPresenter @Inject constructor(
|
||||
showContent(it.isNotEmpty())
|
||||
updateDataSet(it)
|
||||
}
|
||||
analytics.logEvent("load_attendance_summary", "items" to it.size, "force_refresh" to forceRefresh, "item_id" to subjectId)
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "attendance_summary",
|
||||
"items" to it.size,
|
||||
"force_refresh" to forceRefresh,
|
||||
"item_id" to subjectId
|
||||
)
|
||||
}) {
|
||||
Timber.i("Loading attendance summary result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
|
@ -8,7 +8,7 @@ import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import io.github.wulkanowy.utils.friday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday
|
||||
import io.github.wulkanowy.utils.isHolidays
|
||||
import io.github.wulkanowy.utils.monday
|
||||
@ -110,7 +110,7 @@ class ExamPresenter @Inject constructor(
|
||||
add(studentRepository.getCurrentStudent()
|
||||
.flatMap { student ->
|
||||
semesterRepository.getCurrentSemester(student).flatMap { semester ->
|
||||
examRepository.getExams(student, semester, currentDate.monday, currentDate.friday, forceRefresh)
|
||||
examRepository.getExams(student, semester, currentDate.monday, currentDate.sunday, forceRefresh)
|
||||
}
|
||||
}
|
||||
.map { createExamItems(it) }
|
||||
@ -131,7 +131,12 @@ class ExamPresenter @Inject constructor(
|
||||
showErrorView(false)
|
||||
showContent(it.isNotEmpty())
|
||||
}
|
||||
analytics.logEvent("load_exam", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "exam",
|
||||
"items" to it.size,
|
||||
"force_refresh" to forceRefresh
|
||||
)
|
||||
}) {
|
||||
Timber.i("Loading exam result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
@ -176,7 +181,7 @@ class ExamPresenter @Inject constructor(
|
||||
showPreButton(!currentDate.minusDays(7).isHolidays)
|
||||
showNextButton(!currentDate.plusDays(7).isHolidays)
|
||||
updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " +
|
||||
currentDate.friday.toFormattedString("dd.MM"))
|
||||
currentDate.sunday.toFormattedString("dd.MM"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -1,11 +1,16 @@
|
||||
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.Student
|
||||
import io.github.wulkanowy.data.repositories.grade.GradeRepository
|
||||
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||
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.changeModifier
|
||||
import io.reactivex.Single
|
||||
@ -17,21 +22,21 @@ class GradeAverageProvider @Inject constructor(
|
||||
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>> {
|
||||
return semesterRepository.getSemesters(student).flatMap { semesters ->
|
||||
when (preferencesRepository.gradeAverageMode) {
|
||||
"only_one_semester" -> getSemesterDetailsWithAverage(student, semesters.single { it.semesterId == semesterId }, forceRefresh)
|
||||
"all_year" -> calculateWholeYearAverage(student, semesters, semesterId, forceRefresh)
|
||||
else -> throw IllegalArgumentException("Incorrect grade average mode: ${preferencesRepository.gradeAverageMode} ")
|
||||
ONE_SEMESTER -> getSemesterDetailsWithAverage(student, semesters.single { it.semesterId == semesterId }, forceRefresh)
|
||||
BOTH_SEMESTERS -> calculateBothSemestersAverage(student, semesters, semesterId, forceRefresh)
|
||||
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 firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
|
||||
|
||||
@ -42,11 +47,30 @@ class GradeAverageProvider @Inject constructor(
|
||||
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 + second?.grades.orEmpty()).calcAverage()
|
||||
} else (selected.average + (second?.average ?: selected.average)) / 2
|
||||
)
|
||||
selected.copy(average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
|
||||
val selectedGrades = selected.grades.updateModifiers(student).calcAverage()
|
||||
(selectedGrades + (second?.grades?.updateModifiers(student)?.calcAverage() ?: selectedGrades)) / 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)
|
||||
@ -58,15 +82,12 @@ class GradeAverageProvider @Inject constructor(
|
||||
val isAnyAverage = summaries.any { it.average != .0 }
|
||||
val allGrades = details.groupBy { it.subject }
|
||||
|
||||
summaries.map { summary ->
|
||||
summaries.emulateEmptySummaries(student, semester, allGrades.toList(), isAnyAverage).map { summary ->
|
||||
val grades = allGrades[summary.subject].orEmpty()
|
||||
GradeDetailsWithAverage(
|
||||
subject = summary.subject,
|
||||
average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
|
||||
grades.map {
|
||||
if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier)
|
||||
else it
|
||||
}.calcAverage()
|
||||
grades.updateModifiers(student).calcAverage()
|
||||
} else summary.average,
|
||||
points = summary.pointsSum,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -133,6 +133,7 @@ class GradePresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun loadChild(index: Int, forceRefresh: Boolean = false) {
|
||||
Timber.d("Load grade tab child. Selected semester: $selectedIndex, semesters: ${semesters.joinToString { it.semesterName.toString() }}")
|
||||
semesters.first { it.semesterName == selectedIndex }.semesterId.also {
|
||||
if (forceRefresh || loadedSemesterId[index] != it) {
|
||||
Timber.i("Load grade child view index: $index")
|
||||
|
@ -7,6 +7,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
import io.github.wulkanowy.databinding.HeaderGradeDetailsBinding
|
||||
@ -14,6 +15,7 @@ import io.github.wulkanowy.databinding.ItemGradeDetailsBinding
|
||||
import io.github.wulkanowy.ui.base.BaseExpandableAdapter
|
||||
import io.github.wulkanowy.utils.getBackgroundColor
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<RecyclerView.ViewHolder>() {
|
||||
@ -22,7 +24,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
|
||||
|
||||
private var items = mutableListOf<GradeDetailsItem>()
|
||||
|
||||
private var expandedPosition = RecyclerView.NO_POSITION
|
||||
private var expandedPosition = NO_POSITION
|
||||
|
||||
private var isExpandable = false
|
||||
|
||||
@ -34,36 +36,53 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
|
||||
headers = data.filter { it.viewType == ViewType.HEADER }.toMutableList()
|
||||
items = if (isExpanded) headers else data.toMutableList()
|
||||
isExpandable = isExpanded
|
||||
expandedPosition = RecyclerView.NO_POSITION
|
||||
expandedPosition = NO_POSITION
|
||||
}
|
||||
|
||||
fun updateDetailsItem(position: Int, grade: Grade) {
|
||||
if (items.getOrNull(position)?.viewType != ViewType.ITEM) {
|
||||
Timber.e("Trying to update item $position on list ${items.size} size, expanded position: $expandedPosition")
|
||||
return
|
||||
}
|
||||
items[position] = GradeDetailsItem(grade, ViewType.ITEM)
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
|
||||
fun getHeaderItem(subject: String): GradeDetailsItem {
|
||||
return headers.single { (it.value as GradeDetailsHeader).subject == subject }
|
||||
val candidates = headers.filter { (it.value as GradeDetailsHeader).subject == subject }
|
||||
|
||||
if (candidates.size > 1) {
|
||||
Timber.e("Header with subject $subject found ${candidates.size} times! Expanded: $expandedPosition. Items: $candidates")
|
||||
}
|
||||
|
||||
return candidates.first()
|
||||
}
|
||||
|
||||
fun updateHeaderItem(item: GradeDetailsItem) {
|
||||
headers[headers.indexOf(item)] = item
|
||||
items[items.indexOf(item)] = item
|
||||
notifyItemChanged(items.indexOf(item))
|
||||
val headerPosition = headers.indexOf(item)
|
||||
val itemPosition = 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() {
|
||||
if (expandedPosition != -1) {
|
||||
refreshList(headers)
|
||||
expandedPosition = RecyclerView.NO_POSITION
|
||||
expandedPosition = NO_POSITION
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun refreshList(newItems: List<GradeDetailsItem>) {
|
||||
private fun refreshList(newItems: MutableList<GradeDetailsItem>) {
|
||||
val diffCallback = GradeDetailsDiffUtil(items, newItems)
|
||||
val diffResult = DiffUtil.calculateDiff(diffCallback)
|
||||
items = newItems.toMutableList()
|
||||
items = newItems
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
@ -84,23 +103,24 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (holder) {
|
||||
is HeaderViewHolder -> bindHeaderViewHolder(
|
||||
binding = holder.binding,
|
||||
holder = holder,
|
||||
header = items[position].value as GradeDetailsHeader,
|
||||
headerPosition = headers.indexOf(items[position]),
|
||||
adapterPosition = position
|
||||
position = position
|
||||
)
|
||||
is ItemViewHolder -> bindItemViewHolder(
|
||||
binding = holder.binding,
|
||||
grade = items[position].value as Grade,
|
||||
position = position
|
||||
holder = holder,
|
||||
grade = items[position].value as Grade
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindHeaderViewHolder(binding: HeaderGradeDetailsBinding, header: GradeDetailsHeader, headerPosition: Int, adapterPosition: Int) {
|
||||
with(binding) {
|
||||
private fun bindHeaderViewHolder(holder: HeaderViewHolder, header: GradeDetailsHeader, position: Int) {
|
||||
val headerPosition = headers.indexOf(items[position])
|
||||
val adapterPosition = holder.adapterPosition
|
||||
|
||||
with(holder.binding) {
|
||||
gradeHeaderDivider.visibility = if (adapterPosition == 0) View.GONE else View.VISIBLE
|
||||
gradeHeaderSubject.apply {
|
||||
with(gradeHeaderSubject) {
|
||||
text = header.subject
|
||||
maxLines = if (headerPosition == expandedPosition) 2 else 1
|
||||
}
|
||||
@ -115,7 +135,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
|
||||
gradeHeaderContainer.setOnClickListener {
|
||||
expandedPosition = if (expandedPosition == adapterPosition) -1 else adapterPosition
|
||||
|
||||
if (expandedPosition != RecyclerView.NO_POSITION) {
|
||||
if (expandedPosition != NO_POSITION) {
|
||||
refreshList(headers.toMutableList().apply {
|
||||
addAll(headerPosition + 1, header.grades)
|
||||
})
|
||||
@ -133,8 +153,8 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun bindItemViewHolder(binding: ItemGradeDetailsBinding, grade: Grade, position: Int) {
|
||||
with(binding) {
|
||||
private fun bindItemViewHolder(holder: ItemViewHolder, grade: Grade) {
|
||||
with(holder.binding) {
|
||||
gradeItemValue.run {
|
||||
text = grade.entry
|
||||
setBackgroundResource(grade.getBackgroundColor(colorTheme))
|
||||
@ -148,7 +168,9 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
|
||||
gradeItemWeight.text = "${root.context.getString(R.string.grade_weight)}: ${grade.weight}"
|
||||
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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ class GradeDetailsPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onGradeItemSelected(grade: Grade, position: Int) {
|
||||
Timber.i("Select grade item ${grade.id}")
|
||||
Timber.i("Select grade item ${grade.id}, position: $position")
|
||||
view?.apply {
|
||||
showGradeDialog(grade, preferencesRepository.gradeColorTheme)
|
||||
if (!grade.isRead) {
|
||||
@ -152,7 +152,12 @@ class GradeDetailsPresenter @Inject constructor(
|
||||
gradeColorTheme = preferencesRepository.gradeColorTheme
|
||||
)
|
||||
}
|
||||
analytics.logEvent("load_grade_details", "items" to grades.size, "force_refresh" to forceRefresh)
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "grade_details",
|
||||
"items" to grades.size,
|
||||
"force_refresh" to forceRefresh
|
||||
)
|
||||
}) {
|
||||
Timber.i("Loading grade details result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
@ -171,19 +176,22 @@ class GradeDetailsPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun createGradeItems(items: List<GradeDetailsWithAverage>): List<GradeDetailsItem> {
|
||||
return items.filter { it.grades.isNotEmpty() }.map { (subject, average, points, _, grades) ->
|
||||
val subItems = grades.map {
|
||||
GradeDetailsItem(it, ViewType.ITEM)
|
||||
}
|
||||
return items
|
||||
.filter { it.grades.isNotEmpty() }
|
||||
.sortedBy { it.subject }
|
||||
.map { (subject, average, points, _, grades) ->
|
||||
val subItems = grades
|
||||
.sortedByDescending { it.date }
|
||||
.map { GradeDetailsItem(it, ViewType.ITEM) }
|
||||
|
||||
listOf(GradeDetailsItem(GradeDetailsHeader(
|
||||
subject = subject,
|
||||
average = average,
|
||||
pointsSum = points,
|
||||
newGrades = grades.filter { grade -> !grade.isRead }.size,
|
||||
grades = subItems
|
||||
), ViewType.HEADER)) + if (preferencesRepository.isGradeExpandable) emptyList() else subItems
|
||||
}.flatten()
|
||||
listOf(GradeDetailsItem(GradeDetailsHeader(
|
||||
subject = subject,
|
||||
average = average,
|
||||
pointsSum = points,
|
||||
newGrades = grades.filter { grade -> !grade.isRead }.size,
|
||||
grades = subItems
|
||||
), ViewType.HEADER)) + if (preferencesRepository.isGradeExpandable) emptyList() else subItems
|
||||
}.flatten()
|
||||
}
|
||||
|
||||
private fun updateGrade(grade: Grade) {
|
||||
|
@ -174,7 +174,12 @@ class GradeStatisticsPresenter @Inject constructor(
|
||||
updateData(it, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList)
|
||||
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)
|
||||
}
|
||||
analytics.logEvent("load_grade_statistics", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "grade_statistics",
|
||||
"items" to it.size,
|
||||
"force_refresh" to forceRefresh
|
||||
)
|
||||
}) {
|
||||
Timber.i("Loading grade stats result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
|
@ -49,7 +49,12 @@ class GradeSummaryPresenter @Inject constructor(
|
||||
showErrorView(false)
|
||||
updateData(it)
|
||||
}
|
||||
analytics.logEvent("load_grade_summary", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "grade_summary",
|
||||
"items" to it.size,
|
||||
"force_refresh" to forceRefresh
|
||||
)
|
||||
}) {
|
||||
Timber.i("Loading grade summary result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
@ -103,14 +108,18 @@ class GradeSummaryPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun createGradeSummaryItems(items: List<GradeDetailsWithAverage>): List<GradeSummary> {
|
||||
return items.map {
|
||||
it.summary.copy(average = it.average)
|
||||
}
|
||||
return items
|
||||
.filter { !checkEmpty(it) }
|
||||
.sortedBy { it.subject }
|
||||
.map { it.summary.copy(average = it.average) }
|
||||
}
|
||||
|
||||
private fun checkEmpty(gradeSummary: GradeSummary, averages: List<Triple<String, Double, String>>): Boolean {
|
||||
private fun checkEmpty(gradeSummary: GradeDetailsWithAverage): Boolean {
|
||||
return gradeSummary.run {
|
||||
finalGrade.isBlank() && predictedGrade.isBlank() && averages.singleOrNull { it.first == subject } == null
|
||||
summary.finalGrade.isBlank()
|
||||
&& summary.predictedGrade.isBlank()
|
||||
&& average == .0
|
||||
&& points.isBlank()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import io.github.wulkanowy.utils.friday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday
|
||||
import io.github.wulkanowy.utils.isHolidays
|
||||
import io.github.wulkanowy.utils.monday
|
||||
@ -124,7 +124,12 @@ class HomeworkPresenter @Inject constructor(
|
||||
showErrorView(false)
|
||||
showContent(it.isNotEmpty())
|
||||
}
|
||||
analytics.logEvent("load_homework", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "homework",
|
||||
"items" to it.size,
|
||||
"force_refresh" to forceRefresh
|
||||
)
|
||||
}) {
|
||||
Timber.i("Loading homework result: An exception occurred")
|
||||
|
||||
@ -170,7 +175,7 @@ class HomeworkPresenter @Inject constructor(
|
||||
showPreButton(!currentDate.minusDays(7).isHolidays)
|
||||
showNextButton(!currentDate.plusDays(7).isHolidays)
|
||||
updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " +
|
||||
currentDate.friday.toFormattedString("dd.MM"))
|
||||
currentDate.sunday.toFormattedString("dd.MM"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,10 @@ import javax.inject.Inject
|
||||
class LoginRecoverFragment :
|
||||
BaseFragment<FragmentLoginRecoverBinding>(R.layout.fragment_login_recover), LoginRecoverView {
|
||||
|
||||
private var _binding: FragmentLoginRecoverBinding? = null
|
||||
|
||||
private val bindingLocal: FragmentLoginRecoverBinding get() = _binding!!
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: LoginRecoverPresenter
|
||||
|
||||
@ -36,13 +40,13 @@ class LoginRecoverFragment :
|
||||
private lateinit var hostSymbols: Array<String>
|
||||
|
||||
override val recoverHostValue: String
|
||||
get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginRecoverHost.text.toString())).orEmpty()
|
||||
get() = hostValues.getOrNull(hostKeys.indexOf(bindingLocal.loginRecoverHost.text.toString())).orEmpty()
|
||||
|
||||
override val formHostSymbol: String
|
||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginRecoverHost.text.toString())).orEmpty()
|
||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(bindingLocal.loginRecoverHost.text.toString())).orEmpty()
|
||||
|
||||
override val recoverNameValue: String
|
||||
get() = binding.loginRecoverName.text.toString().trim()
|
||||
get() = bindingLocal.loginRecoverName.text.toString().trim()
|
||||
|
||||
override val emailHintString: String
|
||||
get() = getString(R.string.login_email_hint)
|
||||
@ -55,7 +59,7 @@ class LoginRecoverFragment :
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentLoginRecoverBinding.bind(view)
|
||||
_binding = FragmentLoginRecoverBinding.bind(view)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
@ -64,7 +68,7 @@ class LoginRecoverFragment :
|
||||
hostValues = resources.getStringArray(R.array.hosts_values)
|
||||
hostSymbols = resources.getStringArray(R.array.hosts_symbols)
|
||||
|
||||
with(binding) {
|
||||
with(bindingLocal) {
|
||||
loginRecoverWebView.setBackgroundColor(Color.TRANSPARENT)
|
||||
loginRecoverName.doOnTextChanged { _, _, _, _ -> presenter.onNameTextChanged() }
|
||||
loginRecoverHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() }
|
||||
@ -74,69 +78,69 @@ class LoginRecoverFragment :
|
||||
loginRecoverLogin.setOnClickListener { (activity as LoginActivity).switchView(0) }
|
||||
}
|
||||
|
||||
with(binding.loginRecoverHost) {
|
||||
with(bindingLocal.loginRecoverHost) {
|
||||
setText(hostKeys.getOrNull(0).orEmpty())
|
||||
setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys))
|
||||
setOnClickListener { if (binding.loginRecoverFormContainer.visibility == GONE) dismissDropDown() }
|
||||
setOnClickListener { if (bindingLocal.loginRecoverFormContainer.visibility == GONE) dismissDropDown() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDefaultCredentials(username: String) {
|
||||
binding.loginRecoverName.setText(username)
|
||||
bindingLocal.loginRecoverName.setText(username)
|
||||
}
|
||||
|
||||
override fun setErrorNameRequired() {
|
||||
with(binding.loginRecoverNameLayout) {
|
||||
with(bindingLocal.loginRecoverNameLayout) {
|
||||
requestFocus()
|
||||
error = getString(R.string.login_field_required)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setUsernameHint(hint: String) {
|
||||
binding.loginRecoverNameLayout.hint = hint
|
||||
bindingLocal.loginRecoverNameLayout.hint = hint
|
||||
}
|
||||
|
||||
override fun setUsernameError(message: String) {
|
||||
with(binding.loginRecoverNameLayout) {
|
||||
with(bindingLocal.loginRecoverNameLayout) {
|
||||
requestFocus()
|
||||
error = message
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearUsernameError() {
|
||||
binding.loginRecoverNameLayout.error = null
|
||||
bindingLocal.loginRecoverNameLayout.error = null
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
binding.loginRecoverProgress.visibility = if (show) VISIBLE else GONE
|
||||
bindingLocal.loginRecoverProgress.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun showRecoverForm(show: Boolean) {
|
||||
binding.loginRecoverFormContainer.visibility = if (show) VISIBLE else GONE
|
||||
bindingLocal.loginRecoverFormContainer.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun showCaptcha(show: Boolean) {
|
||||
binding.loginRecoverCaptchaContainer.visibility = if (show) VISIBLE else GONE
|
||||
bindingLocal.loginRecoverCaptchaContainer.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun showErrorView(show: Boolean) {
|
||||
binding.loginRecoverError.visibility = if (show) VISIBLE else GONE
|
||||
bindingLocal.loginRecoverError.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun setErrorDetails(message: String) {
|
||||
binding.loginRecoverErrorMessage.text = message
|
||||
bindingLocal.loginRecoverErrorMessage.text = message
|
||||
}
|
||||
|
||||
override fun showSuccessView(show: Boolean) {
|
||||
binding.loginRecoverSuccess.visibility = if (show) VISIBLE else GONE
|
||||
bindingLocal.loginRecoverSuccess.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun setSuccessTitle(title: String) {
|
||||
binding.loginRecoverSuccessTitle.text = title
|
||||
bindingLocal.loginRecoverSuccessTitle.text = title
|
||||
}
|
||||
|
||||
override fun setSuccessMessage(message: String) {
|
||||
binding.loginRecoverSuccessMessage.text = message
|
||||
bindingLocal.loginRecoverSuccessMessage.text = message
|
||||
}
|
||||
|
||||
override fun showSoftKeyboard() {
|
||||
@ -157,7 +161,7 @@ class LoginRecoverFragment :
|
||||
callback:e =>Android.captchaCallback(e)})</script>
|
||||
""".trimIndent()
|
||||
|
||||
with(binding.loginRecoverWebView) {
|
||||
with(bindingLocal.loginRecoverWebView) {
|
||||
settings.javaScriptEnabled = true
|
||||
webViewClient = object : WebViewClient() {
|
||||
private var recoverWebViewSuccess: Boolean = true
|
||||
@ -197,6 +201,8 @@ class LoginRecoverFragment :
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
bindingLocal.loginRecoverWebView.destroy()
|
||||
_binding = null
|
||||
presenter.onDetachView()
|
||||
|
||||
super.onDestroyView()
|
||||
|
@ -12,7 +12,13 @@ import javax.inject.Inject
|
||||
class LoginStudentSelectAdapter @Inject constructor() :
|
||||
RecyclerView.Adapter<LoginStudentSelectAdapter.ItemViewHolder>() {
|
||||
|
||||
private val checkedList = mutableMapOf<Int, Boolean>()
|
||||
|
||||
var items = emptyList<Pair<Student, Boolean>>()
|
||||
set(value) {
|
||||
field = value
|
||||
checkedList.clear()
|
||||
}
|
||||
|
||||
var onClickListener: (Student, alreadySaved: Boolean) -> Unit = { _, _ -> }
|
||||
|
||||
@ -31,15 +37,21 @@ class LoginStudentSelectAdapter @Inject constructor() :
|
||||
loginItemSchool.text = student.schoolName
|
||||
loginItemName.isEnabled = !alreadySaved
|
||||
loginItemSchool.isEnabled = !alreadySaved
|
||||
loginItemCheck.isEnabled = !alreadySaved
|
||||
loginItemSignedIn.visibility = if (alreadySaved) View.VISIBLE else View.GONE
|
||||
|
||||
with(loginItemCheck) {
|
||||
isEnabled = !alreadySaved
|
||||
keyListener = null
|
||||
isChecked = checkedList[position] ?: false
|
||||
}
|
||||
|
||||
root.setOnClickListener {
|
||||
onClickListener(student, alreadySaved)
|
||||
|
||||
with(loginItemCheck) {
|
||||
if (isEnabled) {
|
||||
isChecked = !isChecked
|
||||
checkedList[position] = isChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
|
||||
var students = emptyList<Student>()
|
||||
|
||||
private var selectedStudents = mutableListOf<Student>()
|
||||
private val selectedStudents = mutableListOf<Student>()
|
||||
|
||||
fun onAttachView(view: LoginStudentSelectView, students: Serializable?) {
|
||||
super.onAttachView(view)
|
||||
@ -69,6 +69,7 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun loadData(students: List<Student>) {
|
||||
resetSelectedState()
|
||||
this.students = students
|
||||
disposable.add(studentRepository.getSavedStudents()
|
||||
.map { savedStudents ->
|
||||
@ -88,6 +89,11 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun resetSelectedState() {
|
||||
selectedStudents.clear()
|
||||
view?.enableSignIn(false)
|
||||
}
|
||||
|
||||
private fun registerStudents(students: List<Student>) {
|
||||
disposable.add(studentRepository.saveStudents(students)
|
||||
.map { students.first().apply { id = it.first() } }
|
||||
|
@ -54,7 +54,12 @@ class LuckyNumberPresenter @Inject constructor(
|
||||
showEmpty(false)
|
||||
showErrorView(false)
|
||||
}
|
||||
analytics.logEvent("load_lucky_number", "lucky_number" to it.luckyNumber, "force_refresh" to forceRefresh)
|
||||
analytics.logEvent(
|
||||
"load_item",
|
||||
"type" to "lucky_number",
|
||||
"number" to it.luckyNumber,
|
||||
"force_refresh" to forceRefresh
|
||||
)
|
||||
}, {
|
||||
Timber.i("Loading lucky number result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
|
@ -31,6 +31,7 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||
import io.github.wulkanowy.ui.modules.more.MoreFragment
|
||||
import io.github.wulkanowy.ui.modules.note.NoteFragment
|
||||
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.safelyPopFragments
|
||||
@ -46,6 +47,9 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
@Inject
|
||||
lateinit var navController: FragNavController
|
||||
|
||||
@Inject
|
||||
lateinit var analytics: FirebaseAnalyticsHelper
|
||||
|
||||
@Inject
|
||||
lateinit var overlayProvider: Lazy<ElevationOverlayProvider>
|
||||
|
||||
@ -136,6 +140,10 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
}
|
||||
}
|
||||
|
||||
override fun setCurrentScreen(name: String?) {
|
||||
analytics.setCurrentScreen(this, name)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
||||
return if (item?.itemId == R.id.mainMenuAccount) presenter.onAccountManagerSelected()
|
||||
else false
|
||||
|
@ -37,8 +37,9 @@ class MainPresenter @Inject constructor(
|
||||
analytics.logEvent("app_open", "destination" to initMenu?.name)
|
||||
}
|
||||
|
||||
fun onViewChange(section: MainView.Section?) {
|
||||
fun onViewChange(section: MainView.Section?, name: String?) {
|
||||
view?.apply {
|
||||
setCurrentScreen(name)
|
||||
showActionBarElevation(section != GRADE && section != MESSAGE && section != SCHOOL)
|
||||
currentViewTitle?.let { setViewTitle(it) }
|
||||
currentViewSubtitle?.let { setViewSubTitle(it.ifBlank { null }) }
|
||||
|
@ -24,6 +24,8 @@ interface MainView : BaseView {
|
||||
|
||||
fun showAccountPicker()
|
||||
|
||||
fun setCurrentScreen(name: String?)
|
||||
|
||||
fun showActionBarElevation(show: Boolean)
|
||||
|
||||
fun notifyMenuViewReselected()
|
||||
|
@ -63,7 +63,7 @@ class MessagePreviewAdapter @Inject constructor() :
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun bindMessage(holder: MessageViewHolder, message: Message) {
|
||||
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"))
|
||||
messagePreviewContent.text = message.content
|
||||
messagePreviewAuthor.text = if (message.folderId == MessageFolder.SENT.id) "${root.context.getString(R.string.message_to)} ${message.recipient}"
|
||||
|
@ -1,12 +1,20 @@
|
||||
package io.github.wulkanowy.ui.modules.message.preview
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.print.PrintAttributes
|
||||
import android.print.PrintManager
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
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 io.github.wulkanowy.R
|
||||
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.message.MessageFragment
|
||||
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
|
||||
|
||||
class MessagePreviewFragment :
|
||||
@ -29,18 +39,31 @@ class MessagePreviewFragment :
|
||||
@Inject
|
||||
lateinit var previewAdapter: MessagePreviewAdapter
|
||||
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
private var menuReplyButton: MenuItem? = null
|
||||
|
||||
private var menuForwardButton: MenuItem? = null
|
||||
|
||||
private var menuDeleteButton: MenuItem? = null
|
||||
|
||||
private var menuShareButton: MenuItem? = null
|
||||
|
||||
private var menuPrintButton: MenuItem? = null
|
||||
|
||||
override val titleStringId: Int
|
||||
get() = R.string.message_title
|
||||
|
||||
override val deleteMessageSuccessString: String
|
||||
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 {
|
||||
const val MESSAGE_ID_KEY = "message_id"
|
||||
|
||||
@ -77,6 +100,8 @@ class MessagePreviewFragment :
|
||||
menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply)
|
||||
menuForwardButton = menu.findItem(R.id.messagePreviewMenuForward)
|
||||
menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete)
|
||||
menuShareButton = menu.findItem(R.id.messagePreviewMenuShare)
|
||||
menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint)
|
||||
presenter.onCreateOptionsMenu()
|
||||
}
|
||||
|
||||
@ -85,6 +110,8 @@ class MessagePreviewFragment :
|
||||
R.id.messagePreviewMenuReply -> presenter.onReply()
|
||||
R.id.messagePreviewMenuForward -> presenter.onForward()
|
||||
R.id.messagePreviewMenuDelete -> presenter.onMessageDelete()
|
||||
R.id.messagePreviewMenuShare -> presenter.onShare()
|
||||
R.id.messagePreviewMenuPrint -> presenter.onPrint()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@ -108,6 +135,8 @@ class MessagePreviewFragment :
|
||||
menuReplyButton?.isVisible = show
|
||||
menuForwardButton?.isVisible = show
|
||||
menuDeleteButton?.isVisible = show
|
||||
menuShareButton?.isVisible = show
|
||||
menuPrintButton?.isVisible = show && appInfo.systemVersion >= Build.VERSION_CODES.LOLLIPOP
|
||||
}
|
||||
|
||||
override fun setDeletedOptionsLabels() {
|
||||
@ -138,6 +167,38 @@ class MessagePreviewFragment :
|
||||
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() {
|
||||
(activity as MainActivity).popView()
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
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.MessageAttachment
|
||||
import io.github.wulkanowy.data.repositories.message.MessageRepository
|
||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -15,11 +20,14 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val messageRepository: MessageRepository,
|
||||
private val analytics: FirebaseAnalyticsHelper
|
||||
private val analytics: FirebaseAnalyticsHelper,
|
||||
private var appInfo: AppInfo
|
||||
) : BasePresenter<MessagePreviewView>(errorHandler, studentRepository, schedulers) {
|
||||
|
||||
var message: Message? = null
|
||||
|
||||
var attachments: List<MessageAttachment>? = null
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
private var retryCallback: () -> Unit = {}
|
||||
@ -56,11 +64,16 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
.subscribe({ message ->
|
||||
Timber.i("Loading message ${message.message.messageId} preview result: Success ")
|
||||
this@MessagePreviewPresenter.message = message.message
|
||||
this@MessagePreviewPresenter.attachments = message.attachments
|
||||
view?.apply {
|
||||
setMessageWithAttachment(message)
|
||||
initOptions()
|
||||
}
|
||||
analytics.logEvent("load_message_preview", "length" to message.message.content.length)
|
||||
analytics.logEvent(
|
||||
"load_item",
|
||||
"type" to "message_preview",
|
||||
"length" to message.message.content.length
|
||||
)
|
||||
}) {
|
||||
Timber.i("Loading message ${message.messageId} preview result: An exception occurred ")
|
||||
retryCallback = { onMessageLoadRetry(message) }
|
||||
@ -83,6 +96,60 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
} 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() {
|
||||
message?.let { message ->
|
||||
disposable.add(studentRepository.getCurrentStudent()
|
||||
|
@ -1,5 +1,7 @@
|
||||
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.MessageWithAttachment
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
@ -8,6 +10,10 @@ interface MessagePreviewView : BaseView {
|
||||
|
||||
val deleteMessageSuccessString: String
|
||||
|
||||
val messageNoSubjectString: String
|
||||
|
||||
val printHTML: String
|
||||
|
||||
fun initView()
|
||||
|
||||
fun setMessageWithAttachment(item: MessageWithAttachment)
|
||||
@ -34,5 +40,10 @@ interface MessagePreviewView : BaseView {
|
||||
|
||||
fun openMessageForward(message: Message?)
|
||||
|
||||
fun shareText(text: String, subject: String)
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
fun printDocument(html: String, jobName: String)
|
||||
|
||||
fun popView()
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import android.graphics.Typeface
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SortedList
|
||||
import androidx.recyclerview.widget.SortedListAdapterCallback
|
||||
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.repositories.message.MessageFolder
|
||||
@ -19,39 +19,23 @@ class MessageTabAdapter @Inject constructor() :
|
||||
|
||||
var onClickListener: (Message, position: Int) -> Unit = { _, _ -> }
|
||||
|
||||
private val items = SortedList(Message::class.java, object :
|
||||
SortedListAdapterCallback<Message>(this) {
|
||||
private var items = mutableListOf<Message>()
|
||||
|
||||
override fun compare(item1: Message, item2: Message): Int {
|
||||
return item2.date.compareTo(item1.date)
|
||||
}
|
||||
|
||||
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 setDataItems(data: List<Message>) {
|
||||
val diffResult = DiffUtil.calculateDiff(MessageTabDiffUtil(items, data))
|
||||
items = data.toMutableList()
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
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(
|
||||
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
|
||||
|
||||
root.setOnClickListener { onClickListener(item, position) }
|
||||
root.setOnClickListener {
|
||||
holder.adapterPosition.let { if (it != NO_POSITION) onClickListener(item, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
||||
}
|
||||
|
||||
override fun updateData(data: List<Message>) {
|
||||
tabAdapter.replaceAll(data)
|
||||
tabAdapter.setDataItems(data)
|
||||
}
|
||||
|
||||
override fun updateItem(item: Message, position: Int) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
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.repositories.message.MessageFolder
|
||||
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.SchedulersProvider
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import io.reactivex.subjects.PublishSubject
|
||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.pow
|
||||
|
||||
class MessageTabPresenter @Inject constructor(
|
||||
schedulers: SchedulersProvider,
|
||||
@ -31,9 +35,12 @@ class MessageTabPresenter @Inject constructor(
|
||||
|
||||
private var messages = emptyList<Message>()
|
||||
|
||||
private val searchQuery = PublishSubject.create<String>()
|
||||
|
||||
fun onAttachView(view: MessageTabView, folder: MessageFolder) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
initializeSearchStream()
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
this.folder = folder
|
||||
}
|
||||
@ -64,7 +71,7 @@ class MessageTabPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onMessageItemSelected(message: Message, position: Int) {
|
||||
Timber.i("Select message ${message.id} item")
|
||||
Timber.i("Select message ${message.id} item (position: $position)")
|
||||
view?.run {
|
||||
openMessage(message)
|
||||
if (message.unread) {
|
||||
@ -76,33 +83,35 @@ class MessageTabPresenter @Inject constructor(
|
||||
|
||||
private fun loadData(forceRefresh: Boolean) {
|
||||
Timber.i("Loading $folder message data started")
|
||||
disposable.apply {
|
||||
clear()
|
||||
add(studentRepository.getCurrentStudent()
|
||||
.flatMap { student ->
|
||||
semesterRepository.getCurrentSemester(student)
|
||||
.flatMap { messageRepository.getMessages(student, it, folder, forceRefresh) }
|
||||
disposable.add(studentRepository.getCurrentStudent()
|
||||
.flatMap { student ->
|
||||
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)
|
||||
.doFinally {
|
||||
view?.run {
|
||||
showRefresh(false)
|
||||
showProgress(false)
|
||||
enableSwipe(true)
|
||||
notifyParentDataLoaded()
|
||||
}
|
||||
}
|
||||
.subscribe({
|
||||
Timber.i("Loading $folder message result: Success")
|
||||
messages = it
|
||||
onSearchQueryTextChange(lastSearchQuery)
|
||||
analytics.logEvent("load_messages", "items" to it.size, "folder" to folder.name)
|
||||
}) {
|
||||
Timber.i("Loading $folder message result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
})
|
||||
}
|
||||
}
|
||||
.subscribe({
|
||||
Timber.i("Loading $folder message result: Success")
|
||||
messages = it
|
||||
view?.updateData(getFilteredData(lastSearchQuery))
|
||||
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) {
|
||||
@ -116,23 +125,36 @@ class MessageTabPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
fun onSearchQueryTextChange(query: String) {
|
||||
lastSearchQuery = query
|
||||
if (query != searchQuery.toString())
|
||||
searchQuery.onNext(query)
|
||||
}
|
||||
|
||||
val lowerCaseQuery = query.toLowerCase()
|
||||
val filteredList = mutableListOf<Message>()
|
||||
messages.forEach {
|
||||
if (lowerCaseQuery in it.subject.toLowerCase() ||
|
||||
lowerCaseQuery in it.sender.toLowerCase() ||
|
||||
lowerCaseQuery in it.recipient.toLowerCase() ||
|
||||
lowerCaseQuery in it.date.toFormattedString()
|
||||
) {
|
||||
filteredList.add(it)
|
||||
private fun initializeSearchStream() {
|
||||
disposable.add(searchQuery
|
||||
.debounce(250, TimeUnit.MILLISECONDS)
|
||||
.map { query ->
|
||||
lastSearchQuery = query
|
||||
getFilteredData(query)
|
||||
}
|
||||
}
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.subscribe({
|
||||
Timber.d("Applying filter. Full list: ${messages.size}, filtered: ${it.size}")
|
||||
updateData(it)
|
||||
}) { Timber.e(it) })
|
||||
}
|
||||
|
||||
updateData(filteredList)
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateData(data: List<Message>) {
|
||||
@ -144,4 +166,42 @@ class MessageTabPresenter @Inject constructor(
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,12 @@ class MobileDevicePresenter @Inject constructor(
|
||||
showEmpty(it.isEmpty())
|
||||
showErrorView(false)
|
||||
}
|
||||
analytics.logEvent("load_devices", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "devices",
|
||||
"items" to it.size,
|
||||
"force_refresh" to forceRefresh
|
||||
)
|
||||
}) {
|
||||
Timber.i("Loading mobile devices result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
|
@ -69,7 +69,12 @@ class NotePresenter @Inject constructor(
|
||||
showErrorView(false)
|
||||
showContent(it.isNotEmpty())
|
||||
}
|
||||
analytics.logEvent("load_note", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "note",
|
||||
"items" to it.size,
|
||||
"force_refresh" to forceRefresh
|
||||
)
|
||||
}, {
|
||||
Timber.i("Loading note result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
|
@ -88,7 +88,11 @@ class SchoolPresenter @Inject constructor(
|
||||
showEmpty(false)
|
||||
showErrorView(false)
|
||||
}
|
||||
analytics.logEvent("load_school", "force_refresh" to forceRefresh)
|
||||
analytics.logEvent(
|
||||
"load_item",
|
||||
"type" to "school",
|
||||
"force_refresh" to forceRefresh
|
||||
)
|
||||
}, {
|
||||
Timber.i("Loading school result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
|
@ -75,7 +75,12 @@ class TeacherPresenter @Inject constructor(
|
||||
showEmpty(it.isEmpty())
|
||||
showErrorView(false)
|
||||
}
|
||||
analytics.logEvent("load_teachers", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "teachers",
|
||||
"items" to it.size,
|
||||
"force_refresh" to forceRefresh
|
||||
)
|
||||
}) {
|
||||
Timber.i("Loading teachers result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
|
@ -7,6 +7,7 @@ import androidx.appcompat.app.AlertDialog
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.thelittlefireman.appkillermanager.AppKillerManager
|
||||
import com.thelittlefireman.appkillermanager.exceptions.NoActionFoundException
|
||||
import com.yariksoffice.lingver.Lingver
|
||||
import dagger.android.support.AndroidSupportInjection
|
||||
import io.github.wulkanowy.R
|
||||
@ -14,6 +15,7 @@ import io.github.wulkanowy.ui.base.BaseActivity
|
||||
import io.github.wulkanowy.ui.base.ErrorDialog
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import javax.inject.Inject
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat(),
|
||||
@ -133,9 +135,13 @@ class SettingsFragment : PreferenceFragmentCompat(),
|
||||
.setMessage(R.string.pref_notify_fix_sync_issues_message)
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.setPositiveButton(R.string.pref_notify_fix_sync_issues_settings_button) { _, _ ->
|
||||
AppKillerManager.doActionPowerSaving(requireContext())
|
||||
AppKillerManager.doActionAutoStart(requireContext())
|
||||
AppKillerManager.doActionNotification(requireContext())
|
||||
try {
|
||||
AppKillerManager.doActionPowerSaving(requireContext())
|
||||
AppKillerManager.doActionAutoStart(requireContext())
|
||||
AppKillerManager.doActionNotification(requireContext())
|
||||
} catch (e: NoActionFoundException) {
|
||||
requireContext().openInternetBrowser("https://dontkillmyapp.com/${AppKillerManager.getDevice()?.manufacturer}", ::showMessage)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
@ -132,41 +132,46 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
}
|
||||
|
||||
private fun updateTimeLeft(binding: ItemTimetableBinding, lesson: Timetable, position: Int) {
|
||||
val isShowTimeUntil = lesson.isShowTimeUntil(getPreviousLesson(position))
|
||||
val until = lesson.until
|
||||
val left = lesson.left
|
||||
val isJustFinished = lesson.isJustFinished
|
||||
|
||||
with(binding) {
|
||||
when {
|
||||
// before lesson
|
||||
lesson.isShowTimeUntil(getPreviousLesson(position)) -> {
|
||||
isShowTimeUntil -> {
|
||||
Timber.d("Show time until lesson: $position")
|
||||
timetableItemTimeLeft.visibility = GONE
|
||||
with(timetableItemTimeUntil) {
|
||||
visibility = VISIBLE
|
||||
text = context.getString(R.string.timetable_time_until,
|
||||
if (lesson.until.seconds <= 60) {
|
||||
context.getString(R.string.timetable_seconds, lesson.until.seconds.toString(10))
|
||||
if (until.seconds <= 60) {
|
||||
context.getString(R.string.timetable_seconds, until.seconds.toString(10))
|
||||
} else {
|
||||
context.getString(R.string.timetable_minutes, lesson.until.toMinutes().toString(10))
|
||||
context.getString(R.string.timetable_minutes, until.toMinutes().toString(10))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
// after lesson start
|
||||
lesson.left != null -> {
|
||||
left != null -> {
|
||||
Timber.d("Show time left lesson: $position")
|
||||
timetableItemTimeUntil.visibility = GONE
|
||||
with(timetableItemTimeLeft) {
|
||||
visibility = VISIBLE
|
||||
text = context.getString(
|
||||
R.string.timetable_time_left,
|
||||
if (lesson.left!!.seconds < 60) {
|
||||
context.getString(R.string.timetable_seconds, lesson.left?.seconds?.toString(10))
|
||||
if (left.seconds < 60) {
|
||||
context.getString(R.string.timetable_seconds, left.seconds.toString(10))
|
||||
} else {
|
||||
context.getString(R.string.timetable_minutes, lesson.left?.toMinutes()?.toString(10))
|
||||
context.getString(R.string.timetable_minutes, left.toMinutes().toString(10))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
// right after lesson finish
|
||||
lesson.isJustFinished -> {
|
||||
isJustFinished -> {
|
||||
Timber.d("Show just finished lesson: $position")
|
||||
timetableItemTimeUntil.visibility = GONE
|
||||
timetableItemTimeLeft.visibility = VISIBLE
|
||||
|
@ -154,7 +154,12 @@ class TimetablePresenter @Inject constructor(
|
||||
showErrorView(false)
|
||||
showContent(it.isNotEmpty())
|
||||
}
|
||||
analytics.logEvent("load_timetable", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "timetable",
|
||||
"items" to it.size,
|
||||
"force_refresh" to forceRefresh
|
||||
)
|
||||
}) {
|
||||
Timber.i("Loading timetable result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
|
@ -43,6 +43,7 @@ class CompletedLessonsPresenter @Inject constructor(
|
||||
completedLessonsErrorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
completedLessonsErrorHandler.onFeatureDisabled = {
|
||||
this.view?.showFeatureDisabled()
|
||||
this.view?.showEmpty(true)
|
||||
Timber.i("Completed lessons feature disabled by school")
|
||||
}
|
||||
loadData(ofEpochDay(date ?: baseDate.toEpochDay()))
|
||||
@ -134,7 +135,12 @@ class CompletedLessonsPresenter @Inject constructor(
|
||||
showErrorView(false)
|
||||
showContent(it.isNotEmpty())
|
||||
}
|
||||
analytics.logEvent("load_completed_lessons", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "completed_lessons",
|
||||
"items" to it.size,
|
||||
"force_refresh" to forceRefresh
|
||||
)
|
||||
}) {
|
||||
Timber.i("Loading completed lessons result: An exception occurred")
|
||||
completedLessonsErrorHandler.dispatch(it)
|
||||
|
@ -10,7 +10,7 @@ import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.BuildConfig.APPLICATION_ID
|
||||
|
||||
@ColorInt
|
||||
fun Context.getThemeAttrColor(@AttrRes colorAttr: Int): Int {
|
||||
@ -39,6 +39,12 @@ fun Context.openInternetBrowser(uri: String, onActivityNotFound: (uri: String) -
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.openAppInMarket(onActivityNotFound: (uri: String) -> Unit) {
|
||||
openInternetBrowser("market://details?id=${APPLICATION_ID}") {
|
||||
openInternetBrowser("https://github.com/wulkanowy/wulkanowy/releases", onActivityNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.openEmailClient(chooserTitle: String, email: String, subject: String, body: String, onActivityNotFound: () -> Unit = {}) {
|
||||
val intent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")).apply {
|
||||
putExtra(Intent.EXTRA_EMAIL, arrayOf(email))
|
||||
@ -65,4 +71,17 @@ fun Context.openDialer(phone: String) {
|
||||
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
|
||||
|
@ -4,14 +4,14 @@ import androidx.fragment.app.Fragment
|
||||
import com.ncapdevi.fragnav.FragNavController
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
|
||||
inline fun FragNavController.setOnViewChangeListener(crossinline listener: (section: MainView.Section?) -> Unit) {
|
||||
inline fun FragNavController.setOnViewChangeListener(crossinline listener: (section: MainView.Section?, name: String?) -> Unit) {
|
||||
transactionListener = object : FragNavController.TransactionListener {
|
||||
override fun onFragmentTransaction(fragment: Fragment?, transactionType: FragNavController.TransactionType) {
|
||||
listener(fragment?.toSection())
|
||||
listener(fragment?.toSection(), fragment?.let { it::class.java.simpleName })
|
||||
}
|
||||
|
||||
override fun onTabTransaction(fragment: Fragment?, index: Int) {
|
||||
listener(fragment?.toSection())
|
||||
listener(fragment?.toSection(), fragment?.let { it::class.java.simpleName })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,4 +52,4 @@ class LifecycleAwareVariableActivity<T : Any> : ReadWriteProperty<AppCompatActiv
|
||||
@Suppress("unused")
|
||||
fun <T : Any> Fragment.lifecycleAwareVariable() = LifecycleAwareVariable<T>()
|
||||
|
||||
fun <T : Any> AppCompatActivity.lifecycleAwareVariable() = LifecycleAwareVariableActivity<T>()
|
||||
fun <T : Any> lifecycleAwareVariable() = LifecycleAwareVariableActivity<T>()
|
||||
|
@ -17,7 +17,7 @@ class SchooldaysRangeLimiter : DateRangeLimiter {
|
||||
override fun isOutOfRange(year: Int, month: Int, day: Int): Boolean {
|
||||
val date = LocalDate.of(year, month + 1, day)
|
||||
val dayOfWeek = date.dayOfWeek
|
||||
return dayOfWeek == DayOfWeek.SUNDAY || dayOfWeek == DayOfWeek.SATURDAY || date.isHolidays
|
||||
return dayOfWeek == DayOfWeek.SUNDAY || date.isHolidays
|
||||
}
|
||||
|
||||
override fun getStartDate(): Calendar {
|
||||
|
@ -23,6 +23,8 @@ fun Sdk.init(student: Student): Sdk {
|
||||
certKey = student.certificateKey
|
||||
privateKey = student.privateKey
|
||||
|
||||
emptyCookieJarInterceptor = true
|
||||
|
||||
Timber.d("Sdk in ${student.loginMode} mode reinitialized")
|
||||
|
||||
return this
|
||||
|
@ -92,8 +92,8 @@ inline val LocalDate.weekDayName: String
|
||||
inline val LocalDate.monday: LocalDate
|
||||
get() = with(MONDAY)
|
||||
|
||||
inline val LocalDate.friday: LocalDate
|
||||
get() = with(FRIDAY)
|
||||
inline val LocalDate.sunday: LocalDate
|
||||
get() = with(SUNDAY)
|
||||
|
||||
/**
|
||||
* [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335)
|
||||
|
@ -1,10 +1,8 @@
|
||||
Wersja 0.18.0
|
||||
- naprawiliśmy odświeżanie zadań domowych
|
||||
- naprawiliśmy powiadomienia na androidzie 8.0
|
||||
- oceny powinny się teraz odświeżać trochę szybciej
|
||||
- dodaliśmy tryb pełnoekranowy w zadaniach
|
||||
- dodaliśmy wyszukiwanie w wiadomościach
|
||||
- dodaliśmy opcje oznaczania bieżącej lekcji na planie/w powiadomieniu (domyślnie wyłączone)
|
||||
- dodaliśmy testową opcję naprawy powiadomień na np. Huawei, Xiaomi (znajdziesz ją w ustawieniach)
|
||||
Wersja 0.19.0
|
||||
- naprawiliśmy pokazywanie brakujących przedmiotów na liście podsumowania ocen
|
||||
- ulepszyliśmy wygląd menadżera kont
|
||||
- ulepszyliśmy wyszukiwarkę wiadomości
|
||||
- dodaliśmy powiadomienia o proponowanych i końcowych ocenach
|
||||
- dodaliśmy opcję udostępniania i drukowania wiadomości
|
||||
|
||||
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
|
||||
|
13
app/src/main/res/drawable/ic_menu_message_print.xml
Normal file
13
app/src/main/res/drawable/ic_menu_message_print.xml
Normal 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>
|
10
app/src/main/res/drawable/ic_menu_message_share.xml
Normal file
10
app/src/main/res/drawable/ic_menu_message_share.xml
Normal 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>
|
@ -148,7 +148,7 @@
|
||||
android:hint="@string/message_content"
|
||||
android:imeOptions="flagNoExtractUi"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textMultiLine"
|
||||
android:inputType="textMultiLine|textCapSentences"
|
||||
android:minHeight="58dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingLeft="16dp"
|
||||
|
@ -5,45 +5,60 @@
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
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"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/accountDialogTitle"
|
||||
android:overScrollMode="never"
|
||||
tools:itemCount="3"
|
||||
tools:listitem="@layout/item_account" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<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_below="@id/accountDialogRecycler"
|
||||
android:layout_margin="8dp"
|
||||
android:text="@string/account_add_new"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
<TextView
|
||||
android:id="@+id/accountDialogTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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" />
|
||||
|
||||
<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_below="@id/accountDialogRecycler"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_margin="8dp"
|
||||
android:text="@string/account_logout" />
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/accountDialogRecycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_weight="1"
|
||||
android:overScrollMode="never"
|
||||
tools:itemCount="3"
|
||||
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>
|
||||
|
@ -153,8 +153,8 @@
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="16sp" />
|
||||
android:textSize="16sp"
|
||||
tools:text="@tools:sample/date/ddmmyy" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/attendanceNextButton"
|
||||
|
@ -138,8 +138,8 @@
|
||||
android:layout_height="match_parent"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="16sp" />
|
||||
android:textSize="16sp"
|
||||
tools:text="@tools:sample/date/ddmmyy" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/examNextButton"
|
||||
|
@ -138,8 +138,8 @@
|
||||
android:layout_height="match_parent"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="16sp" />
|
||||
android:textSize="16sp"
|
||||
tools:text="@tools:sample/date/ddmmyy" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/homeworkNextButton"
|
||||
|
@ -67,7 +67,6 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/login_contact_email"
|
||||
app:icon="@drawable/ic_more_messages" />
|
||||
@ -78,7 +77,6 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/about_faq"
|
||||
app:icon="@drawable/ic_about_faq" />
|
||||
@ -182,7 +180,6 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:text="@string/login_recover_button"
|
||||
android:textAppearance="?android:textAppearance"
|
||||
app:backgroundTint="?android:windowBackground"
|
||||
@ -223,9 +220,9 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:text="@string/login_advanced"
|
||||
android:textAppearance="?android:textAppearance"
|
||||
android:visibility="gone"
|
||||
app:backgroundTint="?android:windowBackground"
|
||||
app:fontFamily="sans-serif-medium"
|
||||
app:layout_constraintBottom_toBottomOf="@id/loginFormSignIn"
|
||||
@ -241,7 +238,6 @@
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginTop="48dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/login_sign_in"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
@ -24,10 +24,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<View
|
||||
@ -42,9 +42,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginLeft="32dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginRight="32dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/login_contact_header"
|
||||
@ -55,28 +55,29 @@
|
||||
android:id="@+id/loginStudentSelectContactButtons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp">
|
||||
android:layout_marginRight="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:id="@+id/loginStudentSelectContactEmail"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/login_contact_email"
|
||||
app:icon="@drawable/ic_more_messages" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:id="@+id/loginStudentSelectContactDiscord"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/login_contact_discord"
|
||||
app:icon="@drawable/ic_about_discord" />
|
||||
</LinearLayout>
|
||||
@ -95,9 +96,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginLeft="32dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginRight="32dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/login_select_student"
|
||||
@ -129,8 +130,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/login_sign_in"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -139,8 +139,8 @@
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="16sp" />
|
||||
android:textSize="16sp"
|
||||
tools:text="@tools:sample/date/ddmmyy" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/timetableNextButton"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user