diff --git a/.travis.yml b/.travis.yml index d858cf3e..4bb84282 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ cache: branches: only: - develop - - 0.18.1 + - 0.18.2 android: licenses: diff --git a/app/build.gradle b/app/build.gradle index 31cada74..e2aab017 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt index 5374c476..54b245dd 100644 --- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt @@ -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, 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") } 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 954b5756..9582a5db 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 @@ -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.emulateEmptySummaries(student: Student, semester: Semester, grades: List>>, calcAverage: Boolean): List { + 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 + ) + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt index d5e05f3b..c129d948 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt @@ -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() - private var expandedPosition = RecyclerView.NO_POSITION + private var expandedPosition = NO_POSITION private var isExpandable = false @@ -35,7 +36,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter 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) { + private fun refreshList(newItems: MutableList) { 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 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): List { 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() + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt index 7383a5ec..3332f2be 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt @@ -12,7 +12,13 @@ import javax.inject.Inject class LoginStudentSelectAdapter @Inject constructor() : RecyclerView.Adapter() { + private val checkedList = mutableMapOf() + var items = emptyList>() + 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 } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt index b2d0aed9..db25c0da 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt @@ -22,7 +22,7 @@ class LoginStudentSelectPresenter @Inject constructor( var students = emptyList() - private var selectedStudents = mutableListOf() + private val selectedStudents = mutableListOf() fun onAttachView(view: LoginStudentSelectView, students: Serializable?) { super.onAttachView(view) @@ -69,6 +69,7 @@ class LoginStudentSelectPresenter @Inject constructor( } private fun loadData(students: List) { + 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) { disposable.add(studentRepository.saveStudents(students) .map { students.first().apply { id = it.first() } } 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 6064f10a..ece6773f 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 @@ -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) } + } } } 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 7243061d..92aeb581 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,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())) 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 d61f1b97..1908d2e2 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -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 diff --git a/app/src/main/res/layout/activity_send_message.xml b/app/src/main/res/layout/activity_send_message.xml index f59dcabd..e0ec52bb 100644 --- a/app/src/main/res/layout/activity_send_message.xml +++ b/app/src/main/res/layout/activity_send_message.xml @@ -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" diff --git a/app/src/main/res/layout/fragment_login_student_select.xml b/app/src/main/res/layout/fragment_login_student_select.xml index 64e06cbd..f9c6c157 100644 --- a/app/src/main/res/layout/fragment_login_student_select.xml +++ b/app/src/main/res/layout/fragment_login_student_select.xml @@ -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"> + android:layout_marginRight="16dp" + android:orientation="horizontal"> + @@ -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" diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c0c75196..6eb8f774 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -290,8 +290,8 @@ Поделиться логами Обновить - Check for updates - Before reporting a bug, check first if an update with the bug fix is available + Проверить наличие обновлений + Прежде чем сообщать об ошибке, проверьте наличие обновлений Содержание Повторить diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index a63e5682..ec4dbc15 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -120,14 +120,14 @@ Години Зміни Брак уроків у цей день - %s min - %s sec - %1$s left - in %1$s - Finished - Now: %s - Next: %s - Later: %s + %s хвилин + %s сек + %1$s лишилося + через %1$s + Завершено + Зараз: %s + Наступний: %s + Пізніше: %s Уроки, що відбулися Показати уроки, що відбулися @@ -290,8 +290,8 @@ Поділитися логами Оновити - Check for updates - Before reporting a bug, check first if an update with the bug fix is available + Провірити наявність оновлень + Перед тим, як повідомлювати о помілці, перевірте наявність оновлень Зміст Повторити @@ -308,8 +308,8 @@ Предмет Попередній Наступний - Search - Search... + Пошук + Пошук... Брак уроків Увібрати тему @@ -324,17 +324,17 @@ Показувати присутність у відвідуваності Тема додатку Більше оцінок - Mark current lesson in timetable - Show chart list in class grades + Позначити поточний урок у розкладі + Показувати діаграми в оцінках класу Показати уроки всього класу Схема кольорів оцінок Мова додатку Повідомлення Показувати повідомлення - Show upcoming lesson notifications - Fix synchronization & notifications issues - 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. - Go to settings + Показувати повідомлення о наступних уроках + Виправити помилки з синхронізацією і повідомленнями + На вашому пристрої можуть бути помилки з синхронізацією і повідомленнями\n\nЩоб виправити іх, вам необхідно додати Wulkanowy в авто-старт и вимкнути оптимізацію/экономію батареї в налаштуваннях пристрою. + Перейти до налаштувань Показувати дебаг-повідомлення Синхронізація Автоматична синхронізація @@ -360,7 +360,7 @@ Нові повідомлення Нові нотатки Показувати push-повідомлення - Upcoming lessons + Наступні уроки Дебаг Чорний diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt index fcc30593..ee8634ca 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt @@ -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 { diff --git a/build.gradle b/build.gradle index 696e778f..7e5759d1 100644 --- a/build.gradle +++ b/build.gradle @@ -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"