save grades to a encrypted cache file, use cache if not older than 24hr
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
313ff4741a
commit
d94f38de93
|
@ -51,7 +51,7 @@ dependencies {
|
||||||
implementation 'androidx.core:core:1.3.1'
|
implementation 'androidx.core:core:1.3.1'
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
implementation 'androidx.security:security-crypto:1.1.0-alpha02'
|
implementation 'androidx.security:security-crypto:1.1.0-alpha02'
|
||||||
implementation 'com.google.android.material:material:1.2.0'
|
implementation 'com.google.android.material:material:1.2.0'
|
||||||
|
@ -66,7 +66,7 @@ dependencies {
|
||||||
implementation 'org.jsoup:jsoup:1.13.1'
|
implementation 'org.jsoup:jsoup:1.13.1'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13'
|
testImplementation 'junit:junit:4.13'
|
||||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
androidTestImplementation 'androidx.test:core:1.3.0'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,21 +24,26 @@ package org.mosad.seil0.projectlaogai.controller.cache
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.security.crypto.EncryptedFile
|
||||||
|
import androidx.security.crypto.MasterKey
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import org.mosad.seil0.projectlaogai.controller.QISPOSParser
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||||
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
|
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.coursesCacheTime
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.coursesCacheTime
|
||||||
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.gradesCacheTime
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.mensaCacheTime
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.mensaCacheTime
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.timetableCacheTime
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.timetableCacheTime
|
||||||
import org.mosad.seil0.projectlaogai.util.*
|
import org.mosad.seil0.projectlaogai.util.*
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The cacheController reads and updates the cache files.
|
* The cacheController reads and updates the cache files.
|
||||||
|
@ -201,6 +206,25 @@ class CacheController(cont: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update the encrypted grades file
|
||||||
|
*/
|
||||||
|
fun updateGrades(context: Context): Job {
|
||||||
|
val file = File(context.filesDir, "grades_encrypted")
|
||||||
|
val parser = QISPOSParser(context)
|
||||||
|
|
||||||
|
return GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
if (parser.checkQISPOSStatus() == 200) {
|
||||||
|
if (file.exists()) { file.delete() }
|
||||||
|
|
||||||
|
// save cache file and update time
|
||||||
|
saveEncrypted(context, file, Gson().toJson(parser.parseGrades()))
|
||||||
|
gradesCacheTime = System.currentTimeMillis() / 1000
|
||||||
|
Preferences.save(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* save changes in lessonMap and subjectMap,
|
* save changes in lessonMap and subjectMap,
|
||||||
* called on addSubject or removeSubject
|
* called on addSubject or removeSubject
|
||||||
|
@ -223,6 +247,22 @@ class CacheController(cont: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun saveEncrypted(context: Context, file: File, text: String) {
|
||||||
|
val masterKey = MasterKey.Builder(context)
|
||||||
|
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||||
|
.build()
|
||||||
|
val encryptedFile = EncryptedFile.Builder(
|
||||||
|
context,
|
||||||
|
file,
|
||||||
|
masterKey,
|
||||||
|
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
|
||||||
|
).build()
|
||||||
|
|
||||||
|
encryptedFile.openFileOutput().bufferedWriter().use {
|
||||||
|
it.write(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -372,4 +412,40 @@ class CacheController(cont: Context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read the encrypted grades file, don't keep them
|
||||||
|
* in CacheController for security reasons
|
||||||
|
* @return the grades as HashMap if the file exists, else a empty HashMap
|
||||||
|
*/
|
||||||
|
fun readGrades(): HashMap<String, ArrayList<GradeSubject>> {
|
||||||
|
val file = File(context.filesDir, "grades_encrypted")
|
||||||
|
|
||||||
|
// if the file does not exit, try creating it
|
||||||
|
if (!file.exists()) {
|
||||||
|
runBlocking { updateGrades(context) }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.exists()) {
|
||||||
|
val masterKey = MasterKey.Builder(context)
|
||||||
|
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||||
|
.build()
|
||||||
|
val encryptedFile = EncryptedFile.Builder(
|
||||||
|
context,
|
||||||
|
file,
|
||||||
|
masterKey,
|
||||||
|
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
|
||||||
|
).build()
|
||||||
|
|
||||||
|
return encryptedFile.openFileInput().bufferedReader().use {
|
||||||
|
GsonBuilder().create()
|
||||||
|
.fromJson(
|
||||||
|
it.readLine(),
|
||||||
|
object : TypeToken<HashMap<String, ArrayList<GradeSubject>>>() {}.type
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return HashMap()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -38,94 +38,91 @@ import org.mosad.seil0.projectlaogai.util.TimetableWeek
|
||||||
* * add second week
|
* * add second week
|
||||||
* * add configurable week to addSubject() and removeSubject(), updateAdditionalLessons()
|
* * add configurable week to addSubject() and removeSubject(), updateAdditionalLessons()
|
||||||
*/
|
*/
|
||||||
class TimetableController {
|
object TimetableController {
|
||||||
|
|
||||||
companion object {
|
val timetable = ArrayList<TimetableWeek>()
|
||||||
val timetable = ArrayList<TimetableWeek>()
|
val lessonMap = HashMap<String, Lesson>() // the key is courseName-subject-lessonID
|
||||||
val lessonMap = HashMap<String, Lesson>() // the key is courseName-subject-lessonID
|
val subjectMap = HashMap<String, ArrayList<String>>() // the key is courseName
|
||||||
val subjectMap = HashMap<String, ArrayList<String>>() // the key is courseName
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* update the main timetable and all additional subjects, async
|
* update the main timetable and all additional subjects, async
|
||||||
*/
|
*/
|
||||||
fun update(context: Context): List<Job> {
|
fun update(context: Context): List<Job> {
|
||||||
return listOf(
|
return listOf(
|
||||||
CacheController.updateTimetable(cCourse.courseName, 0, context),
|
CacheController.updateTimetable(cCourse.courseName, 0, context),
|
||||||
CacheController.updateTimetable(cCourse.courseName, 1, context),
|
CacheController.updateTimetable(cCourse.courseName, 1, context),
|
||||||
CacheController.updateAdditionalLessons(context)
|
CacheController.updateAdditionalLessons(context)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add a subject to the subjectMap and all it's lessons
|
||||||
|
* to the lessonMap
|
||||||
|
* @param courseName course to which the subject belongs
|
||||||
|
* @param subject the subjects name
|
||||||
|
*/
|
||||||
|
fun addSubject(courseName: String, subject: String, context: Context) {
|
||||||
|
// add subject
|
||||||
|
if (subjectMap.containsKey(courseName)) {
|
||||||
|
subjectMap[courseName]?.add(subject)
|
||||||
|
} else {
|
||||||
|
subjectMap[courseName] = arrayListOf(subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// add concrete lessons
|
||||||
* add a subject to the subjectMap and all it's lessons
|
TCoRAPIController.getLessons(courseName, subject, 0).forEach { lesson ->
|
||||||
* to the lessonMap
|
addLesson(courseName, subject, lesson)
|
||||||
* @param courseName course to which the subject belongs
|
}
|
||||||
* @param subject the subjects name
|
|
||||||
*/
|
CacheController.saveAdditionalSubjects(context)
|
||||||
fun addSubject(courseName: String, subject: String, context: Context) {
|
}
|
||||||
// add subject
|
|
||||||
if (subjectMap.containsKey(courseName)) {
|
/**
|
||||||
subjectMap[courseName]?.add(subject)
|
* remove a subject from the subjectMap and all it's lessons
|
||||||
} else {
|
* from the lessonMap
|
||||||
subjectMap[courseName] = arrayListOf(subject)
|
* @param courseName course to which the subject belongs
|
||||||
|
* @param subject the subjects name
|
||||||
|
*/
|
||||||
|
fun removeSubject(courseName: String, subject: String, context: Context) {
|
||||||
|
// remove subject
|
||||||
|
subjectMap[courseName]?.remove(subject)
|
||||||
|
|
||||||
|
// remove concrete lessons
|
||||||
|
val iterator = lessonMap.iterator()
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
val it = iterator.next()
|
||||||
|
if(it.key.contains("$courseName-$subject")) {
|
||||||
|
// remove the lesson from the lessons list
|
||||||
|
iterator.remove() // use iterator to remove, otherwise ConcurrentModificationException
|
||||||
|
|
||||||
|
// remove the lesson from the timetable
|
||||||
|
val id = it.value.lessonID.split(".")
|
||||||
|
if(id.size == 3)
|
||||||
|
timetable[0].days[id[0].toInt()].timeslots[id[1].toInt()].remove(it.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// add concrete lessons
|
|
||||||
TCoRAPIController.getLessons(courseName, subject, 0).forEach { lesson ->
|
|
||||||
addLesson(courseName, subject, lesson)
|
|
||||||
}
|
|
||||||
|
|
||||||
CacheController.saveAdditionalSubjects(context)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
CacheController.saveAdditionalSubjects(context)
|
||||||
* remove a subject from the subjectMap and all it's lessons
|
}
|
||||||
* from the lessonMap
|
|
||||||
* @param courseName course to which the subject belongs
|
|
||||||
* @param subject the subjects name
|
|
||||||
*/
|
|
||||||
fun removeSubject(courseName: String, subject: String, context: Context) {
|
|
||||||
// remove subject
|
|
||||||
subjectMap[courseName]?.remove(subject)
|
|
||||||
|
|
||||||
// remove concrete lessons
|
/**
|
||||||
val iterator = lessonMap.iterator()
|
* add a lesson to the lessonMap, also add it to the timetable
|
||||||
while (iterator.hasNext()) {
|
*/
|
||||||
val it = iterator.next()
|
fun addLesson(courseName: String, subject: String, lesson: Lesson) {
|
||||||
if(it.key.contains("$courseName-$subject")) {
|
//the courseName, subject and lessonID, separator: -
|
||||||
// remove the lesson from the lessons list
|
val key = "$courseName-$subject-${lesson.lessonID}"
|
||||||
iterator.remove() // use iterator to remove, otherwise ConcurrentModificationException
|
lessonMap[key] = lesson
|
||||||
|
|
||||||
// remove the lesson from the timetable
|
addLessonToTimetable(lesson)
|
||||||
val id = it.value.lessonID.split(".")
|
}
|
||||||
if(id.size == 3)
|
|
||||||
timetable[0].days[id[0].toInt()].timeslots[id[1].toInt()].remove(it.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CacheController.saveAdditionalSubjects(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* add a lesson to the lessonMap, also add it to the timetable
|
|
||||||
*/
|
|
||||||
fun addLesson(courseName: String, subject: String, lesson: Lesson) {
|
|
||||||
//the courseName, subject and lessonID, separator: -
|
|
||||||
val key = "$courseName-$subject-${lesson.lessonID}"
|
|
||||||
lessonMap[key] = lesson
|
|
||||||
|
|
||||||
addLessonToTimetable(lesson)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* add a lesson to the timetable
|
|
||||||
*/
|
|
||||||
fun addLessonToTimetable(lesson: Lesson) {
|
|
||||||
val id = lesson.lessonID.split(".")
|
|
||||||
if(id.size == 3)
|
|
||||||
timetable[0].days[id[0].toInt()].timeslots[id[1].toInt()].add(lesson)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add a lesson to the timetable
|
||||||
|
*/
|
||||||
|
fun addLessonToTimetable(lesson: Lesson) {
|
||||||
|
val id = lesson.lessonID.split(".")
|
||||||
|
if(id.size == 3)
|
||||||
|
timetable[0].days[id[0].toInt()].timeslots[id[1].toInt()].add(lesson)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -36,6 +36,7 @@ object Preferences {
|
||||||
var coursesCacheTime: Long = 0
|
var coursesCacheTime: Long = 0
|
||||||
var mensaCacheTime: Long = 0
|
var mensaCacheTime: Long = 0
|
||||||
var timetableCacheTime: Long = 0
|
var timetableCacheTime: Long = 0
|
||||||
|
var gradesCacheTime: Long = 0
|
||||||
var cColorPrimary: Int = Color.parseColor("#009688")
|
var cColorPrimary: Int = Color.parseColor("#009688")
|
||||||
var cColorAccent: Int = Color.parseColor("#0096ff")
|
var cColorAccent: Int = Color.parseColor("#0096ff")
|
||||||
var cCourse = Course(
|
var cCourse = Course(
|
||||||
|
@ -67,6 +68,9 @@ object Preferences {
|
||||||
putLong(context.getString(R.string.save_key_timetableCacheTime),
|
putLong(context.getString(R.string.save_key_timetableCacheTime),
|
||||||
timetableCacheTime
|
timetableCacheTime
|
||||||
)
|
)
|
||||||
|
putLong(context.getString(R.string.save_key_gradesCacheTime),
|
||||||
|
gradesCacheTime
|
||||||
|
)
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,6 +170,9 @@ object Preferences {
|
||||||
timetableCacheTime = sharedPref.getLong(context.getString(
|
timetableCacheTime = sharedPref.getLong(context.getString(
|
||||||
R.string.save_key_timetableCacheTime
|
R.string.save_key_timetableCacheTime
|
||||||
), 0)
|
), 0)
|
||||||
|
gradesCacheTime = sharedPref.getLong(context.getString(
|
||||||
|
R.string.save_key_gradesCacheTime
|
||||||
|
), 0)
|
||||||
|
|
||||||
// load saved course
|
// load saved course
|
||||||
cCourse = Course(
|
cCourse = Course(
|
||||||
|
|
|
@ -35,11 +35,13 @@ import kotlinx.android.synthetic.main.fragment_grades.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import org.mosad.seil0.projectlaogai.R
|
import org.mosad.seil0.projectlaogai.R
|
||||||
import org.mosad.seil0.projectlaogai.controller.QISPOSParser
|
import org.mosad.seil0.projectlaogai.controller.QISPOSParser
|
||||||
|
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
|
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.GradeLinearLayout
|
import org.mosad.seil0.projectlaogai.uicomponents.GradeLinearLayout
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
|
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
|
||||||
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The grades fragment class
|
* The grades fragment class
|
||||||
|
@ -64,6 +66,7 @@ class GradesFragment : Fragment() {
|
||||||
|
|
||||||
parser = QISPOSParser(context!!)// init the parser
|
parser = QISPOSParser(context!!)// init the parser
|
||||||
|
|
||||||
|
// TODO if loading from cache, don't check Qispos state
|
||||||
if (checkCredentials() && checkQisposStatus()) {
|
if (checkCredentials() && checkQisposStatus()) {
|
||||||
GlobalScope.launch(Dispatchers.Default) {
|
GlobalScope.launch(Dispatchers.Default) {
|
||||||
addGrades()
|
addGrades()
|
||||||
|
@ -125,76 +128,89 @@ class GradesFragment : Fragment() {
|
||||||
return statusCode == 200
|
return statusCode == 200
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the grades to the layout, async
|
/**
|
||||||
|
* add the grades to the layout, async
|
||||||
|
* TODO this is slow as hell
|
||||||
|
*/
|
||||||
private fun addGrades() = GlobalScope.launch(Dispatchers.Default) {
|
private fun addGrades() = GlobalScope.launch(Dispatchers.Default) {
|
||||||
withContext(Dispatchers.Main) {
|
val addGradesTime = measureTimeMillis {
|
||||||
refreshLayout_Grades.isRefreshing = true
|
|
||||||
}
|
|
||||||
|
|
||||||
val grades = parser.parseGrades()
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
linLayout_Grades.removeAllViews() // clear layout
|
|
||||||
}
|
|
||||||
|
|
||||||
// for each semester add a new card
|
|
||||||
grades.forEach { semester ->
|
|
||||||
val usedSubjects = ArrayList<String>()
|
|
||||||
val semesterCard = DayCardView(context!!)
|
|
||||||
semesterCard.setDayHeading(semester.key)
|
|
||||||
|
|
||||||
// for each subject add a new linLayout
|
|
||||||
semester.value.forEachIndexed { index, subject ->
|
|
||||||
if (usedSubjects.contains(subject.name)) {
|
|
||||||
return@forEachIndexed
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the first sub subjects
|
|
||||||
val subSubject = semester.value.firstOrNull {
|
|
||||||
it.name.contains(subject.name) && it.name != subject.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// if sub subject is not null, add it to used subjects
|
|
||||||
subSubject?.let {
|
|
||||||
usedSubjects.add(it.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
val subjectLayout = GradeLinearLayout(context).set {
|
|
||||||
subjectName = subject.name
|
|
||||||
grade = subject.grade
|
|
||||||
subSubjectName = subSubject?.name.toString()
|
|
||||||
subGrade = subSubject?.grade.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
// disable sub-subject if not set
|
|
||||||
if (subSubject == null)
|
|
||||||
subjectLayout.disableSubSubject()
|
|
||||||
|
|
||||||
// disable divider if last element
|
|
||||||
if (index == semester.value.lastIndex || semester.value.indexOf(subSubject) == semester.value.lastIndex)
|
|
||||||
subjectLayout.disableDivider()
|
|
||||||
|
|
||||||
semesterCard.getLinLayoutDay().addView(subjectLayout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// without context we can't access the view
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
linLayout_Grades.addView(semesterCard)
|
refreshLayout_Grades.isRefreshing = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the cache is older than 24hr, update blocking
|
||||||
|
val currentTime = System.currentTimeMillis() / 1000
|
||||||
|
if ((currentTime - Preferences.gradesCacheTime) > 86400) {
|
||||||
|
CacheController.updateGrades(context!!).join()
|
||||||
|
}
|
||||||
|
|
||||||
|
val grades = CacheController(context!!).readGrades()
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
linLayout_Grades.removeAllViews() // clear layout
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO this loop takes 3/4 of the time
|
||||||
|
// for each semester add a new card
|
||||||
|
grades.forEach { semester ->
|
||||||
|
val usedSubjects = ArrayList<String>()
|
||||||
|
val semesterCard = DayCardView(context!!)
|
||||||
|
semesterCard.setDayHeading(semester.key)
|
||||||
|
|
||||||
|
// for each subject add a new linLayout
|
||||||
|
semester.value.forEachIndexed { index, subject ->
|
||||||
|
if (usedSubjects.contains(subject.name)) {
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the first sub subjects
|
||||||
|
val subSubject = semester.value.firstOrNull {
|
||||||
|
it.name.contains(subject.name) && it.name != subject.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// if sub subject is not null, add it to used subjects
|
||||||
|
subSubject?.let {
|
||||||
|
usedSubjects.add(it.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
val subjectLayout = GradeLinearLayout(context).set {
|
||||||
|
subjectName = subject.name
|
||||||
|
grade = subject.grade
|
||||||
|
subSubjectName = subSubject?.name.toString()
|
||||||
|
subGrade = subSubject?.grade.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable sub-subject if not set
|
||||||
|
if (subSubject == null)
|
||||||
|
subjectLayout.disableSubSubject()
|
||||||
|
|
||||||
|
// disable divider if last element
|
||||||
|
if (index == semester.value.lastIndex || semester.value.indexOf(subSubject) == semester.value.lastIndex)
|
||||||
|
subjectLayout.disableDivider()
|
||||||
|
|
||||||
|
semesterCard.getLinLayoutDay().addView(subjectLayout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// without context we can't access the view
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
linLayout_Grades.addView(semesterCard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val txtViewLegal = TextView(context).apply {
|
||||||
|
text = resources.getString(R.string.without_guarantee)
|
||||||
|
textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop refreshing and show legal warning
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
linLayout_Grades.addView(txtViewLegal)
|
||||||
|
refreshLayout_Grades.isRefreshing = false
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
println("startup completed in $addGradesTime ms")
|
||||||
|
|
||||||
|
|
||||||
val txtViewLegal = TextView(context).apply {
|
|
||||||
text = resources.getString(R.string.without_guarantee)
|
|
||||||
textAlignment = View.TEXT_ALIGNMENT_CENTER
|
|
||||||
}
|
|
||||||
|
|
||||||
// stop refreshing and show legal warning
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
linLayout_Grades.addView(txtViewLegal)
|
|
||||||
refreshLayout_Grades.isRefreshing = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -33,7 +33,7 @@ import kotlinx.android.synthetic.main.fragment_timetable.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import org.mosad.seil0.projectlaogai.R
|
import org.mosad.seil0.projectlaogai.R
|
||||||
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
|
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
|
||||||
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController.Companion.timetable
|
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController.timetable
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.AddSubjectDialog
|
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.AddSubjectDialog
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
||||||
|
|
|
@ -127,6 +127,7 @@
|
||||||
<string name="save_key_coursesCacheTime" translatable="false">org.mosad.seil0.projectlaogai.coursesCacheTime</string>
|
<string name="save_key_coursesCacheTime" translatable="false">org.mosad.seil0.projectlaogai.coursesCacheTime</string>
|
||||||
<string name="save_key_mensaCacheTime" translatable="false">org.mosad.seil0.projectlaogai.mensaCacheTime</string>
|
<string name="save_key_mensaCacheTime" translatable="false">org.mosad.seil0.projectlaogai.mensaCacheTime</string>
|
||||||
<string name="save_key_timetableCacheTime" translatable="false">org.mosad.seil0.projectlaogai.timetableCacheTime</string>
|
<string name="save_key_timetableCacheTime" translatable="false">org.mosad.seil0.projectlaogai.timetableCacheTime</string>
|
||||||
|
<string name="save_key_gradesCacheTime" translatable="false">org.mosad.seil0.projectlaogai.gradesCacheTime</string>
|
||||||
<string name="save_key_user_email" translatable="false">org.mosad.seil0.projectlaogai.user_email</string>
|
<string name="save_key_user_email" translatable="false">org.mosad.seil0.projectlaogai.user_email</string>
|
||||||
<string name="save_key_user_password" translatable="false">org.mosad.seil0.projectlaogai.user_password</string>
|
<string name="save_key_user_password" translatable="false">org.mosad.seil0.projectlaogai.user_password</string>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue