[UI/Grades] Add "no grade" entity in USOS, count ECTS points by term

This commit is contained in:
Kuba Szczodrzyński 2025-02-01 22:24:27 +01:00
parent 88cd18b8c6
commit 29971777a7
No known key found for this signature in database
GPG Key ID: 43037AC62A600562
9 changed files with 82 additions and 26 deletions

View File

@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NO_GRADE
import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
@ -40,13 +41,13 @@ class UsosApiExamReports(
tag = TAG, tag = TAG,
service = "examrep/user2", service = "examrep/user2",
fields = listOf( fields = listOf(
"id",
"type_description", "type_description",
"course_unit" to listOf("id", "course_name"), "course_unit" to listOf("id", "course_name"),
"sessions" to listOf( "sessions" to listOf(
"number",
"description", "description",
"issuer_grades" to listOf( "issuer_grades" to listOf(
"exam_id",
"exam_session_number",
"value_symbol", "value_symbol",
// "value_description", // "value_description",
"passes", "passes",
@ -93,6 +94,8 @@ class UsosApiExamReports(
} }
private fun processExamReport(termId: String, courseId: String, examReport: JsonObject) { private fun processExamReport(termId: String, courseId: String, examReport: JsonObject) {
val examId = examReport.getString("id")?.toIntOrNull()
?: return
val typeDescription = examReport.getLangString("type_description") val typeDescription = examReport.getLangString("type_description")
val courseUnit = examReport.getJsonObject("course_unit") val courseUnit = examReport.getJsonObject("course_unit")
?: return ?: return
@ -103,16 +106,26 @@ class UsosApiExamReports(
val sessions = examReport.getJsonArray("sessions") val sessions = examReport.getJsonArray("sessions")
?: return ?: return
val gradeCategory = data.gradeCategories[courseUnitId]
val classType = gradeCategory?.columns?.get(0)
val subject = data.getSubject(
id = null,
name = courseName,
shortName = courseId,
)
var hasGrade = false
for (sessionEl in sessions) { for (sessionEl in sessions) {
if (!sessionEl.isJsonObject) if (!sessionEl.isJsonObject)
continue continue
val session = sessionEl.asJsonObject val session = sessionEl.asJsonObject
val sessionNumber = session.getInt("number") ?: continue
val sessionDescription = session.getLangString("description") val sessionDescription = session.getLangString("description")
val issuerGrade = session.getJsonObject("issuer_grades") ?: continue val issuerGrade = session.getJsonObject("issuer_grades")
val examId = issuerGrade.getInt("exam_id") ?: continue
val sessionNumber = issuerGrade.getInt("exam_session_number") ?: continue
val valueSymbol = issuerGrade.getString("value_symbol") ?: continue val valueSymbol = issuerGrade.getString("value_symbol") ?: continue
val passes = issuerGrade.getBoolean("passes") val passes = issuerGrade.getBoolean("passes")
val countsIntoAverage = issuerGrade.getString("counts_into_average") ?: "T" val countsIntoAverage = issuerGrade.getString("counts_into_average") ?: "T"
@ -121,8 +134,6 @@ class UsosApiExamReports(
?.getLong("id") ?: -1L ?.getLong("id") ?: -1L
val comment = issuerGrade.getString("comment") val comment = issuerGrade.getString("comment")
val gradeCategory = data.gradeCategories[courseUnitId]
val classType = gradeCategory?.columns?.get(0)
val value = valueSymbol.toFloatOrNull() ?: 0.0f val value = valueSymbol.toFloatOrNull() ?: 0.0f
if (termId !in data.termNames) { if (termId !in data.termNames) {
@ -142,13 +153,10 @@ class UsosApiExamReports(
comment = termId, comment = termId,
semester = 1, semester = 1,
teacherId = modificationAuthorId, teacherId = modificationAuthorId,
subjectId = data.getSubject( subjectId = subject.id,
id = null,
name = courseName,
shortName = courseId,
).id,
addedDate = Date.fromIso(dateModified), addedDate = Date.fromIso(dateModified),
) )
hasGrade = true
if (sessionNumber > 1) { if (sessionNumber > 1) {
val origId = examId * 10L + sessionNumber - 1 val origId = examId * 10L + sessionNumber - 1
@ -170,5 +178,35 @@ class UsosApiExamReports(
) )
) )
} }
if (!hasGrade) {
// add an "empty" grade for the exam
val gradeObject = Grade(
profileId = profileId,
id = examId * 10L,
name = "...",
type = TYPE_NO_GRADE,
value = 0.0f,
weight = 0.0f,
color = 0xFFBABABD.toInt(),
category = typeDescription,
description = classType,
comment = termId,
semester = 1,
teacherId = -1L,
subjectId = subject.id,
addedDate = 0,
)
data.gradeList.add(gradeObject)
data.metadataList.add(
Metadata(
profileId,
MetadataType.GRADE,
gradeObject.id,
true,
true,
)
)
}
} }
} }

View File

@ -55,6 +55,7 @@ open class Grade(
const val TYPE_DESCRIPTIVE = 30 const val TYPE_DESCRIPTIVE = 30
const val TYPE_DESCRIPTIVE_TEXT = 31 const val TYPE_DESCRIPTIVE_TEXT = 31
const val TYPE_TEXT = 40 const val TYPE_TEXT = 40
const val TYPE_NO_GRADE = 100
} }
@ColumnInfo(name = "gradeValueMax") @ColumnInfo(name = "gradeValueMax")

View File

@ -16,6 +16,7 @@ import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NO_GRADE
import pl.szczodrzynski.edziennik.data.db.full.GradeFull import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.ext.onClick import pl.szczodrzynski.edziennik.ext.onClick
import pl.szczodrzynski.edziennik.ext.startCoroutineTimer import pl.szczodrzynski.edziennik.ext.startCoroutineTimer
@ -235,10 +236,13 @@ class GradesAdapter(
} }
} }
if (item !is GradeFull || onGradeClick != null) if (item !is GradeFull || (onGradeClick != null && item.type != TYPE_NO_GRADE)) {
holder.itemView.setOnClickListener(onClickListener) holder.itemView.setOnClickListener(onClickListener)
else holder.itemView.isEnabled = true
} else {
holder.itemView.setOnClickListener(null) holder.itemView.setOnClickListener(null)
holder.itemView.isEnabled = false
}
} }
fun notifyItemChanged(model: Any) { fun notifyItemChanged(model: Any) {

View File

@ -18,6 +18,7 @@ import com.mikepenz.iconics.typeface.library.community.material.CommunityMateria
import kotlinx.coroutines.* import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NO_GRADE
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
import pl.szczodrzynski.edziennik.data.db.full.GradeFull import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.databinding.GradesListFragmentBinding import pl.szczodrzynski.edziennik.databinding.GradesListFragmentBinding
@ -305,18 +306,24 @@ class GradesListFragment : Fragment(), CoroutineScope {
val semesterCount = mutableListOf<Float>() val semesterCount = mutableListOf<Float>()
val totalSum = mutableListOf<Float>() val totalSum = mutableListOf<Float>()
val totalCount = mutableListOf<Float>() val totalCount = mutableListOf<Float>()
val ectsPoints = mutableMapOf<Long, Float>() val ectsPoints = mutableMapOf<Pair<Long, String?>, Float>()
for (grade in grades) { for (grade in grades) {
val pointsPair = grade.subjectId to grade.comment
if (grade.type == TYPE_NO_GRADE)
// reset points if there's an exam that isn't passed yet
ectsPoints[pointsPair] = 0.0f
if (grade.value == 0.0f || grade.weight == 0.0f) if (grade.value == 0.0f || grade.weight == 0.0f)
continue continue
totalSum.add(grade.value * grade.weight) totalSum.add(grade.value * grade.weight)
totalCount.add(grade.weight) totalCount.add(grade.weight)
if (grade.value < 3.0) if (grade.value < 3.0)
// exam not passed, reset points for this subject // exam not passed, reset points for this subject
ectsPoints[grade.subjectId] = 0.0f ectsPoints[pointsPair] = 0.0f
else if (grade.subjectId !in ectsPoints) else if (pointsPair !in ectsPoints)
// no points for this subject, simply assign // no points for this subject, simply assign
ectsPoints[grade.subjectId] = grade.weight ectsPoints[pointsPair] = grade.weight
if (filterTermId != null && grade.comment != filterTermId) if (filterTermId != null && grade.comment != filterTermId)
continue continue

View File

@ -11,6 +11,7 @@ import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NO_GRADE
import pl.szczodrzynski.edziennik.data.db.full.GradeFull import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.databinding.GradesItemGradeBinding import pl.szczodrzynski.edziennik.databinding.GradesItemGradeBinding
import pl.szczodrzynski.edziennik.ui.grades.GradesAdapter import pl.szczodrzynski.edziennik.ui.grades.GradesAdapter
@ -59,6 +60,9 @@ class GradeViewHolder(
b.gradeWeight.isVisible = weightText != null b.gradeWeight.isVisible = weightText != null
b.gradeTeacherName.text = grade.teacherName b.gradeTeacherName.text = grade.teacherName
if (grade.addedDate == 0L || grade.type == TYPE_NO_GRADE)
b.gradeAddedDate.text = null
else
b.gradeAddedDate.text = Date.fromMillis(grade.addedDate).let { b.gradeAddedDate.text = Date.fromMillis(grade.addedDate).let {
it.getRelativeString(app, 5) ?: it.formattedStringShort it.getRelativeString(app, 5) ?: it.formattedStringShort
} }

View File

@ -25,6 +25,7 @@ import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NO_GRADE
import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Subject import pl.szczodrzynski.edziennik.data.db.entity.Subject
import pl.szczodrzynski.edziennik.data.db.full.GradeFull import pl.szczodrzynski.edziennik.data.db.full.GradeFull
@ -70,7 +71,7 @@ class HomeGradesCard(
app.db.gradeDao().getAllFromDate(profile.id, sevenDaysAgo).observe(fragment, Observer { app.db.gradeDao().getAllFromDate(profile.id, sevenDaysAgo).observe(fragment, Observer {
grades.apply { grades.apply {
clear() clear()
addAll(it) addAll(it.filter { it.type != TYPE_NO_GRADE })
} }
update() update()
}) })

View File

@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NO_GRADE
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_AVG import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_AVG
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_SUM import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_SUM
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_FINAL import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_FINAL
@ -90,6 +91,7 @@ class GradesManager(val app: App) : CoroutineScope {
else else
context.getString(R.string.grades_weight_format, format.format(grade.weight)) context.getString(R.string.grades_weight_format, format.format(grade.weight))
TYPE_POINT_AVG -> context.getString(R.string.grades_max_points_format, format.format(grade.valueMax)) TYPE_POINT_AVG -> context.getString(R.string.grades_max_points_format, format.format(grade.valueMax))
TYPE_NO_GRADE -> context.getString(R.string.grades_weight_no_grade)
else -> null else -> null
} }

View File

@ -350,16 +350,14 @@
android:layout_width="1dp" android:layout_width="1dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:background="@drawable/divider" android:background="@drawable/divider" />
android:visibility="gone" />
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:orientation="vertical" android:orientation="vertical">
android:visibility="gone">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -500,6 +500,7 @@
<string name="grades_value_format">wartość: %s</string> <string name="grades_value_format">wartość: %s</string>
<string name="grades_weight_format">waga %s</string> <string name="grades_weight_format">waga %s</string>
<string name="grades_weight_not_counted">nie liczona do śr.</string> <string name="grades_weight_not_counted">nie liczona do śr.</string>
<string name="grades_weight_no_grade">brak oceny</string>
<string name="grades_year_average_format">koniec roku: %s</string> <string name="grades_year_average_format">koniec roku: %s</string>
<string name="grades_year_average_percent_format">koniec roku: %s%%</string> <string name="grades_year_average_percent_format">koniec roku: %s%%</string>
<string name="grades_year_average_point_format">koniec roku: %spkt</string> <string name="grades_year_average_point_format">koniec roku: %spkt</string>