Merge branch 'release/0.18.2'

This commit is contained in:
Mikołaj Pich 2020-06-02 17:07:58 +02:00
commit 4b6b722f87
17 changed files with 333 additions and 168 deletions

View File

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

View File

@ -17,8 +17,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 17
targetSdkVersion 29
versionCode 60
versionName "0.18.1"
versionCode 61
versionName "0.18.2"
multiDexEnabled true
resValue "string", "app_name", "Wulkanowy"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -114,7 +114,7 @@ play {
ext {
work_manager = "2.3.4"
room = "2.2.5"
dagger = "2.27"
dagger = "2.28"
chucker = "3.2.0"
mockk = "1.9.2"
}
@ -124,7 +124,7 @@ configurations.all {
}
dependencies {
implementation "io.github.wulkanowy:sdk:0.18.1"
implementation "io.github.wulkanowy:sdk:0.18.2"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.core:core-ktx:1.2.0"

View File

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

View File

@ -1,5 +1,7 @@
package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.grade.GradeRepository
@ -58,15 +60,14 @@ class GradeAverageProvider @Inject constructor(
val isAnyAverage = summaries.any { it.average != .0 }
val allGrades = details.groupBy { it.subject }
summaries.map { summary ->
summaries.emulateEmptySummaries(student, semester, allGrades.toList(), isAnyAverage).map { summary ->
val grades = allGrades[summary.subject].orEmpty()
GradeDetailsWithAverage(
subject = summary.subject,
average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
grades.map {
if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier)
else it
}.calcAverage()
(if (student.loginMode == Sdk.Mode.SCRAPPER.name)
grades.map { it.changeModifier(plusModifier, minusModifier) }
else grades).calcAverage()
} else summary.average,
points = summary.pointsSum,
summary = summary,
@ -75,4 +76,26 @@ class GradeAverageProvider @Inject constructor(
}
}
}
private fun List<GradeSummary>.emulateEmptySummaries(student: Student, semester: Semester, grades: List<Pair<String, List<Grade>>>, calcAverage: Boolean): List<GradeSummary> {
if (isNotEmpty() && size == grades.size) return this
return grades.mapIndexed { i, (subject, details) ->
singleOrNull { it.subject == subject }?.let { return@mapIndexed it }
GradeSummary(
studentId = student.studentId,
semesterId = semester.semesterId,
position = i,
subject = subject,
predictedGrade = "",
finalGrade = "",
proposedPoints = "",
finalPoints = "",
pointsSum = "",
average = if (calcAverage) (if (student.loginMode == Sdk.Mode.SCRAPPER.name) {
details.map { it.changeModifier(plusModifier, minusModifier) }
} else details).calcAverage() else .0
)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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
@ -77,7 +78,9 @@ class MessageTabAdapter @Inject constructor() :
}
messageItemAttachmentIcon.visibility = if (item.hasAttachments) View.VISIBLE else View.GONE
root.setOnClickListener { onClickListener(item, holder.adapterPosition) }
root.setOnClickListener {
holder.adapterPosition.let { if (it != NO_POSITION) onClickListener(item, it) }
}
}
}

View File

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

View File

@ -1,8 +1,9 @@
Wersja 0.18.1
- naprawiliśmy sortowanie w ocenach
- naprawilismy wiele problemów ze stabilnością
- nazwy opcji w ustawieniach nie są już ucięte
- w zadaniach domowych wyświetlają się teraz pozycje na weekend
- wyłączyliśmy logowanie przez token (bo nie działa i nie wiadomo kiedy będzie działać)
Wersja 0.18.2
- naprawiliśmy zaznaczanie uczniów przy logowaniu
- naprawiliśmy odświeżanie planu lekcji na samsungach
- naprawiliśmy wysyłanie wiadomości
- poprawiliśmy oznaczanie nowych wiadomości jako przeczytanych
- w podsumowaniu ocen nie będą się już pokazywać „puste” przedmioty
- w polu pisania wiadomości pierwsza litera w zdaniu będzie teraz domyślnie duża
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

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

View File

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

View File

@ -290,8 +290,8 @@
<string name="logviewer_share">Поделиться логами</string>
<string name="logviewer_refresh">Обновить</string>
<!--Error dialog-->
<string name="dialog_error_check_update">Check for updates</string>
<string name="dialog_error_check_update_message">Before reporting a bug, check first if an update with the bug fix is available</string>
<string name="dialog_error_check_update">Проверить наличие обновлений</string>
<string name="dialog_error_check_update_message">Прежде чем сообщать об ошибке, проверьте наличие обновлений</string>
<!--Generic-->
<string name="all_content">Содержание</string>
<string name="all_retry">Повторить</string>

View File

@ -120,14 +120,14 @@
<string name="timetable_time">Години</string>
<string name="timetable_changes">Зміни</string>
<string name="timetable_no_items">Брак уроків у цей день</string>
<string name="timetable_minutes">%s min</string>
<string name="timetable_seconds">%s sec</string>
<string name="timetable_time_left">%1$s left</string>
<string name="timetable_time_until">in %1$s</string>
<string name="timetable_finished">Finished</string>
<string name="timetable_now">Now: %s</string>
<string name="timetable_next">Next: %s</string>
<string name="timetable_later">Later: %s</string>
<string name="timetable_minutes">%s хвилин</string>
<string name="timetable_seconds">%s сек</string>
<string name="timetable_time_left">%1$s лишилося</string>
<string name="timetable_time_until">через %1$s</string>
<string name="timetable_finished">Завершено</string>
<string name="timetable_now">Зараз: %s</string>
<string name="timetable_next">Наступний: %s</string>
<string name="timetable_later">Пізніше: %s</string>
<!--Completed lessons-->
<string name="completed_lessons_title">Уроки, що відбулися</string>
<string name="completed_lessons_button">Показати уроки, що відбулися</string>
@ -290,8 +290,8 @@
<string name="logviewer_share">Поділитися логами</string>
<string name="logviewer_refresh">Оновити</string>
<!--Error dialog-->
<string name="dialog_error_check_update">Check for updates</string>
<string name="dialog_error_check_update_message">Before reporting a bug, check first if an update with the bug fix is available</string>
<string name="dialog_error_check_update">Провірити наявність оновлень</string>
<string name="dialog_error_check_update_message">Перед тим, як повідомлювати о помілці, перевірте наявність оновлень</string>
<!--Generic-->
<string name="all_content">Зміст</string>
<string name="all_retry">Повторити</string>
@ -308,8 +308,8 @@
<string name="all_subject">Предмет</string>
<string name="all_prev">Попередній</string>
<string name="all_next">Наступний</string>
<string name="all_search">Search</string>
<string name="all_search_hint">Search...</string>
<string name="all_search">Пошук</string>
<string name="all_search_hint">Пошук...</string>
<!--Timetable Widget-->
<string name="widget_timetable_no_items">Брак уроків</string>
<string name="widget_timetable_theme_title">Увібрати тему</string>
@ -324,17 +324,17 @@
<string name="pref_view_present">Показувати присутність у відвідуваності</string>
<string name="pref_view_app_theme">Тема додатку</string>
<string name="pref_view_expand_grade">Більше оцінок</string>
<string name="pref_view_timetable_show_timers">Mark current lesson in timetable</string>
<string name="pref_view_grade_statistics_list">Show chart list in class grades</string>
<string name="pref_view_timetable_show_timers">Позначити поточний урок у розкладі</string>
<string name="pref_view_grade_statistics_list">Показувати діаграми в оцінках класу</string>
<string name="pref_view_timetable_show_whole_class">Показати уроки всього класу</string>
<string name="pref_view_grade_color_scheme">Схема кольорів оцінок</string>
<string name="pref_view_app_language">Мова додатку</string>
<string name="pref_notify_header">Повідомлення</string>
<string name="pref_notify_switch">Показувати повідомлення</string>
<string name="pref_notify_upcoming_lessons_switch">Show upcoming lesson notifications</string>
<string name="pref_notify_fix_sync_issues">Fix synchronization &amp; notifications issues</string>
<string name="pref_notify_fix_sync_issues_message">Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings.</string>
<string name="pref_notify_fix_sync_issues_settings_button">Go to settings</string>
<string name="pref_notify_upcoming_lessons_switch">Показувати повідомлення о наступних уроках</string>
<string name="pref_notify_fix_sync_issues">Виправити помилки з синхронізацією і повідомленнями</string>
<string name="pref_notify_fix_sync_issues_message">На вашому пристрої можуть бути помилки з синхронізацією і повідомленнями\n\nЩоб виправити іх, вам необхідно додати Wulkanowy в авто-старт и вимкнути оптимізацію/экономію батареї в налаштуваннях пристрою.</string>
<string name="pref_notify_fix_sync_issues_settings_button">Перейти до налаштувань</string>
<string name="pref_notify_debug_switch">Показувати дебаг-повідомлення</string>
<string name="pref_services_header">Синхронізація</string>
<string name="pref_services_switch">Автоматична синхронізація</string>
@ -360,7 +360,7 @@
<string name="channel_new_message">Нові повідомлення</string>
<string name="channel_new_notes">Нові нотатки</string>
<string name="channel_push">Показувати push-повідомлення</string>
<string name="channel_upcoming_lessons">Upcoming lessons</string>
<string name="channel_upcoming_lessons">Наступні уроки</string>
<string name="channel_debug">Дебаг</string>
<!--Colors-->
<string name="all_black">Чорний</string>

View File

@ -1,11 +1,11 @@
package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.createSemesterEntity
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.grade.GradeRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.createSemesterEntity
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.sdk.Sdk
import io.reactivex.Single
@ -84,8 +84,6 @@ class GradeAverageProviderTest {
fun setUp() {
MockitoAnnotations.initMocks(this)
`when`(preferencesRepository.gradeMinusModifier).thenReturn(.33)
`when`(preferencesRepository.gradePlusModifier).thenReturn(.33)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
@ -93,7 +91,83 @@ class GradeAverageProviderTest {
}
@Test
fun onlyOneSemesterTest() {
fun `force calc current semester average with default modifiers in scraper mode`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) // from details and after set custom plus/minus
}
@Test
fun `force calc current semester average with custom modifiers in scraper mode`() {
val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeMinusModifier).thenReturn(.33)
`when`(preferencesRepository.gradePlusModifier).thenReturn(.33)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) // from details and after set custom plus/minus
}
@Test
fun `force calc current semester average with custom modifiers in api mode`() {
val student = student.copy(loginMode = Sdk.Mode.API.name)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) // useless in this mode
`when`(preferencesRepository.gradePlusModifier).thenReturn(.33)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) // (from details): 3.375
}
@Test
fun `force calc current semester average with custom modifiers in hybrid mode`() {
val student = student.copy(loginMode = Sdk.Mode.HYBRID.name)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) // useless in this mode
`when`(preferencesRepository.gradePlusModifier).thenReturn(.33)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) // (from details): 3.375
}
@Test
fun `calc current semester average`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(2.9, items.single { it.subject == "Matematyka" }.average, .0) // from summary: 2,9
assertEquals(3.4, items.single { it.subject == "Fizyka" }.average, .0) // from details: 3,4
}
@Test
fun `force calc current semester average`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
@ -101,65 +175,12 @@ class GradeAverageProviderTest {
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(2.5, items.single { it.subject == "Matematyka" }.average, .0)
assertEquals(3.0, items.single { it.subject == "Fizyka" }.average, .0)
assertEquals(2.5, items.single { it.subject == "Matematyka" }.average, .0) // from details: 2,5
assertEquals(3.0, items.single { it.subject == "Fizyka" }.average, .0) // from details: 3,0
}
@Test
fun onlyOneSemester_gradesWithModifiers_default() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0)
}
@Test
fun onlyOneSemester_gradesWithModifiers_api() {
val student = student.copy(loginMode = Sdk.Mode.API.name)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0)
}
@Test
fun onlyOneSemester_gradesWithModifiers_scrapper() {
val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0)
}
@Test
fun onlyOneSemester_gradesWithModifiers_hybrid() {
val student = student.copy(loginMode = Sdk.Mode.HYBRID.name)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0)
}
@Test
fun allYearFirstSemesterTest() {
fun `force calc full year average when current is first`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries))
@ -167,33 +188,19 @@ class GradeAverageProviderTest {
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0)
assertEquals(3.5, items.single { it.subject == "Fizyka" }.average, .0)
}
@Test
fun allYearSecondSemesterTest() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0)
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0)
assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) // (from summary): 3,5
assertEquals(3.5, items.single { it.subject == "Fizyka" }.average, .0) // (from summary): 3,5
}
@Test(expected = IllegalArgumentException::class)
fun incorrectAverageModeTest() {
fun `calc average on invalid mode`() {
`when`(preferencesRepository.gradeAverageMode).thenReturn("test_mode")
gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).blockingGet()
}
@Test
fun allYearSemester_averageFromSummary() {
fun `calc full year average`() {
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to listOf(
@ -208,14 +215,14 @@ class GradeAverageProviderTest {
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.25, items.single { it.subject == "Matematyka" }.average, .0)
assertEquals(3.75, items.single { it.subject == "Fizyka" }.average, .0)
assertEquals(3.25, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 3,0 + 3,5 → 3,25
assertEquals(3.75, items.single { it.subject == "Fizyka" }.average, .0) // (from summaries ↑): 3,5 + 4,0 → 3,75
}
@Test
fun onlyOneSemester_averageFromSummary_forceCalc() {
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
fun `force calc full year average`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to listOf(
getSummary(22, "Matematyka", 1.1),
@ -225,8 +232,102 @@ class GradeAverageProviderTest {
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0)
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0)
assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25
}
@Test
fun `calc full year average when no summaries`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to emptyList()))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to emptyList()))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 2,5 + 3,5 → 3,0
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25
}
@Test
fun `force calc full year average when no summaries`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to emptyList()))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to emptyList()))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25
}
@Test
fun `calc full year average when missing summaries in both semesters`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to listOf(
getSummary(22, "Matematyka", 4.0)
)))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to listOf(
getSummary(23, "Matematyka", 3.0)
)))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 4,0 + 3,0 → 3,5
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25
}
@Test
fun `calc full year average when missing summary in second semester`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries.dropLast(1)))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.4, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries): 3,9 + 2,9 → 3,4
assertEquals(3.05, items.single { it.subject == "Fizyka" }.average, .0) // 3,1 (from summary) + 3,0 (from details) → 3,05
}
@Test
fun `calc full year average when missing summary in first semester`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries.dropLast(1)))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.4, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries): 3,9 + 2,9 → 3,4
assertEquals(3.45, items.single { it.subject == "Fizyka" }.average, .0) // 3,5 (from details) + 3,4 (from summary) → 3,45
}
@Test
fun `force calc full year average when missing summary in first semester`() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries.dropLast(1)))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size)
assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25
}
private fun getGrade(semesterId: Int, subject: String, value: Double, modifier: Double = 0.0): Grade {

View File

@ -1,6 +1,6 @@
buildscript {
ext.kotlin_version = '1.3.72'
ext.about_libraries = '8.1.3'
ext.about_libraries = '8.1.6'
repositories {
mavenCentral()
google()
@ -9,7 +9,7 @@ buildscript {
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools.build:gradle:3.6.3'
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1'
classpath "com.github.triplet.gradle:play-publisher:2.7.5"