[UI/Grades] Update views for university grades

This commit is contained in:
Kuba Szczodrzyński 2025-01-31 23:10:07 +01:00
parent 6de7ee9cee
commit 30a77f1a99
No known key found for this signature in database
GPG Key ID: 43037AC62A600562
9 changed files with 209 additions and 22 deletions

View File

@ -153,7 +153,9 @@ class UsosApiExamReports(
if (sessionNumber > 1) { if (sessionNumber > 1) {
val origId = examId * 10L + sessionNumber - 1 val origId = examId * 10L + sessionNumber - 1
val grades = data.gradeList.filter { it.id == origId } val grades = data.gradeList.filter { it.id == origId }
grades.firstOrNull()?.parentId = gradeObject.id val improvedGrade = grades.firstOrNull()
improvedGrade?.parentId = gradeObject.id
improvedGrade?.weight = 0.0f
gradeObject.isImprovement = true gradeObject.isImprovement = true
} }

View File

@ -134,6 +134,7 @@ class GradesAdapter(
if (model.state == STATE_CLOSED) { if (model.state == STATE_CLOSED) {
val subItems = when { val subItems = when {
model is GradesSubject && manager.isUniversity -> listOf()
model is GradesSemester && model.grades.isEmpty() -> model is GradesSemester && model.grades.isEmpty() ->
listOf(GradesEmpty()) listOf(GradesEmpty())
model is GradesSemester && manager.hideImproved -> model is GradesSemester && manager.hideImproved ->
@ -147,10 +148,12 @@ class GradesAdapter(
if (notifyAdapter) notifyItemInserted(position) if (notifyAdapter) notifyItemInserted(position)
} }
position++
model.state = STATE_OPENED model.state = STATE_OPENED
items.addAll(position, subItems.filterNotNull()) if (subItems.isNotEmpty()) {
if (notifyAdapter) notifyItemRangeInserted(position, subItems.size) position++
items.addAll(position, subItems.filterNotNull())
if (notifyAdapter) notifyItemRangeInserted(position, subItems.size)
}
if (model is GradesSubject) { if (model is GradesSubject) {
// auto expand first semester // auto expand first semester

View File

@ -258,8 +258,40 @@ class GradesListFragment : Fragment(), CoroutineScope {
subject.lastAddedDate = max(subject.lastAddedDate, grade.addedDate) subject.lastAddedDate = max(subject.lastAddedDate, grade.addedDate)
} }
when (manager.orderBy) {
GradesManager.ORDER_BY_DATE_DESC -> items.sortByDescending { it.lastAddedDate }
GradesManager.ORDER_BY_DATE_ASC -> items.sortBy { it.lastAddedDate }
}
val stats = GradesStats() val stats = GradesStats()
if (manager.isUniversity) {
val semesterSum = mutableListOf<Float>()
val semesterCount = mutableListOf<Float>()
val totalSum = mutableListOf<Float>()
val totalCount = mutableListOf<Float>()
val ectsPoints = mutableMapOf<Long, Float>()
for (grade in grades) {
if (grade.value == 0.0f || grade.weight == 0.0f)
continue
totalSum.add(grade.value * grade.weight)
totalCount.add(grade.weight)
if (grade.value < 3.0)
// exam not passed, reset points for this subject
ectsPoints[grade.subjectId] = 0.0f
else if (grade.subjectId !in ectsPoints)
// no points for this subject, simply assign
ectsPoints[grade.subjectId] = grade.weight
semesterSum.add(grade.value * grade.weight)
semesterCount.add(grade.weight)
}
stats.universitySem = semesterSum.sum() / semesterCount.sum()
stats.universityTotal = totalSum.sum() / totalCount.sum()
stats.universityEcts = ectsPoints.values.sum()
return (items + stats).toMutableList()
}
val sem1Expected = mutableListOf<Float>() val sem1Expected = mutableListOf<Float>()
val sem2Expected = mutableListOf<Float>() val sem2Expected = mutableListOf<Float>()
val yearlyExpected = mutableListOf<Float>() val yearlyExpected = mutableListOf<Float>()
@ -330,11 +362,6 @@ class GradesListFragment : Fragment(), CoroutineScope {
stats.pointSem2 = sem2Point.averageOrNull()?.toFloat() ?: 0f stats.pointSem2 = sem2Point.averageOrNull()?.toFloat() ?: 0f
stats.pointYearly = yearlyPoint.averageOrNull()?.toFloat() ?: 0f stats.pointYearly = yearlyPoint.averageOrNull()?.toFloat() ?: 0f
when (manager.orderBy) {
GradesManager.ORDER_BY_DATE_DESC -> items.sortByDescending { it.lastAddedDate }
GradesManager.ORDER_BY_DATE_ASC -> items.sortBy { it.lastAddedDate }
}
return (items + stats).toMutableList() return (items + stats).toMutableList()
} }

View File

@ -22,4 +22,8 @@ class GradesStats {
var pointSem1 = 0f var pointSem1 = 0f
var pointSem2 = 0f var pointSem2 = 0f
var pointYearly = 0f var pointYearly = 0f
var universitySem = 0f
var universityTotal = 0f
var universityEcts = 0f
} }

View File

@ -31,9 +31,35 @@ class StatsViewHolder(
override fun onBind(activity: AppCompatActivity, app: App, item: GradesStats, position: Int, adapter: GradesAdapter) { override fun onBind(activity: AppCompatActivity, app: App, item: GradesStats, position: Int, adapter: GradesAdapter) {
val manager = app.gradesManager val manager = app.gradesManager
val isUniversity = manager.isUniversity
val showAverages = mutableListOf<Int>() val showAverages = mutableListOf<Int>()
val showPoint = mutableListOf<Int>() val showPoint = mutableListOf<Int>()
b.universityTitle.isVisible = isUniversity
b.universityLayout.isVisible = isUniversity
b.universityDivider.isVisible = isUniversity
if (isUniversity) {
val format = DecimalFormat("#.00")
b.normalTitle.isVisible = false
b.normalLayout.isVisible = false
b.normalDivider.isVisible = false
b.helpButton.isVisible = false
b.pointTitle.isVisible = false
b.pointLayout.isVisible = false
b.pointDivider.isVisible = false
b.noData.isVisible = false
b.disclaimer.isVisible = true
b.customValueDivider.isVisible = false
b.customValueLayout.isVisible = false
b.universitySemester.text = format.format(item.universitySem)
b.universityTotal.text = format.format(item.universityTotal)
b.universityEcts.text = format.format(item.universityEcts)
return
}
getSemesterString(app, item.normalSem1, item.normalSem1Proposed, item.normalSem1Final, item.sem1NotAllFinal).let { (average, notice) -> getSemesterString(app, item.normalSem1, item.normalSem1Proposed, item.normalSem1Final, item.sem1NotAllFinal).let { (average, notice) ->
b.normalSemester1Layout.isVisible = average != null b.normalSemester1Layout.isVisible = average != null
b.normalSemester1Notice.isVisible = notice != null b.normalSemester1Notice.isVisible = notice != null

View File

@ -62,9 +62,24 @@ class SubjectViewHolder(
val firstSemester = item.semesters.firstOrNull() ?: return val firstSemester = item.semesters.firstOrNull() ?: return
b.yearSummary.text = manager.getYearSummaryString(app, item.semesters.map { it.grades.size }.sum(), item.averages) if (manager.isUniversity) {
val ectsPoints = item.semesters.firstOrNull()?.grades?.maxOf { it.weight }
b.yearSummary.text = if (ectsPoints != null)
contextWrapper.getString(
R.string.grades_ects_points_format,
ectsPoints
)
else
null
} else {
b.yearSummary.text = manager.getYearSummaryString(
app,
item.semesters.map { it.grades.size }.sum(),
item.averages
)
}
if (firstSemester.number != item.semester) { if (firstSemester.number != item.semester && !manager.isUniversity) {
b.gradesContainer.addView(TextView(contextWrapper).apply { b.gradesContainer.addView(TextView(contextWrapper).apply {
setTextColor(android.R.attr.textColorSecondary.resolveAttr(context)) setTextColor(android.R.attr.textColorSecondary.resolveAttr(context))
setText(R.string.grades_preview_other_semester, firstSemester.number) setText(R.string.grades_preview_other_semester, firstSemester.number)
@ -92,16 +107,18 @@ class SubjectViewHolder(
)) ))
} }
b.previewContainer.addView(TextView(contextWrapper).apply { if (!manager.isUniversity) {
setTextColor(android.R.attr.textColorSecondary.resolveAttr(context)) b.previewContainer.addView(TextView(contextWrapper).apply {
text = manager.getAverageString(app, firstSemester.averages, nameSemester = true, showSemester = firstSemester.number) setTextColor(android.R.attr.textColorSecondary.resolveAttr(context))
//gravity = Gravity.END text = manager.getAverageString(app, firstSemester.averages, nameSemester = true, showSemester = firstSemester.number)
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { //gravity = Gravity.END
setMargins(0, 0, 8.dp, 0) layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
} setMargins(0, 0, 8.dp, 0)
maxLines = 1 }
ellipsize = TextUtils.TruncateAt.END maxLines = 1
}) ellipsize = TextUtils.TruncateAt.END
})
}
// add the topmost semester's grades to preview container (collapsed) // add the topmost semester's grades to preview container (collapsed)
firstSemester.proposedGrade?.let { firstSemester.proposedGrade?.let {
@ -139,7 +156,7 @@ class SubjectViewHolder(
} }
// if showing semester 2, add yearly grades to preview container (collapsed) // if showing semester 2, add yearly grades to preview container (collapsed)
if (firstSemester.number == item.semester) { if (firstSemester.number == item.semester && !manager.isUniversity) {
b.previewContainer.addView(TextView(contextWrapper).apply { b.previewContainer.addView(TextView(contextWrapper).apply {
text = manager.getAverageString(app, item.averages, nameSemester = true) text = manager.getAverageString(app, item.averages, nameSemester = true)
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {

View File

@ -70,6 +70,8 @@ class GradesManager(val app: App) : CoroutineScope {
get() = app.profile.config.grades.hideImproved get() = app.profile.config.grades.hideImproved
val averageWithoutWeight val averageWithoutWeight
get() = app.profile.config.grades.averageWithoutWeight get() = app.profile.config.grades.averageWithoutWeight
val isUniversity
get() = app.profile.loginStoreType.schoolType == SchoolType.UNIVERSITY
fun getOrderByString() = when (orderBy) { fun getOrderByString() = when (orderBy) {

View File

@ -283,6 +283,107 @@
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:background="@drawable/divider" /> android:background="@drawable/divider" />
<TextView
android:id="@+id/universityTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:text="@string/grades_stats_university"
android:textAppearance="@style/NavView.TextView.Subtitle" />
<LinearLayout
android:id="@+id/universityLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingHorizontal="8dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/grades_stats_this_semester" />
<TextView
android:id="@+id/universitySemester"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:textSize="24sp"
tools:text="4,56" />
</LinearLayout>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:background="@drawable/divider" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/grades_stats_all_grades" />
<TextView
android:id="@+id/universityTotal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:textSize="24sp"
tools:text="4,67" />
</LinearLayout>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:background="@drawable/divider"
android:visibility="gone" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/grades_stats_ects_points" />
<TextView
android:id="@+id/universityEcts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:textSize="24sp"
tools:text="120" />
</LinearLayout>
</LinearLayout>
<View
android:id="@+id/universityDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="@drawable/divider" />
<TextView <TextView
android:id="@+id/disclaimer" android:id="@+id/disclaimer"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -448,6 +448,7 @@
<string name="grades_config_minus_value">Własna wartość minusa</string> <string name="grades_config_minus_value">Własna wartość minusa</string>
<string name="grades_config_plus_value">Własna wartość plusa</string> <string name="grades_config_plus_value">Własna wartość plusa</string>
<string name="grades_config_title">Konfiguracja ocen</string> <string name="grades_config_title">Konfiguracja ocen</string>
<string name="grades_ects_points_format">Punkty ECTS: %#.2f</string>
<string name="grades_editor_add_grade">Dodaj ocenę</string> <string name="grades_editor_add_grade">Dodaj ocenę</string>
<string name="grades_editor_add_grade_title">Dodawanie oceny</string> <string name="grades_editor_add_grade_title">Dodawanie oceny</string>
<string name="grades_editor_add_grade_weight">Podaj wagę oceny</string> <string name="grades_editor_add_grade_weight">Podaj wagę oceny</string>
@ -474,9 +475,11 @@
<string name="grades_semester_average_point_format">semestr %d: %spkt</string> <string name="grades_semester_average_point_format">semestr %d: %spkt</string>
<string name="grades_semester_format">Semestr %d</string> <string name="grades_semester_format">Semestr %d</string>
<string name="grades_semester_header_format">Semestr %d</string> <string name="grades_semester_header_format">Semestr %d</string>
<string name="grades_stats_all_grades">wszystkie oceny</string>
<string name="grades_stats_custom_config_notice">Aktualne ustawienia ocen mogą wpływać na średnią. Jeśli uważasz, że się ona nie zgadza, kliknij Konfiguruj.</string> <string name="grades_stats_custom_config_notice">Aktualne ustawienia ocen mogą wpływać na średnią. Jeśli uważasz, że się ona nie zgadza, kliknij Konfiguruj.</string>
<string name="grades_stats_custom_value_notice">Została ustawiona własna wartość plusa/minusa. Jeśli uważasz, że się ona nie zgadza, kliknij Konfiguruj.</string> <string name="grades_stats_custom_value_notice">Została ustawiona własna wartość plusa/minusa. Jeśli uważasz, że się ona nie zgadza, kliknij Konfiguruj.</string>
<string name="grades_stats_disclaimer">*średnie ocen są poglądowe i mogą się różnić, w zależności od ustawień szkoły</string> <string name="grades_stats_disclaimer">*średnie ocen są poglądowe i mogą się różnić, w zależności od ustawień szkoły</string>
<string name="grades_stats_ects_points">punkty ECTS</string>
<string name="grades_stats_expected">*przewidywana średnia</string> <string name="grades_stats_expected">*przewidywana średnia</string>
<string name="grades_stats_from_final">*z ocen końcowych\nPrzewidywana: %s</string> <string name="grades_stats_from_final">*z ocen końcowych\nPrzewidywana: %s</string>
<string name="grades_stats_from_final_no_expected">*z ocen końcowych</string> <string name="grades_stats_from_final_no_expected">*z ocen końcowych</string>
@ -490,7 +493,9 @@
<string name="grades_stats_proposed_avg">Śr. ocen proponowanych:\n%s</string> <string name="grades_stats_proposed_avg">Śr. ocen proponowanych:\n%s</string>
<string name="grades_stats_semester_1">semestr 1</string> <string name="grades_stats_semester_1">semestr 1</string>
<string name="grades_stats_semester_2">semestr 2</string> <string name="grades_stats_semester_2">semestr 2</string>
<string name="grades_stats_this_semester">ten semestr</string>
<string name="grades_stats_title">Statystyka ocen</string> <string name="grades_stats_title">Statystyka ocen</string>
<string name="grades_stats_university">Średnia ocen za studia</string>
<string name="grades_stats_yearly">całoroczna</string> <string name="grades_stats_yearly">całoroczna</string>
<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>