mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-02-08 00:54:37 +01:00
[API/Usos] Implement basic grades support
This commit is contained in:
parent
514fbafd00
commit
d44b85073a
@ -10,10 +10,12 @@ import pl.szczodrzynski.edziennik.data.db.enums.FeatureType
|
|||||||
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
||||||
|
|
||||||
const val ENDPOINT_USOS_API_USER = 7000
|
const val ENDPOINT_USOS_API_USER = 7000
|
||||||
const val ENDPOINT_USOS_API_TERMS = 7010
|
const val ENDPOINT_USOS_API_TERMS = 7010
|
||||||
const val ENDPOINT_USOS_API_COURSES = 7020
|
const val ENDPOINT_USOS_API_COURSES = 7020
|
||||||
const val ENDPOINT_USOS_API_TIMETABLE = 7030
|
const val ENDPOINT_USOS_API_TIMETABLE = 7030
|
||||||
|
const val ENDPOINT_USOS_API_ECTS_POINTS = 7040
|
||||||
|
const val ENDPOINT_USOS_API_EXAM_REPORTS = 7050
|
||||||
|
|
||||||
val UsosFeatures = listOf(
|
val UsosFeatures = listOf(
|
||||||
/*
|
/*
|
||||||
@ -39,4 +41,12 @@ val UsosFeatures = listOf(
|
|||||||
Feature(LoginType.USOS, FeatureType.TIMETABLE, listOf(
|
Feature(LoginType.USOS, FeatureType.TIMETABLE, listOf(
|
||||||
ENDPOINT_USOS_API_TIMETABLE to LoginMethod.USOS_API,
|
ENDPOINT_USOS_API_TIMETABLE to LoginMethod.USOS_API,
|
||||||
)),
|
)),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Grades
|
||||||
|
*/
|
||||||
|
Feature(LoginType.USOS, FeatureType.GRADES, listOf(
|
||||||
|
ENDPOINT_USOS_API_ECTS_POINTS to LoginMethod.USOS_API,
|
||||||
|
ENDPOINT_USOS_API_EXAM_REPORTS to LoginMethod.USOS_API,
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
|
@ -8,6 +8,8 @@ import pl.szczodrzynski.edziennik.R
|
|||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.template.data.web.TemplateWebSample
|
import pl.szczodrzynski.edziennik.data.api.edziennik.template.data.web.TemplateWebSample
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.*
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiCourses
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiCourses
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiEctsPoints
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiExamReports
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiTerms
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiTerms
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiTimetable
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiTimetable
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiUser
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiUser
|
||||||
@ -58,6 +60,14 @@ class UsosData(val data: DataUsos, val onSuccess: () -> Unit) {
|
|||||||
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
|
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
|
||||||
UsosApiTimetable(data, lastSync, onSuccess)
|
UsosApiTimetable(data, lastSync, onSuccess)
|
||||||
}
|
}
|
||||||
|
ENDPOINT_USOS_API_ECTS_POINTS -> {
|
||||||
|
data.startProgress(R.string.edziennik_progress_endpoint_grade_categories)
|
||||||
|
UsosApiEctsPoints(data, lastSync, onSuccess)
|
||||||
|
}
|
||||||
|
ENDPOINT_USOS_API_EXAM_REPORTS -> {
|
||||||
|
data.startProgress(R.string.edziennik_progress_endpoint_grades)
|
||||||
|
UsosApiExamReports(data, lastSync, onSuccess)
|
||||||
|
}
|
||||||
else -> onSuccess(endpointId)
|
else -> onSuccess(endpointId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2025-1-31.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_INCOMPLETE_RESPONSE
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_ECTS_POINTS
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi
|
||||||
|
import pl.szczodrzynski.edziennik.ext.DAY
|
||||||
|
import pl.szczodrzynski.edziennik.ext.filter
|
||||||
|
|
||||||
|
class UsosApiEctsPoints(
|
||||||
|
override val data: DataUsos,
|
||||||
|
override val lastSync: Long?,
|
||||||
|
val onSuccess: (endpointId: Int) -> Unit,
|
||||||
|
) : UsosApi(data, lastSync) {
|
||||||
|
companion object {
|
||||||
|
const val TAG = "UsosApiEctsPoints"
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
apiRequest<JsonObject>(
|
||||||
|
tag = TAG,
|
||||||
|
service = "courses/user_ects_points",
|
||||||
|
responseType = ResponseType.OBJECT,
|
||||||
|
) { json, response ->
|
||||||
|
if (!processResponse(json)) {
|
||||||
|
data.error(TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response)
|
||||||
|
return@apiRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setSyncNext(ENDPOINT_USOS_API_ECTS_POINTS, 2 * DAY)
|
||||||
|
onSuccess(ENDPOINT_USOS_API_ECTS_POINTS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processResponse(json: JsonObject): Boolean {
|
||||||
|
for ((_, coursePointsEl) in json.entrySet()) {
|
||||||
|
if (!coursePointsEl.isJsonObject)
|
||||||
|
continue
|
||||||
|
for ((courseId, pointsEl) in coursePointsEl.asJsonObject.entrySet()) {
|
||||||
|
if (!pointsEl.isJsonPrimitive)
|
||||||
|
continue
|
||||||
|
val gradeCategories = data.gradeCategories
|
||||||
|
.filter { it.text == courseId }
|
||||||
|
gradeCategories.forEach {
|
||||||
|
it.weight = pointsEl.asString.toFloatOrNull() ?: -1.0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2025-1-31.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_INCOMPLETE_RESPONSE
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_EXAM_REPORTS
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
|
||||||
|
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.Metadata
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getBoolean
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getInt
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getJsonArray
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getJsonObject
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getLong
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getString
|
||||||
|
import pl.szczodrzynski.edziennik.ext.join
|
||||||
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
|
||||||
|
class UsosApiExamReports(
|
||||||
|
override val data: DataUsos,
|
||||||
|
override val lastSync: Long?,
|
||||||
|
val onSuccess: (endpointId: Int) -> Unit,
|
||||||
|
) : UsosApi(data, lastSync) {
|
||||||
|
companion object {
|
||||||
|
const val TAG = "UsosApiExamReports"
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
apiRequest<JsonObject>(
|
||||||
|
tag = TAG,
|
||||||
|
service = "examrep/user2",
|
||||||
|
fields = listOf(
|
||||||
|
"type_description",
|
||||||
|
"course_unit" to listOf("id", "course_name"),
|
||||||
|
"sessions" to listOf(
|
||||||
|
"description",
|
||||||
|
"issuer_grades" to listOf(
|
||||||
|
"exam_id",
|
||||||
|
"exam_session_number",
|
||||||
|
"value_symbol",
|
||||||
|
// "value_description",
|
||||||
|
"passes",
|
||||||
|
"counts_into_average",
|
||||||
|
"date_modified",
|
||||||
|
"modification_author",
|
||||||
|
"comment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responseType = ResponseType.OBJECT,
|
||||||
|
) { json, response ->
|
||||||
|
if (!processResponse(json)) {
|
||||||
|
data.error(TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response)
|
||||||
|
return@apiRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
data.toRemove.add(DataRemoveModel.Grades.all())
|
||||||
|
data.setSyncNext(ENDPOINT_USOS_API_EXAM_REPORTS, SYNC_ALWAYS)
|
||||||
|
onSuccess(ENDPOINT_USOS_API_EXAM_REPORTS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processResponse(json: JsonObject): Boolean {
|
||||||
|
for ((_, courseEditionEl) in json.entrySet()) {
|
||||||
|
if (!courseEditionEl.isJsonObject)
|
||||||
|
continue
|
||||||
|
for ((courseId, examReportsEl) in courseEditionEl.asJsonObject.entrySet()) {
|
||||||
|
if (!examReportsEl.isJsonArray)
|
||||||
|
continue
|
||||||
|
for (examReportEl in examReportsEl.asJsonArray) {
|
||||||
|
if (!examReportEl.isJsonObject)
|
||||||
|
continue
|
||||||
|
val examReport = examReportEl.asJsonObject
|
||||||
|
processExamReport(courseId, examReport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processExamReport(courseId: String, examReport: JsonObject) {
|
||||||
|
val typeDescription = examReport.getLangString("type_description")
|
||||||
|
val courseUnit = examReport.getJsonObject("course_unit")
|
||||||
|
?: return
|
||||||
|
val courseUnitId = courseUnit.getString("id")?.toLongOrNull()
|
||||||
|
?: return
|
||||||
|
val courseName = courseUnit.getLangString("course_name")
|
||||||
|
?: return
|
||||||
|
val sessions = examReport.getJsonArray("sessions")
|
||||||
|
?: return
|
||||||
|
|
||||||
|
for (sessionEl in sessions) {
|
||||||
|
if (!sessionEl.isJsonObject)
|
||||||
|
continue
|
||||||
|
val session = sessionEl.asJsonObject
|
||||||
|
|
||||||
|
val sessionDescription = session.getLangString("description")
|
||||||
|
val issuerGrade = session.getJsonObject("issuer_grades") ?: continue
|
||||||
|
|
||||||
|
val examId = issuerGrade.getInt("exam_id") ?: continue
|
||||||
|
val sessionNumber = issuerGrade.getInt("exam_session_number") ?: continue
|
||||||
|
val valueSymbol = issuerGrade.getString("value_symbol") ?: continue
|
||||||
|
val passes = issuerGrade.getBoolean("passes")
|
||||||
|
val countsIntoAverage = issuerGrade.getString("counts_into_average") ?: "T"
|
||||||
|
val dateModified = issuerGrade.getString("date_modified")
|
||||||
|
val modificationAuthorId = issuerGrade.getJsonObject("modification_author")
|
||||||
|
?.getLong("id") ?: -1L
|
||||||
|
val comment = issuerGrade.getString("comment")
|
||||||
|
|
||||||
|
val gradeCategory = data.gradeCategories[courseUnitId]
|
||||||
|
val classType = gradeCategory?.columns?.get(0)
|
||||||
|
val value = valueSymbol.toFloatOrNull() ?: 0.0f
|
||||||
|
|
||||||
|
val gradeObject = Grade(
|
||||||
|
profileId = profileId,
|
||||||
|
id = examId * 10L + sessionNumber,
|
||||||
|
name = valueSymbol,
|
||||||
|
type = TYPE_NORMAL,
|
||||||
|
value = value,
|
||||||
|
weight = if (countsIntoAverage == "T") gradeCategory?.weight ?: 0.0f else 0.0f,
|
||||||
|
color = (if (passes == true) 0xFF465FB3 else 0xFFB71C1C).toInt(),
|
||||||
|
category = typeDescription,
|
||||||
|
description = listOfNotNull(classType, sessionDescription).join(" - "),
|
||||||
|
comment = comment,
|
||||||
|
semester = 1,
|
||||||
|
teacherId = modificationAuthorId,
|
||||||
|
subjectId = data.getSubject(
|
||||||
|
id = null,
|
||||||
|
name = courseName,
|
||||||
|
shortName = courseId,
|
||||||
|
).id,
|
||||||
|
addedDate = Date.fromIso(dateModified),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (sessionNumber > 1) {
|
||||||
|
val origId = examId * 10L + sessionNumber - 1
|
||||||
|
val grades = data.gradeList.filter { it.id == origId }
|
||||||
|
grades.firstOrNull()?.parentId = gradeObject.id
|
||||||
|
gradeObject.isImprovement = true
|
||||||
|
}
|
||||||
|
|
||||||
|
data.gradeList.add(gradeObject)
|
||||||
|
data.metadataList.add(
|
||||||
|
Metadata(
|
||||||
|
profileId,
|
||||||
|
MetadataType.GRADE,
|
||||||
|
gradeObject.id,
|
||||||
|
profile?.empty ?: false,
|
||||||
|
profile?.empty ?: false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -93,6 +93,7 @@ internal val FEATURES_PODLASIE = setOf(
|
|||||||
internal val FEATURES_USOS = setOf(
|
internal val FEATURES_USOS = setOf(
|
||||||
TIMETABLE,
|
TIMETABLE,
|
||||||
AGENDA,
|
AGENDA,
|
||||||
|
GRADES,
|
||||||
|
|
||||||
STUDENT_INFO,
|
STUDENT_INFO,
|
||||||
STUDENT_NUMBER,
|
STUDENT_NUMBER,
|
||||||
|
@ -15,6 +15,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL
|
|||||||
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
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.enums.SchoolType
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
|
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
|
||||||
import pl.szczodrzynski.edziennik.ext.asColoredSpannable
|
import pl.szczodrzynski.edziennik.ext.asColoredSpannable
|
||||||
import pl.szczodrzynski.edziennik.ext.get
|
import pl.szczodrzynski.edziennik.ext.get
|
||||||
@ -158,6 +159,19 @@ class GradesManager(val app: App) : CoroutineScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
type == TYPE_NORMAL && defColor -> grade.color and 0xffffff
|
type == TYPE_NORMAL && defColor -> grade.color and 0xffffff
|
||||||
|
type == TYPE_NORMAL && app.profile.loginStoreType.schoolType == SchoolType.UNIVERSITY -> {
|
||||||
|
when (grade.name.lowercase()) {
|
||||||
|
"zal" -> 0x4caf50
|
||||||
|
"nb", "nk" -> 0xff7043
|
||||||
|
"2.0", "nzal" -> 0xff3d00
|
||||||
|
"3.0" -> 0xffff00
|
||||||
|
"3.5" -> 0xc6ff00
|
||||||
|
"4.0" -> 0x76ff03
|
||||||
|
"4.5" -> 0x64dd17
|
||||||
|
"5.0" -> 0x00c853
|
||||||
|
else -> grade.color and 0xffffff
|
||||||
|
}
|
||||||
|
}
|
||||||
type in TYPE_NORMAL..TYPE_YEAR_FINAL -> {
|
type in TYPE_NORMAL..TYPE_YEAR_FINAL -> {
|
||||||
when (grade.name.lowercase()) {
|
when (grade.name.lowercase()) {
|
||||||
"+", "++", "+++" -> 0x4caf50
|
"+", "++", "+++" -> 0x4caf50
|
||||||
|
Loading…
x
Reference in New Issue
Block a user