mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-02-08 09:04:36 +01:00
Merge branch 'develop-v4'
This commit is contained in:
commit
54693bf25e
2
.github/workflows/_build.yml
vendored
2
.github/workflows/_build.yml
vendored
@ -183,7 +183,7 @@ jobs:
|
|||||||
run: python $GITHUB_WORKSPACE/.github/utils/webhook_discord.py $GITHUB_WORKSPACE >> $GITHUB_OUTPUT
|
run: python $GITHUB_WORKSPACE/.github/utils/webhook_discord.py $GITHUB_WORKSPACE >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Upload workflow artifact
|
- name: Upload workflow artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.changelog.outputs.appVersionName }}
|
name: ${{ steps.changelog.outputs.appVersionName }}
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -1,7 +1,7 @@
|
|||||||
name: Release
|
name: Release
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags: ["v*.*.*"]
|
tags: ["v*.*"]
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build release (APK)
|
name: Build release (APK)
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
<h3>Wersja 4.13.7, 2024-07-08</h3>
|
<h3>Wersja 4.14, 2025-02-02</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Dodano opcję uruchomienia aplikacji bez logowania.</li>
|
<li>USOS: <b>dodano obsługę ocen</b>.</li>
|
||||||
|
<li>USOS: obliczanie średniej za studia oraz punktów ECTS.</li>
|
||||||
|
<li>USOS: poprawiono brak planu zajęć po rozpoczęciu roku.</li>
|
||||||
|
<li>Wyłączono archiwizator profili.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
Dzięki za korzystanie ze Szkolnego!<br>
|
Dzięki za korzystanie ze Szkolnego!<br>
|
||||||
<i>© [Kuba Szczodrzyński](@kuba2k2) 2023</i>
|
<i>© [Kuba Szczodrzyński](@kuba2k2) 2025</i>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
/*secret password - removed for source code publication*/
|
/*secret password - removed for source code publication*/
|
||||||
static toys AES_IV[16] = {
|
static toys AES_IV[16] = {
|
||||||
0x0e, 0x87, 0x6d, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
0xee, 0x23, 0xf1, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||||
|
|
||||||
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
|
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
|
||||||
|
|
||||||
|
@ -241,6 +241,14 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
|||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
config.migrate(this@App)
|
config.migrate(this@App)
|
||||||
|
|
||||||
|
if (config.appVersionCore < BuildConfig.VERSION_CODE) {
|
||||||
|
// force syncing all endpoints on update
|
||||||
|
db.endpointTimerDao().clear()
|
||||||
|
config.sync.lastAppSync = 0L
|
||||||
|
config.hash = "invalid"
|
||||||
|
config.appVersionCore = BuildConfig.VERSION_CODE
|
||||||
|
}
|
||||||
|
|
||||||
SSLProviderInstaller.install(applicationContext, this@App::buildHttp)
|
SSLProviderInstaller.install(applicationContext, this@App::buildHttp)
|
||||||
|
|
||||||
if (config.devModePassword != null)
|
if (config.devModePassword != null)
|
||||||
|
@ -43,6 +43,7 @@ class Config(db: AppDb) : BaseConfig(db) {
|
|||||||
var appInstalledTime by config<Long>(0L)
|
var appInstalledTime by config<Long>(0L)
|
||||||
var appRateSnackbarTime by config<Long>(0L)
|
var appRateSnackbarTime by config<Long>(0L)
|
||||||
var appVersion by config<Int>(BuildConfig.VERSION_CODE)
|
var appVersion by config<Int>(BuildConfig.VERSION_CODE)
|
||||||
|
var appVersionCore by config<Int>(0)
|
||||||
var validation by config<String?>(null, "buildValidation")
|
var validation by config<String?>(null, "buildValidation")
|
||||||
|
|
||||||
var archiverEnabled by config<Boolean>(true)
|
var archiverEnabled by config<Boolean>(true)
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.config
|
package pl.szczodrzynski.edziennik.config
|
||||||
|
|
||||||
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.UNIVERSITY_AVERAGE_MODE_ECTS
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
|
||||||
|
|
||||||
@ -15,8 +16,11 @@ class ProfileConfigGrades(base: ProfileConfig) {
|
|||||||
var dontCountEnabled by base.config<Boolean>(false)
|
var dontCountEnabled by base.config<Boolean>(false)
|
||||||
var dontCountGrades by base.config<List<String>> { listOf() }
|
var dontCountGrades by base.config<List<String>> { listOf() }
|
||||||
var hideImproved by base.config<Boolean>(false)
|
var hideImproved by base.config<Boolean>(false)
|
||||||
|
var hideNoGrade by base.config<Boolean>(false)
|
||||||
var hideSticksFromOld by base.config<Boolean>(false)
|
var hideSticksFromOld by base.config<Boolean>(false)
|
||||||
var minusValue by base.config<Float?>(null)
|
var minusValue by base.config<Float?>(null)
|
||||||
var plusValue by base.config<Float?>(null)
|
var plusValue by base.config<Float?>(null)
|
||||||
var yearAverageMode by base.config<Int>(YEAR_ALL_GRADES)
|
var yearAverageMode by base.config<Int>(YEAR_ALL_GRADES)
|
||||||
|
var universityAverageMode by base.config<Int>(UNIVERSITY_AVERAGE_MODE_ECTS)
|
||||||
|
var countEctsInProgress by base.config<Boolean>(false)
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,11 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (profile?.empty == true) {
|
||||||
|
// force app sync on first login
|
||||||
|
app.config.sync.lastAppSync = 0L
|
||||||
|
}
|
||||||
|
|
||||||
edziennikInterface = when (loginStore.type) {
|
edziennikInterface = when (loginStore.type) {
|
||||||
LoginType.LIBRUS -> Librus(app, profile, loginStore, taskCallback)
|
LoginType.LIBRUS -> Librus(app, profile, loginStore, taskCallback)
|
||||||
LoginType.MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback)
|
LoginType.MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback)
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.data.api.edziennik.usos
|
package pl.szczodrzynski.edziennik.data.api.edziennik.usos
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.data.api.models.Data
|
import pl.szczodrzynski.edziennik.data.api.models.Data
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
||||||
@ -73,4 +74,9 @@ class DataUsos(
|
|||||||
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", 0); return mStudentId ?: 0 }
|
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", 0); return mStudentId ?: 0 }
|
||||||
set(value) { profile["studentId"] = value; mStudentId = value }
|
set(value) { profile["studentId"] = value; mStudentId = value }
|
||||||
private var mStudentId: Int? = null
|
private var mStudentId: Int? = null
|
||||||
|
|
||||||
|
var termNames: Map<String, String> = mapOf()
|
||||||
|
get() { mTermNames = mTermNames ?: profile?.getStudentData("termNames", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mTermNames ?: mapOf() }
|
||||||
|
set(value) { profile["termNames"] = app.gson.toJson(value); mTermNames = value }
|
||||||
|
private var mTermNames: Map<String, String>? = null
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ 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.DataUsos
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_COURSES
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_COURSES
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Team
|
import pl.szczodrzynski.edziennik.data.db.entity.Team
|
||||||
import pl.szczodrzynski.edziennik.ext.*
|
import pl.szczodrzynski.edziennik.ext.*
|
||||||
|
|
||||||
@ -25,17 +26,20 @@ class UsosApiCourses(
|
|||||||
apiRequest<JsonObject>(
|
apiRequest<JsonObject>(
|
||||||
tag = TAG,
|
tag = TAG,
|
||||||
service = "courses/user",
|
service = "courses/user",
|
||||||
|
params = mapOf(
|
||||||
|
"active_terms_only" to false,
|
||||||
|
),
|
||||||
fields = listOf(
|
fields = listOf(
|
||||||
// "terms" to listOf("id", "name", "start_date", "end_date"),
|
// "terms" to listOf("id", "name", "start_date", "end_date"),
|
||||||
"course_editions" to listOf(
|
"course_editions" to listOf(
|
||||||
"course_id",
|
"course_id",
|
||||||
"course_name",
|
"course_name",
|
||||||
// "term_id",
|
|
||||||
"user_groups" to listOf(
|
"user_groups" to listOf(
|
||||||
"course_unit_id",
|
"course_unit_id",
|
||||||
"group_number",
|
"group_number",
|
||||||
// "class_type",
|
"class_type",
|
||||||
"class_type_id",
|
"class_type_id",
|
||||||
|
"term_id",
|
||||||
"lecturers",
|
"lecturers",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -63,22 +67,38 @@ class UsosApiCourses(
|
|||||||
for (courseEdition in courseEditions) {
|
for (courseEdition in courseEditions) {
|
||||||
val courseId = courseEdition.getString("course_id") ?: continue
|
val courseId = courseEdition.getString("course_id") ?: continue
|
||||||
val courseName = courseEdition.getLangString("course_name") ?: continue
|
val courseName = courseEdition.getLangString("course_name") ?: continue
|
||||||
val userGroups = courseEdition.getJsonArray("user_groups")?.asJsonObjectList() ?: continue
|
val userGroups =
|
||||||
|
courseEdition.getJsonArray("user_groups")?.asJsonObjectList() ?: continue
|
||||||
for (userGroup in userGroups) {
|
for (userGroup in userGroups) {
|
||||||
val courseUnitId = userGroup.getLong("course_unit_id") ?: continue
|
val courseUnitId = userGroup.getLong("course_unit_id") ?: continue
|
||||||
val groupNumber = userGroup.getInt("group_number") ?: continue
|
val groupNumber = userGroup.getInt("group_number") ?: continue
|
||||||
// val classType = userGroup.getLangString("class_type") ?: continue
|
val classType = userGroup.getLangString("class_type") ?: continue
|
||||||
val classTypeId = userGroup.getString("class_type_id") ?: continue
|
val classTypeId = userGroup.getString("class_type_id") ?: continue
|
||||||
|
val termId = userGroup.getString("term_id") ?: continue
|
||||||
val lecturers = userGroup.getLecturerIds("lecturers")
|
val lecturers = userGroup.getLecturerIds("lecturers")
|
||||||
|
|
||||||
data.teamList.put(courseUnitId, Team(
|
data.teamList.put(
|
||||||
|
courseUnitId, Team(
|
||||||
profileId,
|
profileId,
|
||||||
courseUnitId,
|
courseUnitId,
|
||||||
"${profile?.studentClassName} $classTypeId$groupNumber - $courseName",
|
"${profile?.studentClassName} $courseName ($classTypeId$groupNumber)",
|
||||||
2,
|
2,
|
||||||
"${data.schoolId}:${courseId} $classTypeId$groupNumber",
|
"${data.schoolId}:${termId}:${courseId} $classTypeId$groupNumber",
|
||||||
lecturers.firstOrNull() ?: -1L,
|
lecturers.firstOrNull() ?: -1L,
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val gradeCategory = data.gradeCategories[courseUnitId]
|
||||||
|
data.gradeCategories.put(
|
||||||
|
courseUnitId, GradeCategory(
|
||||||
|
profileId,
|
||||||
|
courseUnitId,
|
||||||
|
gradeCategory?.weight ?: -1.0f,
|
||||||
|
0,
|
||||||
|
courseId,
|
||||||
|
).addColumn(classType)
|
||||||
|
)
|
||||||
|
|
||||||
hasValidTeam = true
|
hasValidTeam = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,212 @@
|
|||||||
|
/*
|
||||||
|
* 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.Grade.Companion.TYPE_NO_GRADE
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val missingTermNames = mutableSetOf<String>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
apiRequest<JsonObject>(
|
||||||
|
tag = TAG,
|
||||||
|
service = "examrep/user2",
|
||||||
|
fields = listOf(
|
||||||
|
"id",
|
||||||
|
"type_description",
|
||||||
|
"course_unit" to listOf("id", "course_name"),
|
||||||
|
"sessions" to listOf(
|
||||||
|
"number",
|
||||||
|
"description",
|
||||||
|
"issuer_grades" to listOf(
|
||||||
|
"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)
|
||||||
|
|
||||||
|
if (missingTermNames.isEmpty())
|
||||||
|
onSuccess(ENDPOINT_USOS_API_EXAM_REPORTS)
|
||||||
|
else
|
||||||
|
UsosApiTerms(data, lastSync, onSuccess, missingTermNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processResponse(json: JsonObject): Boolean {
|
||||||
|
for ((termId, 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(termId, courseId, examReport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processExamReport(termId: String, courseId: String, examReport: JsonObject) {
|
||||||
|
val examId = examReport.getString("id")?.toIntOrNull()
|
||||||
|
?: return
|
||||||
|
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
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (!sessionEl.isJsonObject)
|
||||||
|
continue
|
||||||
|
val session = sessionEl.asJsonObject
|
||||||
|
|
||||||
|
val sessionNumber = session.getInt("number") ?: continue
|
||||||
|
val sessionDescription = session.getLangString("description")
|
||||||
|
val issuerGrade = session.getJsonObject("issuer_grades")
|
||||||
|
|
||||||
|
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 value = valueSymbol.toFloatOrNull() ?: 0.0f
|
||||||
|
|
||||||
|
if (termId !in data.termNames) {
|
||||||
|
missingTermNames.add(termId)
|
||||||
|
}
|
||||||
|
|
||||||
|
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, comment).join(" - "),
|
||||||
|
comment = termId,
|
||||||
|
semester = 1,
|
||||||
|
teacherId = modificationAuthorId,
|
||||||
|
subjectId = subject.id,
|
||||||
|
addedDate = Date.fromIso(dateModified),
|
||||||
|
)
|
||||||
|
hasGrade = true
|
||||||
|
|
||||||
|
if (sessionNumber > 1) {
|
||||||
|
val origId = examId * 10L + sessionNumber - 1
|
||||||
|
val grades = data.gradeList.filter { it.id == origId }
|
||||||
|
val improvedGrade = grades.firstOrNull()
|
||||||
|
improvedGrade?.parentId = gradeObject.id
|
||||||
|
improvedGrade?.weight = 0.0f
|
||||||
|
gradeObject.isImprovement = true
|
||||||
|
}
|
||||||
|
|
||||||
|
data.gradeList.add(gradeObject)
|
||||||
|
data.metadataList.add(
|
||||||
|
Metadata(
|
||||||
|
profileId,
|
||||||
|
MetadataType.GRADE,
|
||||||
|
gradeObject.id,
|
||||||
|
profile?.empty ?: false,
|
||||||
|
profile?.empty ?: false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@
|
|||||||
package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api
|
package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api
|
||||||
|
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_INCOMPLETE_RESPONSE
|
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.DataUsos
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_TERMS
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_TERMS
|
||||||
@ -16,43 +17,81 @@ class UsosApiTerms(
|
|||||||
override val data: DataUsos,
|
override val data: DataUsos,
|
||||||
override val lastSync: Long?,
|
override val lastSync: Long?,
|
||||||
val onSuccess: (endpointId: Int) -> Unit,
|
val onSuccess: (endpointId: Int) -> Unit,
|
||||||
|
names: Set<String>? = null,
|
||||||
) : UsosApi(data, lastSync) {
|
) : UsosApi(data, lastSync) {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "UsosApiTerms"
|
const val TAG = "UsosApiTerms"
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
apiRequest<JsonArray>(
|
if (names != null) {
|
||||||
|
apiRequest<JsonObject>(
|
||||||
tag = TAG,
|
tag = TAG,
|
||||||
service = "terms/search",
|
service = "terms/terms",
|
||||||
params = mapOf(
|
params = mapOf("term_ids" to names.joinToString("|")),
|
||||||
"query" to Date.getToday().year.toString(),
|
responseType = ResponseType.OBJECT,
|
||||||
),
|
|
||||||
responseType = ResponseType.ARRAY,
|
|
||||||
) { json, response ->
|
) { json, response ->
|
||||||
if (!processResponse(json)) {
|
if (!processResponse(json.entrySet().map { it.value.asJsonObject })) {
|
||||||
data.error(TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response)
|
data.error(TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response)
|
||||||
return@apiRequest
|
return@apiRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
data.setSyncNext(ENDPOINT_USOS_API_TERMS, 7 * DAY)
|
data.setSyncNext(ENDPOINT_USOS_API_TERMS, 2 * DAY)
|
||||||
|
onSuccess(ENDPOINT_USOS_API_TERMS)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
apiRequest<JsonArray>(
|
||||||
|
tag = TAG,
|
||||||
|
service = "terms/search",
|
||||||
|
params = mapOf("query" to Date.getToday().year.toString()),
|
||||||
|
responseType = ResponseType.ARRAY,
|
||||||
|
) { json, response ->
|
||||||
|
if (!processResponse(json.asJsonObjectList())) {
|
||||||
|
data.error(TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response)
|
||||||
|
return@apiRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setSyncNext(ENDPOINT_USOS_API_TERMS, 2 * DAY)
|
||||||
onSuccess(ENDPOINT_USOS_API_TERMS)
|
onSuccess(ENDPOINT_USOS_API_TERMS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun processResponse(json: JsonArray): Boolean {
|
private fun processResponse(terms: List<JsonObject>): Boolean {
|
||||||
|
val profile = profile ?: return false
|
||||||
|
val termNames = data.termNames.toMutableMap()
|
||||||
val today = Date.getToday()
|
val today = Date.getToday()
|
||||||
for (term in json.asJsonObjectList()) {
|
for (term in terms) {
|
||||||
|
val id = term.getString("id")
|
||||||
|
val name = term.getLangString("name")
|
||||||
|
val orderKey = term.getInt("order_key")
|
||||||
|
if (id != null && name != null)
|
||||||
|
termNames[id] = "$orderKey$$name"
|
||||||
|
|
||||||
if (!term.getBoolean("is_active", false))
|
if (!term.getBoolean("is_active", false))
|
||||||
continue
|
continue
|
||||||
val startDate = term.getString("start_date")?.let { Date.fromY_m_d(it) } ?: continue
|
val startDate = term.getString("start_date")?.let { Date.fromY_m_d(it) } ?: continue
|
||||||
val finishDate = term.getString("finish_date")?.let { Date.fromY_m_d(it) } ?: continue
|
val finishDate = term.getString("finish_date")?.let { Date.fromY_m_d(it) } ?: continue
|
||||||
if (today in startDate..finishDate) {
|
if (today !in startDate..finishDate)
|
||||||
profile?.studentSchoolYearStart = startDate.year
|
continue
|
||||||
profile?.dateSemester1Start = startDate
|
|
||||||
profile?.dateSemester2Start = finishDate
|
if (startDate.month >= 8)
|
||||||
}
|
profile.dateSemester1Start = startDate
|
||||||
|
else
|
||||||
|
profile.dateSemester2Start = startDate
|
||||||
|
|
||||||
|
if (finishDate.month >= 8)
|
||||||
|
profile.dateYearEnd = finishDate
|
||||||
|
else
|
||||||
|
profile.dateSemester2Start = finishDate
|
||||||
}
|
}
|
||||||
|
// update school year start
|
||||||
|
profile.studentSchoolYearStart = profile.dateSemester1Start.year
|
||||||
|
// update year end date if there is a new year
|
||||||
|
if (profile.dateYearEnd <= profile.dateSemester1Start)
|
||||||
|
profile.dateYearEnd =
|
||||||
|
profile.dateSemester1Start.clone().setYear(profile.dateSemester1Start.year + 1)
|
||||||
|
data.termNames = termNames
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,8 @@ class UsosApiUser(
|
|||||||
"last_name",
|
"last_name",
|
||||||
"student_number",
|
"student_number",
|
||||||
"student_programmes" to listOf(
|
"student_programmes" to listOf(
|
||||||
"programme" to listOf("id"),
|
"id",
|
||||||
|
"programme" to listOf("id", "description"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -40,9 +41,11 @@ class UsosApiUser(
|
|||||||
) { json, response ->
|
) { json, response ->
|
||||||
val programmes = json.getJsonArray("student_programmes")
|
val programmes = json.getJsonArray("student_programmes")
|
||||||
if (programmes.isNullOrEmpty()) {
|
if (programmes.isNullOrEmpty()) {
|
||||||
data.error(ApiError(TAG, ERROR_USOS_NO_STUDENT_PROGRAMMES)
|
data.error(
|
||||||
|
ApiError(TAG, ERROR_USOS_NO_STUDENT_PROGRAMMES)
|
||||||
.withApiResponse(json)
|
.withApiResponse(json)
|
||||||
.withResponse(response))
|
.withResponse(response)
|
||||||
|
)
|
||||||
return@apiRequest
|
return@apiRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,13 +53,19 @@ class UsosApiUser(
|
|||||||
val lastName = json.getString("last_name")
|
val lastName = json.getString("last_name")
|
||||||
val studentName = buildFullName(firstName, lastName)
|
val studentName = buildFullName(firstName, lastName)
|
||||||
|
|
||||||
|
val studentProgrammeId = programmes.getJsonObject(0)
|
||||||
|
.getString("id")
|
||||||
|
val programmeId = programmes.getJsonObject(0)
|
||||||
|
.getJsonObject("programme")
|
||||||
|
.getString("id")
|
||||||
|
|
||||||
data.studentId = json.getInt("id") ?: data.studentId
|
data.studentId = json.getInt("id") ?: data.studentId
|
||||||
profile?.studentNameLong = studentName
|
profile?.studentNameLong = studentName
|
||||||
profile?.studentNameShort = studentName.getShortName()
|
profile?.studentNameShort = studentName.getShortName()
|
||||||
profile?.studentNumber = json.getInt("student_number", -1)
|
profile?.studentNumber = json.getInt("student_number", -1)
|
||||||
profile?.studentClassName = programmes.getJsonObject(0).getJsonObject("programme").getString("id")
|
profile?.studentClassName = programmeId
|
||||||
|
|
||||||
profile?.studentClassName?.let {
|
val team = programmeId?.let {
|
||||||
data.getTeam(
|
data.getTeam(
|
||||||
id = null,
|
id = null,
|
||||||
name = it,
|
name = it,
|
||||||
@ -64,6 +73,7 @@ class UsosApiUser(
|
|||||||
isTeamClass = true,
|
isTeamClass = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
team?.code = "${data.schoolId}:${studentProgrammeId}:${programmeId}"
|
||||||
|
|
||||||
data.setSyncNext(ENDPOINT_USOS_API_USER, 4 * DAY)
|
data.setSyncNext(ENDPOINT_USOS_API_USER, 4 * DAY)
|
||||||
onSuccess(ENDPOINT_USOS_API_USER)
|
onSuccess(ENDPOINT_USOS_API_USER)
|
||||||
|
@ -206,7 +206,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
teams.filter { it.profileId == profile.id }.map { it.code }
|
teams.filter { it.profileId == profile.id }.map { it.code }
|
||||||
)
|
)
|
||||||
val hash = user.toString().md5()
|
val hash = user.toString().md5()
|
||||||
if (hash == profile.config.hash)
|
if (hash == profile.config.hash && app.config.hash != "invalid")
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
return@mapNotNull user to profile.config
|
return@mapNotNull user to profile.config
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,6 @@ object Signing {
|
|||||||
|
|
||||||
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
|
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
|
||||||
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
|
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
|
||||||
return "$param1.MTIzNDU2Nzg5MD0WAYwfGc===.$param2".sha256()
|
return "$param1.MTIzNDU2Nzg5MDADAoYzGn===.$param2".sha256()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,4 +24,7 @@ interface EndpointTimerDao {
|
|||||||
|
|
||||||
@Query("DELETE FROM endpointTimers WHERE profileId = :profileId")
|
@Query("DELETE FROM endpointTimers WHERE profileId = :profileId")
|
||||||
fun clear(profileId: Int)
|
fun clear(profileId: Int)
|
||||||
|
|
||||||
|
@Query("DELETE FROM endpointTimers")
|
||||||
|
fun clear()
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
@ -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,
|
||||||
|
@ -72,7 +72,6 @@ fun Profile.getAppData() =
|
|||||||
if (App.profileId == this.id) App.data else AppData.get(this.loginStoreType)
|
if (App.profileId == this.id) App.data else AppData.get(this.loginStoreType)
|
||||||
|
|
||||||
fun Profile.shouldArchive(): Boolean {
|
fun Profile.shouldArchive(): Boolean {
|
||||||
if (loginStoreType == LoginType.DEMO)
|
|
||||||
return false
|
return false
|
||||||
// vulcan hotfix
|
// vulcan hotfix
|
||||||
if (dateYearEnd.month > 6) {
|
if (dateYearEnd.month > 6) {
|
||||||
|
@ -21,6 +21,8 @@ import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_M
|
|||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_DATE_DESC
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_DATE_DESC
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_SUBJECT_ASC
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_SUBJECT_ASC
|
||||||
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.UNIVERSITY_AVERAGE_MODE_ECTS
|
||||||
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.UNIVERSITY_AVERAGE_MODE_SIMPLE
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_AVG
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_AVG
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_SEM
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_SEM
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_AVG
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_AVG
|
||||||
@ -47,6 +49,8 @@ class GradesConfigDialog(
|
|||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override suspend fun loadConfig() {
|
override suspend fun loadConfig() {
|
||||||
|
b.isUniversity = app.gradesManager.isUniversity
|
||||||
|
|
||||||
b.customPlusCheckBox.isChecked = app.profile.config.grades.plusValue != null
|
b.customPlusCheckBox.isChecked = app.profile.config.grades.plusValue != null
|
||||||
b.customPlusValue.isVisible = b.customPlusCheckBox.isChecked
|
b.customPlusValue.isVisible = b.customPlusCheckBox.isChecked
|
||||||
b.customMinusCheckBox.isChecked = app.profile.config.grades.minusValue != null
|
b.customMinusCheckBox.isChecked = app.profile.config.grades.minusValue != null
|
||||||
@ -76,10 +80,18 @@ class GradesConfigDialog(
|
|||||||
else -> null
|
else -> null
|
||||||
}?.isChecked = true
|
}?.isChecked = true
|
||||||
|
|
||||||
|
when (app.profile.config.grades.universityAverageMode) {
|
||||||
|
UNIVERSITY_AVERAGE_MODE_ECTS -> b.gradeUniversityAverageMode1
|
||||||
|
UNIVERSITY_AVERAGE_MODE_SIMPLE -> b.gradeUniversityAverageMode0
|
||||||
|
else -> null
|
||||||
|
}?.isChecked = true
|
||||||
|
|
||||||
b.dontCountGrades.isChecked =
|
b.dontCountGrades.isChecked =
|
||||||
app.profile.config.grades.dontCountEnabled && app.profile.config.grades.dontCountGrades.isNotEmpty()
|
app.profile.config.grades.dontCountEnabled && app.profile.config.grades.dontCountGrades.isNotEmpty()
|
||||||
b.hideImproved.isChecked = app.profile.config.grades.hideImproved
|
b.hideImproved.isChecked = app.profile.config.grades.hideImproved
|
||||||
|
b.hideNoGrade.isChecked = app.profile.config.grades.hideNoGrade
|
||||||
b.averageWithoutWeight.isChecked = app.profile.config.grades.averageWithoutWeight
|
b.averageWithoutWeight.isChecked = app.profile.config.grades.averageWithoutWeight
|
||||||
|
b.countEctsInProgress.isChecked = app.profile.config.grades.countEctsInProgress
|
||||||
|
|
||||||
if (app.profile.config.grades.dontCountGrades.isEmpty()) {
|
if (app.profile.config.grades.dontCountGrades.isEmpty()) {
|
||||||
b.dontCountGradesText.setText("nb, 0, bz, bd")
|
b.dontCountGradesText.setText("nb, 0, bz, bd")
|
||||||
@ -149,10 +161,21 @@ class GradesConfigDialog(
|
|||||||
app.profile.config.grades.yearAverageMode = YEAR_1_SEM_2_SEM
|
app.profile.config.grades.yearAverageMode = YEAR_1_SEM_2_SEM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.gradeUniversityAverageMode1.setOnSelectedListener {
|
||||||
|
app.profile.config.grades.universityAverageMode = UNIVERSITY_AVERAGE_MODE_ECTS
|
||||||
|
}
|
||||||
|
b.gradeUniversityAverageMode0.setOnSelectedListener {
|
||||||
|
app.profile.config.grades.universityAverageMode = UNIVERSITY_AVERAGE_MODE_SIMPLE
|
||||||
|
}
|
||||||
|
|
||||||
b.hideImproved.onChange { _, isChecked -> app.profile.config.grades.hideImproved = isChecked }
|
b.hideImproved.onChange { _, isChecked -> app.profile.config.grades.hideImproved = isChecked }
|
||||||
|
b.hideNoGrade.onChange { _, isChecked -> app.profile.config.grades.hideNoGrade = isChecked }
|
||||||
b.averageWithoutWeight.onChange { _, isChecked ->
|
b.averageWithoutWeight.onChange { _, isChecked ->
|
||||||
app.profile.config.grades.averageWithoutWeight = isChecked
|
app.profile.config.grades.averageWithoutWeight = isChecked
|
||||||
}
|
}
|
||||||
|
b.countEctsInProgress.onChange { _, isChecked ->
|
||||||
|
app.profile.config.grades.countEctsInProgress = isChecked
|
||||||
|
}
|
||||||
|
|
||||||
b.averageWithoutWeightHelp.onClick {
|
b.averageWithoutWeightHelp.onClick {
|
||||||
MaterialAlertDialogBuilder(activity)
|
MaterialAlertDialogBuilder(activity)
|
||||||
|
@ -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
|
||||||
@ -134,6 +135,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 +149,12 @@ class GradesAdapter(
|
|||||||
if (notifyAdapter) notifyItemInserted(position)
|
if (notifyAdapter) notifyItemInserted(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
position++
|
|
||||||
model.state = STATE_OPENED
|
model.state = STATE_OPENED
|
||||||
|
if (subItems.isNotEmpty()) {
|
||||||
|
position++
|
||||||
items.addAll(position, subItems.filterNotNull())
|
items.addAll(position, subItems.filterNotNull())
|
||||||
if (notifyAdapter) notifyItemRangeInserted(position, subItems.size)
|
if (notifyAdapter) notifyItemRangeInserted(position, subItems.size)
|
||||||
|
}
|
||||||
|
|
||||||
if (model is GradesSubject) {
|
if (model is GradesSubject) {
|
||||||
// auto expand first semester
|
// auto expand first semester
|
||||||
@ -232,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) {
|
||||||
|
@ -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
|
||||||
@ -28,7 +29,10 @@ import pl.szczodrzynski.edziennik.ui.grades.models.GradesAverages
|
|||||||
import pl.szczodrzynski.edziennik.ui.grades.models.GradesSemester
|
import pl.szczodrzynski.edziennik.ui.grades.models.GradesSemester
|
||||||
import pl.szczodrzynski.edziennik.ui.grades.models.GradesStats
|
import pl.szczodrzynski.edziennik.ui.grades.models.GradesStats
|
||||||
import pl.szczodrzynski.edziennik.ui.grades.models.GradesSubject
|
import pl.szczodrzynski.edziennik.ui.grades.models.GradesSubject
|
||||||
|
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager
|
||||||
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.UNIVERSITY_AVERAGE_MODE_ECTS
|
||||||
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.UNIVERSITY_AVERAGE_MODE_SIMPLE
|
||||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
|
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
|
||||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
|
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
@ -85,6 +89,35 @@ class GradesListFragment : Fragment(), CoroutineScope {
|
|||||||
else -> grades
|
else -> grades
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (manager.isUniversity) {
|
||||||
|
val termIds = grades.map { it.comment }.toSet().toMutableList()
|
||||||
|
val termNames: MutableMap<String, String> = mutableMapOf()
|
||||||
|
// deserialize to a map of termId to (orderKey, termName)
|
||||||
|
val terms = app.profile.getStudentData("termNames", null)
|
||||||
|
?.let { app.gson.fromJson(it, termNames::class.java) }
|
||||||
|
?.mapValues { (_, value) -> value.split('$', limit = 2) }
|
||||||
|
?.mapValues { (_, value) -> Pair(value[0].toIntOrNull() ?: 0, value[1]) }
|
||||||
|
?: mapOf()
|
||||||
|
// sort by order key
|
||||||
|
termIds.sortByDescending { termId -> terms[termId]?.first ?: 0 }
|
||||||
|
// populate the dropdown
|
||||||
|
b.semesterLayout.isVisible = true
|
||||||
|
b.semesterDropdown.items = termIds.mapIndexed { id, termId ->
|
||||||
|
TextInputDropDown.Item(
|
||||||
|
id.toLong(),
|
||||||
|
terms[termId]?.second ?: termId ?: "-",
|
||||||
|
tag = termId,
|
||||||
|
)
|
||||||
|
}.toMutableList()
|
||||||
|
b.semesterDropdown.select(index = 0)
|
||||||
|
b.semesterDropdown.setOnChangeListener { item ->
|
||||||
|
b.semesterDropdown.select(item)
|
||||||
|
adapter.items = processGrades(items)
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
return@setOnChangeListener true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// load & configure the adapter
|
// load & configure the adapter
|
||||||
adapter.items = withContext(Dispatchers.Default) { processGrades(items) }
|
adapter.items = withContext(Dispatchers.Default) { processGrades(items) }
|
||||||
if (items.isNotNullNorEmpty() && b.list.adapter == null) {
|
if (items.isNotNullNorEmpty() && b.list.adapter == null) {
|
||||||
@ -188,13 +221,23 @@ class GradesListFragment : Fragment(), CoroutineScope {
|
|||||||
var semesterNumber = 0
|
var semesterNumber = 0
|
||||||
var subject = GradesSubject(subjectId, "")
|
var subject = GradesSubject(subjectId, "")
|
||||||
var semester = GradesSemester(0, 1)
|
var semester = GradesSemester(0, 1)
|
||||||
|
val isUniversity = manager.isUniversity
|
||||||
|
val filterTermId = b.semesterDropdown.selected?.tag
|
||||||
|
|
||||||
val hideImproved = manager.hideImproved
|
val hideNoGrade = app.profile.config.grades.hideNoGrade
|
||||||
|
val countEctsInProgress = app.profile.config.grades.countEctsInProgress
|
||||||
|
val universityAverageMode = app.profile.config.grades.universityAverageMode
|
||||||
|
|
||||||
// grades returned by the query are ordered
|
// grades returned by the query are ordered
|
||||||
// by the subject ID, so it's easier and probably
|
// by the subject ID, so it's easier and probably
|
||||||
// a bit faster to build all the models
|
// a bit faster to build all the models
|
||||||
for (grade in grades) {
|
for (grade in grades) {
|
||||||
|
if (isUniversity && filterTermId != null && grade.comment != filterTermId)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (hideNoGrade && grade.type == TYPE_NO_GRADE)
|
||||||
|
continue
|
||||||
|
|
||||||
/*if (grade.parentId != null && grade.parentId != -1L)
|
/*if (grade.parentId != null && grade.parentId != -1L)
|
||||||
continue // the grade is hidden as a new, improved one is available*/
|
continue // the grade is hidden as a new, improved one is available*/
|
||||||
if (grade.subjectId != subjectId) {
|
if (grade.subjectId != subjectId) {
|
||||||
@ -258,8 +301,63 @@ 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 (isUniversity) {
|
||||||
|
val semesterSum = mutableListOf<Float>()
|
||||||
|
val semesterCount = mutableListOf<Float>()
|
||||||
|
val totalSum = mutableListOf<Float>()
|
||||||
|
val totalCount = mutableListOf<Float>()
|
||||||
|
val ectsPoints = mutableMapOf<Pair<Long, String?>, Float>()
|
||||||
|
for (grade in grades) {
|
||||||
|
val pointsPair = grade.subjectId to grade.comment
|
||||||
|
if (grade.type == TYPE_NO_GRADE && !countEctsInProgress)
|
||||||
|
// 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)
|
||||||
|
continue
|
||||||
|
if (universityAverageMode == UNIVERSITY_AVERAGE_MODE_ECTS)
|
||||||
|
totalSum.add(grade.value * grade.weight)
|
||||||
|
else
|
||||||
|
totalSum.add(grade.value)
|
||||||
|
totalCount.add(grade.weight)
|
||||||
|
|
||||||
|
if (grade.value < 3.0)
|
||||||
|
// exam not passed, reset points for this subject
|
||||||
|
ectsPoints[pointsPair] = 0.0f
|
||||||
|
else if (pointsPair !in ectsPoints)
|
||||||
|
// no points for this subject, simply assign
|
||||||
|
ectsPoints[pointsPair] = grade.weight
|
||||||
|
|
||||||
|
if (filterTermId != null && grade.comment != filterTermId)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (universityAverageMode == UNIVERSITY_AVERAGE_MODE_ECTS)
|
||||||
|
semesterSum.add(grade.value * grade.weight)
|
||||||
|
else
|
||||||
|
semesterSum.add(grade.value)
|
||||||
|
semesterCount.add(grade.weight)
|
||||||
|
}
|
||||||
|
when (universityAverageMode) {
|
||||||
|
UNIVERSITY_AVERAGE_MODE_SIMPLE -> {
|
||||||
|
stats.universitySem = semesterSum.sum() / semesterCount.size
|
||||||
|
stats.universityTotal = totalSum.sum() / totalCount.size
|
||||||
|
}
|
||||||
|
UNIVERSITY_AVERAGE_MODE_ECTS -> {
|
||||||
|
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 +428,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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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,6 +107,7 @@ class SubjectViewHolder(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!manager.isUniversity) {
|
||||||
b.previewContainer.addView(TextView(contextWrapper).apply {
|
b.previewContainer.addView(TextView(contextWrapper).apply {
|
||||||
setTextColor(android.R.attr.textColorSecondary.resolveAttr(context))
|
setTextColor(android.R.attr.textColorSecondary.resolveAttr(context))
|
||||||
text = manager.getAverageString(app, firstSemester.averages, nameSemester = true, showSemester = firstSemester.number)
|
text = manager.getAverageString(app, firstSemester.averages, nameSemester = true, showSemester = firstSemester.number)
|
||||||
@ -102,6 +118,7 @@ class SubjectViewHolder(
|
|||||||
maxLines = 1
|
maxLines = 1
|
||||||
ellipsize = TextUtils.TruncateAt.END
|
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 {
|
||||||
|
@ -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()
|
||||||
})
|
})
|
||||||
|
@ -12,9 +12,11 @@ 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
|
||||||
|
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
|
||||||
@ -42,6 +44,8 @@ class GradesManager(val app: App) : CoroutineScope {
|
|||||||
const val YEAR_ALL_GRADES = 4
|
const val YEAR_ALL_GRADES = 4
|
||||||
const val COLOR_MODE_DEFAULT = 0
|
const val COLOR_MODE_DEFAULT = 0
|
||||||
const val COLOR_MODE_WEIGHTED = 1
|
const val COLOR_MODE_WEIGHTED = 1
|
||||||
|
const val UNIVERSITY_AVERAGE_MODE_SIMPLE = 0
|
||||||
|
const val UNIVERSITY_AVERAGE_MODE_ECTS = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
private val job = Job()
|
private val job = Job()
|
||||||
@ -69,6 +73,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) {
|
||||||
@ -87,6 +93,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +165,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
|
||||||
|
@ -6,6 +6,14 @@
|
|||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="isUniversity"
|
||||||
|
type="boolean" />
|
||||||
|
</data>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
@ -19,16 +27,18 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginVertical="16dp"
|
android:layout_marginTop="16dp"
|
||||||
style="@style/TextAppearance.AppCompat.Small"
|
style="@style/TextAppearance.AppCompat.Small"
|
||||||
android:text="@string/grades_config_title"/>
|
android:text="@string/grades_config_title"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
android:layout_marginBottom="4dp"
|
android:layout_marginBottom="4dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:isVisible="@{!isUniversity}">
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/customPlusCheckBox"
|
android:id="@+id/customPlusCheckBox"
|
||||||
@ -54,7 +64,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="6dp"
|
android:layout_marginBottom="6dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:isVisible="@{!isUniversity}">
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/customMinusCheckBox"
|
android:id="@+id/customMinusCheckBox"
|
||||||
@ -79,14 +90,16 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:layout_marginBottom="6dp"
|
android:layout_marginBottom="6dp"
|
||||||
android:background="@drawable/divider"/>
|
android:background="@drawable/divider"
|
||||||
|
android:isVisible="@{!isUniversity}" />
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/dontCountGrades"
|
android:id="@+id/dontCountGrades"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="0dp"
|
android:minHeight="0dp"
|
||||||
android:text="@string/grades_config_dont_count_grades"/>
|
android:text="@string/grades_config_dont_count_grades"
|
||||||
|
android:isVisible="@{!isUniversity}" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -94,7 +107,8 @@
|
|||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||||
android:hint="@string/grades_config_dont_count_hint"
|
android:hint="@string/grades_config_dont_count_hint"
|
||||||
app:placeholderText="@string/grades_config_dont_count_placeholder"
|
app:placeholderText="@string/grades_config_dont_count_placeholder"
|
||||||
android:enabled="@{dontCountGrades.checked}">
|
android:enabled="@{dontCountGrades.checked}"
|
||||||
|
android:isVisible="@{!isUniversity}">
|
||||||
|
|
||||||
<pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
|
<pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
|
||||||
android:id="@+id/dontCountGradesText"
|
android:id="@+id/dontCountGradesText"
|
||||||
@ -111,10 +125,19 @@
|
|||||||
android:minHeight="32dp"
|
android:minHeight="32dp"
|
||||||
android:text="@string/grades_config_dont_show_improved"/>
|
android:text="@string/grades_config_dont_show_improved"/>
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/hideNoGrade"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="32dp"
|
||||||
|
android:text="@string/grades_config_hide_no_grade"
|
||||||
|
android:isVisible="@{isUniversity}" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:isVisible="@{!isUniversity}">
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/averageWithoutWeight"
|
android:id="@+id/averageWithoutWeight"
|
||||||
@ -136,6 +159,14 @@
|
|||||||
tools:src="@android:drawable/ic_menu_help" />
|
tools:src="@android:drawable/ic_menu_help" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/countEctsInProgress"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="32dp"
|
||||||
|
android:text="@string/grades_config_ects_in_progress"
|
||||||
|
android:isVisible="@{isUniversity}" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -195,11 +226,13 @@
|
|||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginBottom="4dp"
|
android:layout_marginBottom="4dp"
|
||||||
style="@style/TextAppearance.AppCompat.Small"
|
style="@style/TextAppearance.AppCompat.Small"
|
||||||
android:text="@string/menu_grades_average_mode"/>
|
android:text="@string/menu_grades_average_mode"
|
||||||
|
android:isVisible="@{!isUniversity}" />
|
||||||
|
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:isVisible="@{!isUniversity}">
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
android:id="@+id/gradeAverageMode4"
|
android:id="@+id/gradeAverageMode4"
|
||||||
@ -236,6 +269,35 @@
|
|||||||
android:minHeight="0dp"
|
android:minHeight="0dp"
|
||||||
android:text="@string/settings_register_avg_mode_3"/>
|
android:text="@string/settings_register_avg_mode_3"/>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
style="@style/TextAppearance.AppCompat.Small"
|
||||||
|
android:text="@string/menu_grades_university_average_mode"
|
||||||
|
android:isVisible="@{isUniversity}" />
|
||||||
|
|
||||||
|
<RadioGroup
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:isVisible="@{isUniversity}">
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/gradeUniversityAverageMode1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="0dp"
|
||||||
|
android:text="@string/settings_register_university_avg_mode_1"/>
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/gradeUniversityAverageMode0"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="0dp"
|
||||||
|
android:text="@string/settings_register_university_avg_mode_0"/>
|
||||||
|
</RadioGroup>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -283,6 +283,105 @@
|
|||||||
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" />
|
||||||
|
|
||||||
|
<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_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"
|
||||||
|
@ -37,6 +37,31 @@
|
|||||||
app:drawableTopCompat="@drawable/ic_no_grades"
|
app:drawableTopCompat="@drawable/ic_no_grades"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/semesterLayout"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="8dp"
|
||||||
|
android:layout_marginVertical="8dp"
|
||||||
|
android:hint="@string/title_semester"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
||||||
|
android:id="@+id/semesterDropdown"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
tools:text="Semestr zimowy 2024/2025" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/list"
|
android:id="@+id/list"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -44,6 +69,7 @@
|
|||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:listitem="@layout/grades_item_subject"
|
tools:listitem="@layout/grades_item_subject"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
|
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -445,9 +445,12 @@
|
|||||||
<string name="grades_config_dont_count_hint">Oceny oddziel przecinkiem</string>
|
<string name="grades_config_dont_count_hint">Oceny oddziel przecinkiem</string>
|
||||||
<string name="grades_config_dont_count_placeholder">Podaj oceny…</string>
|
<string name="grades_config_dont_count_placeholder">Podaj oceny…</string>
|
||||||
<string name="grades_config_dont_show_improved">Ukrywaj oceny poprawione z listy</string>
|
<string name="grades_config_dont_show_improved">Ukrywaj oceny poprawione z listy</string>
|
||||||
|
<string name="grades_config_ects_in_progress">Licz ECTS przed zaliczeniem całego przedmiotu</string>
|
||||||
|
<string name="grades_config_hide_no_grade">Ukrywaj pozycje \"brak oceny\"</string>
|
||||||
<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 +477,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,11 +495,14 @@
|
|||||||
<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>
|
||||||
<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>
|
||||||
@ -689,6 +697,7 @@
|
|||||||
<string name="menu_grades_config">Ustawienia ocen</string>
|
<string name="menu_grades_config">Ustawienia ocen</string>
|
||||||
<string name="menu_grades_editor">Symulator edycji ocen</string>
|
<string name="menu_grades_editor">Symulator edycji ocen</string>
|
||||||
<string name="menu_grades_sort_mode">Sortuj oceny</string>
|
<string name="menu_grades_sort_mode">Sortuj oceny</string>
|
||||||
|
<string name="menu_grades_university_average_mode">Sposób obliczania średniej za studia</string>
|
||||||
<string name="menu_help">Pomoc</string>
|
<string name="menu_help">Pomoc</string>
|
||||||
<string name="menu_home_page">Strona główna</string>
|
<string name="menu_home_page">Strona główna</string>
|
||||||
<string name="menu_homework">Zadania domowe</string>
|
<string name="menu_homework">Zadania domowe</string>
|
||||||
@ -970,6 +979,8 @@
|
|||||||
<string name="settings_register_dont_count_zero_text">Nie wliczaj oceny 0 do średniej</string>
|
<string name="settings_register_dont_count_zero_text">Nie wliczaj oceny 0 do średniej</string>
|
||||||
<string name="settings_register_login_not_implemented_text">Ten e-dziennik nie został jeszcze zaimplementowany w aplikacji.</string>
|
<string name="settings_register_login_not_implemented_text">Ten e-dziennik nie został jeszcze zaimplementowany w aplikacji.</string>
|
||||||
<string name="settings_register_show_teacher_absences_text">Pokazuj nieobecności nauczycieli w Terminarzu</string>
|
<string name="settings_register_show_teacher_absences_text">Pokazuj nieobecności nauczycieli w Terminarzu</string>
|
||||||
|
<string name="settings_register_university_avg_mode_0">Średnia arytmetyczna ocen</string>
|
||||||
|
<string name="settings_register_university_avg_mode_1">Średnia ważona ocen (ECTS = waga)</string>
|
||||||
<string name="settings_sync_customize_endpoint_announcements">Tablica ogłoszeń</string>
|
<string name="settings_sync_customize_endpoint_announcements">Tablica ogłoszeń</string>
|
||||||
<string name="settings_sync_customize_endpoint_attendance">Obecności/nieobecności</string>
|
<string name="settings_sync_customize_endpoint_attendance">Obecności/nieobecności</string>
|
||||||
<string name="settings_sync_customize_endpoint_class_free_days">Dni wolne klasy</string>
|
<string name="settings_sync_customize_endpoint_class_free_days">Dni wolne klasy</string>
|
||||||
|
@ -5,8 +5,8 @@ buildscript {
|
|||||||
kotlin_version = '1.6.10'
|
kotlin_version = '1.6.10'
|
||||||
|
|
||||||
release = [
|
release = [
|
||||||
versionName: "4.13.7",
|
versionName: "4.14",
|
||||||
versionCode: 4130799
|
versionCode: 4140099
|
||||||
]
|
]
|
||||||
|
|
||||||
setup = [
|
setup = [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user