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: branches:
only: only:
- develop - develop
- 0.18.1 - 0.18.2
android: android:
licenses: licenses:

View File

@ -17,8 +17,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 17 minSdkVersion 17
targetSdkVersion 29 targetSdkVersion 29
versionCode 60 versionCode 61
versionName "0.18.1" versionName "0.18.2"
multiDexEnabled true multiDexEnabled true
resValue "string", "app_name", "Wulkanowy" resValue "string", "app_name", "Wulkanowy"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -114,7 +114,7 @@ play {
ext { ext {
work_manager = "2.3.4" work_manager = "2.3.4"
room = "2.2.5" room = "2.2.5"
dagger = "2.27" dagger = "2.28"
chucker = "3.2.0" chucker = "3.2.0"
mockk = "1.9.2" mockk = "1.9.2"
} }
@ -124,7 +124,7 @@ configurations.all {
} }
dependencies { 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 "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.core:core-ktx:1.2.0" 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
import android.app.AlarmManager.RTC_WAKEUP import android.app.AlarmManager.RTC_WAKEUP
import android.app.PendingIntent import android.app.PendingIntent
import android.app.PendingIntent.FLAG_CANCEL_CURRENT import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.core.app.AlarmManagerCompat import androidx.core.app.AlarmManagerCompat
@ -55,7 +55,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) { private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
if (now() in range) cancelNotification() if (now() in range) cancelNotification()
alarmManager.cancel(PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_CANCEL_CURRENT)) alarmManager.cancel(PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT))
} }
fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id) fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
@ -102,7 +102,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also { PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id) it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
it.putExtra(LESSON_TYPE, notificationType) it.putExtra(LESSON_TYPE, notificationType)
}, FLAG_CANCEL_CURRENT) }, FLAG_UPDATE_CURRENT)
) )
Timber.d("TimetableNotification scheduled: type: $notificationType, subject: ${intent.getStringExtra(LESSON_TITLE)}, start: $time, student: $studentId") Timber.d("TimetableNotification scheduled: type: $notificationType, subject: ${intent.getStringExtra(LESSON_TITLE)}, start: $time, student: $studentId")
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
import androidx.recyclerview.widget.SortedList import androidx.recyclerview.widget.SortedList
import androidx.recyclerview.widget.SortedListAdapterCallback import androidx.recyclerview.widget.SortedListAdapterCallback
import io.github.wulkanowy.R import io.github.wulkanowy.R
@ -77,7 +78,9 @@ class MessageTabAdapter @Inject constructor() :
} }
messageItemAttachmentIcon.visibility = if (item.hasAttachments) View.VISIBLE else View.GONE messageItemAttachmentIcon.visibility = if (item.hasAttachments) View.VISIBLE else View.GONE
root.setOnClickListener { onClickListener(item, holder.adapterPosition) } root.setOnClickListener {
holder.adapterPosition.let { if (it != NO_POSITION) onClickListener(item, it) }
}
} }
} }

View File

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

View File

@ -1,8 +1,9 @@
Wersja 0.18.1 Wersja 0.18.2
- naprawiliśmy sortowanie w ocenach - naprawiliśmy zaznaczanie uczniów przy logowaniu
- naprawilismy wiele problemów ze stabilnością - naprawiliśmy odświeżanie planu lekcji na samsungach
- nazwy opcji w ustawieniach nie są już ucięte - naprawiliśmy wysyłanie wiadomości
- w zadaniach domowych wyświetlają się teraz pozycje na weekend - poprawiliśmy oznaczanie nowych wiadomości jako przeczytanych
- wyłączyliśmy logowanie przez token (bo nie działa i nie wiadomo kiedy będzie działać) - 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 Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,11 @@
package io.github.wulkanowy.ui.modules.grade package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.createSemesterEntity
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.grade.GradeRepository import io.github.wulkanowy.data.repositories.grade.GradeRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.createSemesterEntity
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.reactivex.Single import io.reactivex.Single
@ -84,8 +84,6 @@ class GradeAverageProviderTest {
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
`when`(preferencesRepository.gradeMinusModifier).thenReturn(.33)
`when`(preferencesRepository.gradePlusModifier).thenReturn(.33)
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters)) `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
@ -93,7 +91,83 @@ class GradeAverageProviderTest {
} }
@Test @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.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester") `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries)) `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
@ -101,65 +175,12 @@ class GradeAverageProviderTest {
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size) assertEquals(2, items.size)
assertEquals(2.5, items.single { it.subject == "Matematyka" }.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) assertEquals(3.0, items.single { it.subject == "Fizyka" }.average, .0) // from details: 3,0
} }
@Test @Test
fun onlyOneSemester_gradesWithModifiers_default() { fun `force calc full year average when current is first`() {
`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() {
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries)) `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries))
@ -167,33 +188,19 @@ class GradeAverageProviderTest {
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId).blockingGet() val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId).blockingGet()
assertEquals(2, items.size) assertEquals(2, items.size)
assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) // (from summary): 3,5
assertEquals(3.5, items.single { it.subject == "Fizyka" }.average, .0) assertEquals(3.5, items.single { it.subject == "Fizyka" }.average, .0) // (from summary): 3,5
}
@Test
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)
} }
@Test(expected = IllegalArgumentException::class) @Test(expected = IllegalArgumentException::class)
fun incorrectAverageModeTest() { fun `calc average on invalid mode`() {
`when`(preferencesRepository.gradeAverageMode).thenReturn("test_mode") `when`(preferencesRepository.gradeAverageMode).thenReturn("test_mode")
gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).blockingGet() gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).blockingGet()
} }
@Test @Test
fun allYearSemester_averageFromSummary() { fun `calc full year average`() {
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year") `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false) `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to listOf( `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to listOf(
@ -208,14 +215,14 @@ class GradeAverageProviderTest {
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size) assertEquals(2, items.size)
assertEquals(3.25, items.single { it.subject == "Matematyka" }.average, .0) assertEquals(3.25, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 3,0 + 3,5 → 3,25
assertEquals(3.75, items.single { it.subject == "Fizyka" }.average, .0) assertEquals(3.75, items.single { it.subject == "Fizyka" }.average, .0) // (from summaries ↑): 3,5 + 4,0 → 3,75
} }
@Test @Test
fun onlyOneSemester_averageFromSummary_forceCalc() { fun `force calc full year average`() {
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true) `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
`when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
`when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries)) `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries))
`when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to listOf( `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to listOf(
getSummary(22, "Matematyka", 1.1), getSummary(22, "Matematyka", 1.1),
@ -225,8 +232,102 @@ class GradeAverageProviderTest {
val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet() val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
assertEquals(2, items.size) assertEquals(2, items.size)
assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0
assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25
}
@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 { private fun getGrade(semesterId: Int, subject: String, value: Double, modifier: Double = 0.0): Grade {

View File

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