From 5529ffcf73712532e8bef74bbf9ed2cfa532fe5e Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2020 12:01:52 +0000 Subject: [PATCH 01/13] Bump fragment-ktx from 1.2.4 to 1.2.5 (#878) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 498c7a376..1f616e6fc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -131,7 +131,7 @@ dependencies { 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" From 8cce81585aec9fafc1c5a0131952aa35219edb7b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2020 12:02:10 +0000 Subject: [PATCH 02/13] Bump firebase-analytics from 17.4.2 to 17.4.3 (#881) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 1f616e6fc..0abc9295e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -181,7 +181,7 @@ dependencies { implementation "io.coil-kt:coil:0.11.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.0" - playImplementation 'com.google.firebase:firebase-analytics:17.4.2' + playImplementation 'com.google.firebase:firebase-analytics:17.4.3' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.7' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.7" playImplementation 'com.google.firebase:firebase-messaging:20.2.0' From 00943717a2bdf5ce6fa060c32fb9a2c83cdc0cef Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2020 12:02:38 +0000 Subject: [PATCH 03/13] Bump firebase-crashlytics from 17.0.0 to 17.0.1 (#880) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 0abc9295e..86ee94c37 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -185,7 +185,7 @@ dependencies { 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.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" From f151f7bd62aac50f71a5387c4dd819bf4ba8e164 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2020 12:05:30 +0000 Subject: [PATCH 04/13] Bump about_libraries from 8.1.6 to 8.2.0 (#879) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7e5759d12..6b0859822 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext.kotlin_version = '1.3.72' - ext.about_libraries = '8.1.6' + ext.about_libraries = '8.2.0' repositories { mavenCentral() google() From eedaa637716752ec3250a61da735551dd99a383d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2020 12:05:54 +0000 Subject: [PATCH 05/13] Bump sonarqube-gradle-plugin from 2.8 to 3.0 (#882) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6b0859822..4de7310b5 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { classpath 'com.google.gms:google-services:4.3.3' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1' classpath "com.github.triplet.gradle:play-publisher:2.7.5" - classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8" + classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${about_libraries}" } From 30af77614e4b222ab8eee896fd33cc847c7c0c17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 11 Jun 2020 14:38:04 +0200 Subject: [PATCH 06/13] Fix showing summary summary for subjects without partial grades (#877) --- app/build.gradle | 6 +++--- .../wulkanowy/ui/modules/grade/GradeAverageProvider.kt | 2 +- app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 86ee94c37..44896664a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -77,8 +77,8 @@ android { } } - viewBinding { - enabled = true + buildFeatures { + viewBinding = true } lintOptions { @@ -124,7 +124,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:0.18.3" + implementation "io.github.wulkanowy:sdk:7dc0761" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.2.0" diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index bda098f47..af1699323 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -98,7 +98,7 @@ class GradeAverageProvider @Inject constructor( } private fun List.emulateEmptySummaries(student: Student, semester: Semester, grades: List>>, calcAverage: Boolean): List { - if (isNotEmpty() && size == grades.size) return this + if (isNotEmpty() && size > grades.size) return this return grades.mapIndexed { i, (subject, details) -> singleOrNull { it.subject == subject }?.let { return@mapIndexed it } diff --git a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt index e4d4163b4..63a30db8c 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt @@ -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 From a05da2656a3960848e2baef1ce9545e316b6f116 Mon Sep 17 00:00:00 2001 From: Dominik Korsa Date: Fri, 12 Jun 2020 21:35:51 +0200 Subject: [PATCH 07/13] Add account headers in student picker (#871) --- .../repositories/student/StudentRemote.kt | 2 +- .../wulkanowy/ui/modules/account/Account.kt | 3 + .../ui/modules/account/AccountAdapter.kt | 60 +++++++++++-- .../ui/modules/account/AccountDialog.kt | 3 +- .../ui/modules/account/AccountItem.kt | 9 ++ .../ui/modules/account/AccountPresenter.kt | 9 ++ .../ui/modules/account/AccountView.kt | 3 +- app/src/main/res/layout/dialog_account.xml | 89 +++++++++++-------- app/src/main/res/layout/header_account.xml | 28 ++++++ app/src/main/res/layout/item_account.xml | 30 +++++-- app/src/main/res/values-pl/strings.xml | 4 + app/src/main/res/values/strings.xml | 4 + 12 files changed, 185 insertions(+), 59 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/account/Account.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt create mode 100644 app/src/main/res/layout/header_account.xml diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt index 15c336505..4c0ffd820 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt @@ -14,7 +14,7 @@ class StudentRemote @Inject constructor(private val sdk: Sdk) { private fun mapStudents(students: List, email: String, password: String): List { return students.map { student -> Student( - email = email, + email = email.ifBlank { student.email }, password = password, isParent = student.isParent, symbol = student.symbol, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/Account.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/Account.kt new file mode 100644 index 000000000..dbcb499e8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/Account.kt @@ -0,0 +1,3 @@ +package io.github.wulkanowy.ui.modules.account + +data class Account(val email: String, val isParent: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt index 7df0ca378..27915a710 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountAdapter.kt @@ -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() { +class AccountAdapter @Inject constructor() : RecyclerView.Adapter() { - var items = emptyList() + var items = emptyList>() 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(), AccountView { } } - override fun updateData(data: List) { + override fun updateData(data: List>) { with(accountAdapter) { items = data notifyDataSetChanged() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt new file mode 100644 index 000000000..05a4a69ce --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountItem.kt @@ -0,0 +1,9 @@ +package io.github.wulkanowy.ui.modules.account + +data class AccountItem(val value: T, val viewType: ViewType) { + + enum class ViewType(val id: Int) { + HEADER(1), + ITEM(2) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt index 3416a043f..b5fbcdb63 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt @@ -83,11 +83,20 @@ class AccountPresenter @Inject constructor( } } + private fun createAccountItems(items: List): List> { + 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) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt index abb9e1d27..a1f8086cd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountView.kt @@ -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) + fun updateData(data: List>) fun dismissView() diff --git a/app/src/main/res/layout/dialog_account.xml b/app/src/main/res/layout/dialog_account.xml index 6e975c80d..d56de3a2e 100644 --- a/app/src/main/res/layout/dialog_account.xml +++ b/app/src/main/res/layout/dialog_account.xml @@ -5,45 +5,60 @@ android:layout_width="300dp" android:layout_height="wrap_content"> - - - + android:orientation="vertical"> - + - + + + + + + + + + + + diff --git a/app/src/main/res/layout/header_account.xml b/app/src/main/res/layout/header_account.xml new file mode 100644 index 000000000..6219c26db --- /dev/null +++ b/app/src/main/res/layout/header_account.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/app/src/main/res/layout/item_account.xml b/app/src/main/res/layout/item_account.xml index 614e4d2df..9568b345a 100644 --- a/app/src/main/res/layout/item_account.xml +++ b/app/src/main/res/layout/item_account.xml @@ -3,20 +3,17 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="56dp" + android:layout_height="wrap_content" android:background="?selectableItemBackground" android:orientation="horizontal" - android:paddingStart="24dp" - android:paddingLeft="24dp" - android:paddingEnd="24dp" - android:paddingRight="24dp" + android:paddingVertical="8dp" + android:paddingHorizontal="16dp" tools:context=".ui.modules.account.AccountAdapter"> + + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 7f25d8a67..4cb8997cf 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -265,6 +265,10 @@ Wyloguj Czy chcesz wylogować aktualnego ucznia? Wylogowanie ucznia + Konto ucznia + Konto rodzica + Tryb API mobilne + Tryb hybrydowy Wersja aplikacji Twórcy diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7793cd9c2..29e3460b4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -282,6 +282,10 @@ Logout Do you want to log out of an active student? Student logout + Student account + Parent account + Mobile API mode + Hybrid mode From a52983693722869b56e6afddfb29ddd6f469e3a9 Mon Sep 17 00:00:00 2001 From: Dominik Korsa Date: Sat, 13 Jun 2020 14:12:01 +0200 Subject: [PATCH 08/13] Fix lint errors (#873) --- .../data/db/migrations/Migration13Test.kt | 30 +++++++-------- .../attendance/AttendanceLocalTest.kt | 37 +++++++++++++++++-- .../luckynumber/LuckyNumberLocalTest.kt | 1 - .../wulkanowy/utils/CrashlyticsUtils.kt | 1 - .../github/wulkanowy/data/RepositoryModule.kt | 6 +-- .../appcreator/AppCreatorRepository.kt | 2 +- .../data/repositories/exam/ExamLocal.kt | 2 +- .../repositories/recipient/RecipientLocal.kt | 2 +- .../reportingunit/ReportingUnitLocal.kt | 2 +- .../sync/works/GradeStatisticsWork.kt | 2 +- .../about/logviewer/LogViewerPresenter.kt | 6 +-- .../modules/attendance/AttendancePresenter.kt | 1 - .../completed/CompletedLessonsPresenter.kt | 2 +- .../wulkanowy/utils/LifecycleAwareVariable.kt | 2 +- app/src/main/res/values/strings.xml | 2 +- .../io/github/wulkanowy/TestEnityCreator.kt | 23 ++++++++++++ .../message/MessageRepositoryTest.kt | 13 +++---- 17 files changed, 91 insertions(+), 43 deletions(-) diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt b/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt index 6f9406f6d..da4284b02 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt @@ -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) } } diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt index c7ede6ae5..cdfc524ad 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt @@ -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 + ) } diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt index 65ef1fcbf..efad0d4d5 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt @@ -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 diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt index 6351997d0..d03a319a2 100644 --- a/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt @@ -2,7 +2,6 @@ package io.github.wulkanowy.utils -import android.content.Context import timber.log.Timber open class TimberTreeNoOp : Timber.Tree() { diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt index 9540372fc..19af1b2f8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt @@ -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 diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt index 6a0b2d32e..76cf698c9 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt @@ -10,7 +10,7 @@ import javax.inject.Singleton @Singleton class AppCreatorRepository @Inject constructor(private val assets: AssetManager) { fun getAppCreators(): Single> { - return Single.fromCallable> { + return Single.fromCallable { Gson().fromJson( assets.open("contributors.json").bufferedReader().use { it.readText() }, Array::class.java diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt index 0f484d323..389eb5835 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt @@ -13,7 +13,7 @@ class ExamLocal @Inject constructor(private val examDb: ExamDao) { fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe> { return examDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) - .filter { !it.isEmpty() } + .filter { it.isNotEmpty() } } fun saveExams(exams: List) { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt index 9b1d4ac2f..ff8175440 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt @@ -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> { - 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): List { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt index 6f9eec3fc..0631c668c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt @@ -11,7 +11,7 @@ import javax.inject.Singleton class ReportingUnitLocal @Inject constructor(private val reportingUnitDb: ReportingUnitDao) { fun getReportingUnits(student: Student): Maybe> { - return reportingUnitDb.load(student.studentId).filter { !it.isEmpty() } + return reportingUnitDb.load(student.studentId).filter { it.isNotEmpty() } } fun getReportingUnit(student: Student, unitId: Int): Maybe { diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt index 327c71740..c4681fb8f 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt @@ -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() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt index 4485cb3eb..33eb1122d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt @@ -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) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index 437e06c92..f58d0617f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -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( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt index 92aeb5814..f72d753a9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt @@ -43,7 +43,7 @@ class CompletedLessonsPresenter @Inject constructor( completedLessonsErrorHandler.showErrorMessage = ::showErrorViewOnError completedLessonsErrorHandler.onFeatureDisabled = { this.view?.showFeatureDisabled() - this.view?.showEmpty(true); + this.view?.showEmpty(true) Timber.i("Completed lessons feature disabled by school") } loadData(ofEpochDay(date ?: baseDate.toEpochDay())) diff --git a/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt b/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt index 2c49ee97c..b96faeb21 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt @@ -52,4 +52,4 @@ class LifecycleAwareVariableActivity : ReadWriteProperty Fragment.lifecycleAwareVariable() = LifecycleAwareVariable() -fun AppCompatActivity.lifecycleAwareVariable() = LifecycleAwareVariableActivity() +fun lifecycleAwareVariable() = LifecycleAwareVariableActivity() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 29e3460b4..bec752cf2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -343,7 +343,7 @@ Prev Next Search - Search... + Search… diff --git a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt index 5d003f273..f7d29220f 100644 --- a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt +++ b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy +import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Timetable @@ -72,3 +73,25 @@ fun getTimetableEntity( teacher = "", teacherOld = "" ) + +fun getMessageEntity( + messageId: Int, + content: String, + unread: Boolean +) = Message( + studentId = 1, + realId = 1, + messageId = messageId, + sender = "", + senderId = 1, + recipient = "", + subject = "", + content = content, + date = now(), + folderId = 1, + unread = unread, + unreadBy = 1, + readBy = 1, + removed = false, + hasAttachments = false +) diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt index b7963d430..fcc4188a6 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt @@ -2,10 +2,10 @@ package io.github.wulkanowy.data.repositories.message import androidx.room.EmptyResultSetException import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.UnitTestInternetObservingStrategy +import io.github.wulkanowy.getMessageEntity import io.reactivex.Single import io.reactivex.observers.TestObserver import org.junit.Assert.assertEquals @@ -15,7 +15,6 @@ import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -import org.threeten.bp.LocalDateTime.now import java.net.UnknownHostException class MessageRepositoryTest { @@ -44,7 +43,7 @@ class MessageRepositoryTest { @Test fun `throw error when message is not in the db`() { - val testMessage = Message(1, 1, 1, "", 1, "", "", "", now(), 1, false, 1, 1, false, false) + val testMessage = getMessageEntity(1, "", false) `when`(local.getMessageWithAttachment(student, testMessage)).thenReturn(Single.error(EmptyResultSetException("No message in database"))) val message = repo.getMessage(student, testMessage) @@ -55,7 +54,7 @@ class MessageRepositoryTest { @Test fun `get message when content already in db`() { - val testMessage = Message(1, 1, 123, "", 1, "", "", "Test", now(), 1, false, 1, 1, false, false) + val testMessage = getMessageEntity(123, "Test", false) val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) `when`(local.getMessageWithAttachment(student, testMessage)).thenReturn(Single.just(messageWithAttachment)) @@ -67,7 +66,7 @@ class MessageRepositoryTest { @Test fun `get message when content in db is empty`() { - val testMessage = Message(1, 1, 123, "", 1, "", "", "", now(), 1, true, 1, 1, false, false) + val testMessage = getMessageEntity(123, "", true) val testMessageWithContent = testMessage.copy(content = "Test") val mWa = MessageWithAttachment(testMessage, emptyList()) @@ -86,7 +85,7 @@ class MessageRepositoryTest { @Test fun `get message when content in db is empty and there is no internet connection`() { - val testMessage = Message(1, 1, 123, "", 1, "", "", "", now(), 1, false, 1, 1, false, false) + val testMessage = getMessageEntity(123, "", false) val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) testObservingStrategy.isInternetConnection = false @@ -100,7 +99,7 @@ class MessageRepositoryTest { @Test fun `get message when content in db is empty, unread and there is no internet connection`() { - val testMessage = Message(1, 1, 123, "", 1, "", "", "", now(), 1, true, 1, 1, false, false) + val testMessage = getMessageEntity(123, "", true) val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) testObservingStrategy.isInternetConnection = false From a6682c9b73d525133e32accbc2aae89636fad9b2 Mon Sep 17 00:00:00 2001 From: Dominik Korsa Date: Sat, 13 Jun 2020 17:11:18 +0200 Subject: [PATCH 09/13] Add predicted and final grade notifications (#872) --- .../25.json | 2 +- .../26.json | 1768 +++++++++++++++++ .../github/wulkanowy/data/db/AppDatabase.kt | 6 +- .../data/db/entities/GradeSummary.kt | 13 + .../data/db/migrations/Migration26.kt | 14 + .../data/repositories/grade/GradeLocal.kt | 4 + .../repositories/grade/GradeRepository.kt | 39 +- .../services/sync/works/GradeWork.kt | 53 +- app/src/main/res/values-pl/strings.xml | 24 + app/src/main/res/values/strings.xml | 16 + 10 files changed, 1927 insertions(+), 12 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json index e99a11d4a..474824df6 100644 --- a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/25.json @@ -1741,4 +1741,4 @@ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd101f5a26a024f62e6fee161e421b882')" ] } -} \ No newline at end of file +} diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json new file mode 100644 index 000000000..21005f9c6 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/26.json @@ -0,0 +1,1768 @@ +{ + "formatVersion": 1, + "database": { + "version": 26, + "identityHash": "43f8777ffe95a5a2850ee981db3dbe2e", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `grade` INTEGER NOT NULL, `amount` INTEGER NOT NULL, `is_semester` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "grade", + "columnName": "grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semester", + "columnName": "is_semester", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '43f8777ffe95a5a2850ee981db3dbe2e')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt index 762c52f8f..a31b68c0d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt @@ -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 { return arrayOf( @@ -137,7 +138,8 @@ abstract class AppDatabase : RoomDatabase() { Migration22(), Migration23(), Migration24(), - Migration25() + Migration25(), + Migration26() ) } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt index 6e29112b2..dd3126d4d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt @@ -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() } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt new file mode 100644 index 000000000..7130d86d8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt @@ -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") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt index 944ed34ae..52ab60178 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt @@ -27,6 +27,10 @@ class GradeLocal @Inject constructor( gradeDb.updateAll(grades) } + fun updateGradesSummary(gradesSummary: List) { + gradeSummaryDb.updateAll(gradesSummary) + } + fun getGradesDetails(semester: Semester): Maybe> { return gradeDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt index e28350a51..2ba68b967 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt @@ -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> { + return local.getGradesSummary(semester).map { it.filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified } }.toSingle(emptyList()) + } + + fun getNotNotifiedFinalGrades(semester: Semester): Single> { + 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): Completable { return Completable.fromCallable { local.updateGrades(grades) } } + + fun updateGradesSummary(gradesSummary: List): Completable { + return Completable.fromCallable { local.updateGradesSummary(gradesSummary) } + } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt index 6e90826ad..fcdaad6e6 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt @@ -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) { - 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) { + 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) { + 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) { + 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() + ) + } } diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 4cb8997cf..2bba1640b 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -107,12 +107,36 @@ Nowe oceny Nowe oceny + + Nowa ocena przewidywana + Nowe oceny przewidywane + Nowe oceny przewidywane + Nowe oceny przewidywane + + + Nowa ocena końcowa + Nowe oceny końcowe + Nowe oceny końcowe + Nowe oceny końcowe + Masz %1$d nową ocenę Masz %1$d nowe oceny Masz %1$d nowych ocen Masz %1$d nowych ocen + + Masz %1$d nową przewidywaną ocenę + Masz %1$d nowe przewidywane oceny + Masz %1$d nowych przewidywanych ocen + Masz %1$d nowych przewidywanych ocen + + + Masz %1$d nową końcową ocenę + Masz %1$d nowe końcowe oceny + Masz %1$d nowych końcowych ocen + Masz %1$d nowych końcowych ocen + Lekcja Sala diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bec752cf2..3efb53cb2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -113,10 +113,26 @@ New grade New grades + + New predicted grade + New predicted grades + + + New final grade + New final grades + You received %1$d grade You received %1$d grades + + You received %1$d predicted grade + You received %1$d predicted grades + + + You received %1$d final grade + You received %1$d final grades + From 924bcb0d647b1664de83427a669f721bf5594d86 Mon Sep 17 00:00:00 2001 From: Dominik Korsa Date: Sun, 14 Jun 2020 00:50:09 +0200 Subject: [PATCH 10/13] Message sharing and printing (#866) --- app/src/main/assets/message-print-page.html | 94 +++++++++++++++++++ app/src/main/assets/wulkanowy-logo-black.svg | 74 +++++++++++++++ .../message/preview/MessagePreviewAdapter.kt | 2 +- .../message/preview/MessagePreviewFragment.kt | 61 ++++++++++++ .../preview/MessagePreviewPresenter.kt | 65 ++++++++++++- .../message/preview/MessagePreviewView.kt | 11 +++ .../wulkanowy/utils/ContextExtension.kt | 13 +++ .../res/drawable/ic_menu_message_print.xml | 13 +++ .../res/drawable/ic_menu_message_share.xml | 10 ++ .../res/menu/action_menu_message_preview.xml | 14 +++ app/src/main/res/values-pl/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 12 files changed, 359 insertions(+), 2 deletions(-) create mode 100644 app/src/main/assets/message-print-page.html create mode 100644 app/src/main/assets/wulkanowy-logo-black.svg create mode 100644 app/src/main/res/drawable/ic_menu_message_print.xml create mode 100644 app/src/main/res/drawable/ic_menu_message_share.xml diff --git a/app/src/main/assets/message-print-page.html b/app/src/main/assets/message-print-page.html new file mode 100644 index 000000000..8da7dec6e --- /dev/null +++ b/app/src/main/assets/message-print-page.html @@ -0,0 +1,94 @@ + + + + + %SUBJECT% | Wulkanowy + + + +

%SUBJECT%

+
+
+ %INFO% +
+ +
+
+

Treść wiadomości

+ %CONTENT% +
+ + diff --git a/app/src/main/assets/wulkanowy-logo-black.svg b/app/src/main/assets/wulkanowy-logo-black.svg new file mode 100644 index 000000000..9bfbe2c02 --- /dev/null +++ b/app/src/main/assets/wulkanowy-logo-black.svg @@ -0,0 +1,74 @@ + +image/svg+xml \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt index 436dee53f..a94d2cfc8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt @@ -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}" diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt index 99eede15a..575db75b9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt @@ -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()?.let { printManager -> + val printAdapter = webView.createPrintDocumentAdapter(jobName) + + printManager.print( + jobName, + printAdapter, + PrintAttributes.Builder().build() + ) + } + } + override fun popView() { (activity as MainActivity).popView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt index 24678c70e..db7996bca 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt @@ -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(errorHandler, studentRepository, schedulers) { var message: Message? = null + var attachments: List? = null + private lateinit var lastError: Throwable private var retryCallback: () -> Unit = {} @@ -56,6 +64,7 @@ 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() @@ -87,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 = "

Data wysłania

$dateString
" + when { + it.sender.isNotEmpty() -> "

Od

${it.sender}
" + else -> "

Do

${it.recipient}
" + } + + val messageContent = "

${it.content}

" + .replace(Regex("[\\n\\r]{2,}"), "

") + .replace(Regex("[\\n\\r]"), "
") + + 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() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt index 3d620459c..0fdb4bda3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt @@ -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() } diff --git a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt index 2b40cb476..cf715e657 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt @@ -71,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 diff --git a/app/src/main/res/drawable/ic_menu_message_print.xml b/app/src/main/res/drawable/ic_menu_message_print.xml new file mode 100644 index 000000000..204b0f6e3 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_message_print.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_menu_message_share.xml b/app/src/main/res/drawable/ic_menu_message_share.xml new file mode 100644 index 000000000..67a8ee494 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_message_share.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/menu/action_menu_message_preview.xml b/app/src/main/res/menu/action_menu_message_preview.xml index dfc12e234..4c1332e10 100644 --- a/app/src/main/res/menu/action_menu_message_preview.xml +++ b/app/src/main/res/menu/action_menu_message_preview.xml @@ -22,4 +22,18 @@ android:title="@string/message_delete" app:iconTint="@color/material_on_surface_emphasis_medium" app:showAsAction="ifRoom" /> + + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 2bba1640b..a8eccabf0 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -204,6 +204,8 @@ Przenieś do kosza Usuń trwale Wiadomość usunięta pomyślnie + Udostępnij + Drukuj Temat Treść Wiadomość wysłana pomyślnie diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3efb53cb2..1eaebf284 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -210,6 +210,8 @@ Move to trash Delete permanently Message deleted successfully + Share + Print Subject Content Message sent successfully From 6e1ddb482e90008dcf8af110362abedc4d10d42b Mon Sep 17 00:00:00 2001 From: Dominik Korsa Date: Sun, 14 Jun 2020 14:05:24 +0200 Subject: [PATCH 11/13] Message fuzzy search (#869) --- app/build.gradle | 3 +- .../modules/message/tab/MessageTabAdapter.kt | 56 ++++--- .../modules/message/tab/MessageTabFragment.kt | 2 +- .../message/tab/MessageTabPresenter.kt | 147 ++++++++++++------ 4 files changed, 130 insertions(+), 78 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 44896664a..82d92f25a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -142,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" @@ -180,6 +180,7 @@ 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.3' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.7' diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt index ece6773fd..b58508a98 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt @@ -4,10 +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.RecyclerView.NO_POSITION -import androidx.recyclerview.widget.SortedList -import androidx.recyclerview.widget.SortedListAdapterCallback import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.repositories.message.MessageFolder @@ -20,39 +19,23 @@ class MessageTabAdapter @Inject constructor() : var onClickListener: (Message, position: Int) -> Unit = { _, _ -> } - private val items = SortedList(Message::class.java, object : - SortedListAdapterCallback(this) { + private var items = mutableListOf() - 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) { - 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) { + 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) @@ -85,4 +68,19 @@ class MessageTabAdapter @Inject constructor() : } class ItemViewHolder(val binding: ItemMessageBinding) : RecyclerView.ViewHolder(binding.root) + + private class MessageTabDiffUtil(private val old: List, private val new: List) : + 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] + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt index 909bb6873..9954c6428 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt @@ -90,7 +90,7 @@ class MessageTabFragment : BaseFragment(R.layout.frag } override fun updateData(data: List) { - tabAdapter.replaceAll(data) + tabAdapter.setDataItems(data) } override fun updateItem(item: Message, position: Int) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt index 221762d16..533f5ac85 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt @@ -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() + private val searchQuery = PublishSubject.create() + fun onAttachView(view: MessageTabView, folder: MessageFolder) { super.onAttachView(view) view.initView() + initializeSearchStream() errorHandler.showErrorMessage = ::showErrorViewOnError this.folder = folder } @@ -76,38 +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_data", - "type" to "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) { @@ -121,25 +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() - 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) }) + } + + private fun getFilteredData(query: String): List { + return if (query.trim().isEmpty()) { + messages.sortedByDescending { it.date } + } else { + messages + .map { it to calculateMatchRatio(it, query) } + .sortedByDescending { it.second } + .filter { it.second > 5000 } + .map { it.first } } - - Timber.d("Applying filter. Full list: ${messages.size}, filtered: ${filteredList.size}") - - updateData(filteredList) } private fun updateData(data: List) { @@ -151,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() + } } From dfe7981e7fa71321267f2d1daf545a7ae65e9eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 14 Jun 2020 22:37:58 +0200 Subject: [PATCH 12/13] New Crowdin translations (#874) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- .../main/res/values-de/preferences_values.xml | 4 +-- app/src/main/res/values-de/strings.xml | 30 ++++++++++++++--- app/src/main/res/values-pl/strings.xml | 2 +- .../main/res/values-ru/preferences_values.xml | 2 +- app/src/main/res/values-ru/strings.xml | 32 ++++++++++++++++++- .../main/res/values-uk/preferences_values.xml | 2 +- app/src/main/res/values-uk/strings.xml | 32 ++++++++++++++++++- 7 files changed, 93 insertions(+), 11 deletions(-) diff --git a/app/src/main/res/values-de/preferences_values.xml b/app/src/main/res/values-de/preferences_values.xml index 496287984..11935b49b 100644 --- a/app/src/main/res/values-de/preferences_values.xml +++ b/app/src/main/res/values-de/preferences_values.xml @@ -36,8 +36,8 @@ Durchschnittsnote für das 2. Semester - Average of grades from both semesters - Durchschnitt der Bewertungen für das ganze Jahr + Durchschnitt der Noten aus beiden Semestern + Durchschnitt der Noten aus dem ganzen Jahr Nicht zeigen diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4b1669371..7cc44fd97 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -103,10 +103,26 @@ Neue Note Neue Noten + + New predicted grade + New predicted grades + + + New final grade + New final grades + Du hast %1$d Note bekommen Du hast %1$d Noten bekommen + + You received %1$d predicted grade + You received %1$d predicted grades + + + You received %1$d final grade + You received %1$d final grades + Lektion Klassenzimmer @@ -172,6 +188,8 @@ In den Korb wandern Dauerhaft löschen Nachricht erfolgreich gelöscht + Share + Print Thema Inhalt Nachricht erfolgreich gesendet @@ -214,7 +232,7 @@ Die heutige Glücksnummer ist Keine Information über die Glücksnummer. Glücksnummer für heute - Die heutige Glücksnummer ist: + Die heutige Glücksnummer ist: %d Mobile Geräte Keine Geräte @@ -245,6 +263,10 @@ Abmelden Wollen Sie sich von einem aktiven Studenten abmelden? Abmeldung von Student + Student account + Parent account + Mobile API mode + Hybrid mode Version der App Mitarbeiter @@ -270,8 +292,8 @@ Logs teilen Aktualisieren - Check for updates - Before reporting a bug, check first if an update with the bug fix is available + Auf Updates prüfen + Bevor Sie einen Fehler melden, prüfen Sie zuerst, ob ein Update mit der Fehlerbehebung verfügbar ist Inhalt Wiederhol @@ -289,7 +311,7 @@ Zurück Nächste Suchen - Suchen... + Suchen… Keine Lektionen Thema wählen diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a8eccabf0..bd4545e9b 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -339,7 +339,7 @@ Poprzedni Następny Szukaj - Szukaj... + Szukaj… Brak lekcji Wybierz motyw diff --git a/app/src/main/res/values-ru/preferences_values.xml b/app/src/main/res/values-ru/preferences_values.xml index bd8bb844f..a41abf350 100644 --- a/app/src/main/res/values-ru/preferences_values.xml +++ b/app/src/main/res/values-ru/preferences_values.xml @@ -37,7 +37,7 @@ Средняя оценка со 2 семестра Average of grades from both semesters - Средняя оценка с целого года + Average of grades from the whole year Не показывать diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6eb8f7741..15c583173 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -107,12 +107,36 @@ Новые оценки Новые оценки + + New predicted grade + New predicted grades + New predicted grades + New predicted grades + + + New final grade + New final grades + New final grades + New final grades + Вы получили %1$d оценку Вы получили %1$d оценки Вы получили %1$d оценок Вы получили %1$d оценок + + You received %1$d predicted grade + You received %1$d predicted grades + You received %1$d predicted grades + You received %1$d predicted grades + + + You received %1$d final grade + You received %1$d final grades + You received %1$d final grades + You received %1$d final grades + Урок Аудитория @@ -180,6 +204,8 @@ Перенести в корзину Удалить навсегда Сообщение успешно удалено + Share + Print Тема Текст Сообщение успешно отправлено @@ -265,6 +291,10 @@ Выйти Вы точно хотите выйти из данного аккаунта? Выйти + Student account + Parent account + Mobile API mode + Hybrid mode Версия приложения Разработчики @@ -309,7 +339,7 @@ Предыдущий Следующий Поиск - Поиск... + Поиск… Нет уроков Выбрать тему diff --git a/app/src/main/res/values-uk/preferences_values.xml b/app/src/main/res/values-uk/preferences_values.xml index be2179c3e..9942621a5 100644 --- a/app/src/main/res/values-uk/preferences_values.xml +++ b/app/src/main/res/values-uk/preferences_values.xml @@ -37,7 +37,7 @@ Середня оцінка з 2 семестру Average of grades from both semesters - Середня оцінка за весь рік + Average of grades from the whole year Не показувати diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index ec4dbc155..423c4e12f 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -107,12 +107,36 @@ Нові оцінки Нові оцінки + + New predicted grade + New predicted grades + New predicted grades + New predicted grades + + + New final grade + New final grades + New final grades + New final grades + Ви отримали %1$d оцінку Ви отримали %1$d оцінки Ви отримали %1$d оцінок Ви отримали %1$d оцінок + + You received %1$d predicted grade + You received %1$d predicted grades + You received %1$d predicted grades + You received %1$d predicted grades + + + You received %1$d final grade + You received %1$d final grades + You received %1$d final grades + You received %1$d final grades + Урок Аудиторія @@ -180,6 +204,8 @@ Перемістити у кошик Видалити назавжди Повідомлення було успішно видалено + Share + Print Тема Зміст Повідомлення було успішно відправлено @@ -265,6 +291,10 @@ Вийти Ви впевнені, що хочете вийти з цього аккаунту? Вийти з аккаунту учня + Student account + Parent account + Mobile API mode + Hybrid mode Версія додатка Розробники @@ -309,7 +339,7 @@ Попередній Наступний Пошук - Пошук... + Пошук… Брак уроків Увібрати тему From c13f12f729114c5bfd4539f168fcd1dfd383b52c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 14 Jun 2020 22:40:36 +0200 Subject: [PATCH 13/13] Version 0.19.0 --- .travis.yml | 2 +- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 10 ++++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 788aeffa7..a24058083 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ cache: branches: only: - develop - - 0.18.3 + - 0.19.0 android: licenses: diff --git a/app/build.gradle b/app/build.gradle index 82d92f25a..621e5a4fe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 17 targetSdkVersion 29 - versionCode 62 - versionName "0.18.3" + versionCode 63 + versionName "0.19.0" multiDexEnabled true resValue "string", "app_name", "Wulkanowy" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -124,7 +124,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:7dc0761" + 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" diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt index db9e9a9a3..68045da4a 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,6 +1,8 @@ -Wersja 0.18.3 -- poprawiliśmy liczenie średniej i dodaliśmy nowy sposób jej liczenia w ustawieniach -- naprawiliśmy usuwanie wiadomości -- naprawiliśmy wysyłanie wiadomości na Lubelskim Portalu Oświatowym +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