mirror of
https://github.com/wulkanowy/wulkanowy.git
synced 2025-01-31 17:32:45 +01:00
Merge branch 'release/0.18.2'
This commit is contained in:
commit
4b6b722f87
@ -14,7 +14,7 @@ cache:
|
|||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- develop
|
- develop
|
||||||
- 0.18.1
|
- 0.18.2
|
||||||
|
|
||||||
android:
|
android:
|
||||||
licenses:
|
licenses:
|
||||||
|
@ -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"
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() } }
|
||||||
|
@ -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) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()))
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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>
|
||||||
|
@ -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 & 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>
|
||||||
|
@ -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 {
|
||||||
|
@ -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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user