remove grades/qispos
This commit is contained in:
parent
3e52061a20
commit
521045e0dd
|
@ -94,7 +94,6 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
|||
getString(R.string.intent_action_mensaFragment) -> activeFragment = MensaFragment()
|
||||
getString(R.string.intent_action_timetableFragment) -> activeFragment = TimetableFragment()
|
||||
getString(R.string.intent_action_moodleFragment) -> activeFragment = MoodleFragment()
|
||||
getString(R.string.intent_action_gradesFragment) -> activeFragment = GradesFragment()
|
||||
else -> activeFragment = HomeFragment()
|
||||
}
|
||||
|
||||
|
@ -155,7 +154,6 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
|||
R.id.nav_mensa -> MensaFragment()
|
||||
R.id.nav_timetable -> TimetableFragment()
|
||||
R.id.nav_moodle -> MoodleFragment()
|
||||
R.id.nav_grades -> GradesFragment()
|
||||
R.id.nav_settings -> SettingsFragment()
|
||||
else -> HomeFragment()
|
||||
}
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.controller
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||
import org.mosad.seil0.projectlaogai.util.GradeSubject
|
||||
import org.mosad.seil0.projectlaogai.worker.GradesUpdateWorker
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class GradesController {
|
||||
|
||||
/**
|
||||
* show the difference between 2 grades sets
|
||||
*/
|
||||
fun diffGrades(origMap: Map<String, ArrayList<GradeSubject>>, diffMap: Map<String, ArrayList<GradeSubject>>): ArrayList<GradeSubject> {
|
||||
val diff = ArrayList<GradeSubject>()
|
||||
|
||||
diffMap.values.forEach { semester ->
|
||||
// if it's the same, no need to compare
|
||||
if (!origMap.containsValue(semester)) {
|
||||
semester.forEach { gradeSubject ->
|
||||
// for each of the grades, check if it differs from the origMap
|
||||
|
||||
if (origMap.containsKey(gradeSubject.semester)) {
|
||||
// a new or changed subject
|
||||
if (gradeSubject !in origMap[gradeSubject.semester]!!) {
|
||||
diff.add(gradeSubject)
|
||||
}
|
||||
} else {
|
||||
// a new semester
|
||||
diff.add(gradeSubject)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* stop the grades background sync, if the sync interval is greater than 0
|
||||
* start it again with the new interval
|
||||
*/
|
||||
fun updateBackgroundSync(context: Context) {
|
||||
stopBackgroundSync(context)
|
||||
|
||||
// if interval is not 0, start background Sync
|
||||
if (Preferences.gradesSyncInterval > 0)
|
||||
startBackgroundSync(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* start a new periodic worker GradesUpdateWorker
|
||||
*/
|
||||
fun startBackgroundSync(context: Context) {
|
||||
val work = PeriodicWorkRequestBuilder<GradesUpdateWorker>(
|
||||
Preferences.gradesSyncInterval.toLong(),
|
||||
TimeUnit.HOURS
|
||||
).build()
|
||||
|
||||
val workManager = WorkManager.getInstance(context)
|
||||
workManager.enqueueUniquePeriodicWork("GradesUpdateWorker", ExistingPeriodicWorkPolicy.REPLACE, work)
|
||||
}
|
||||
|
||||
/**
|
||||
* cancel GradesUpdateWorker
|
||||
*/
|
||||
fun stopBackgroundSync(context: Context) {
|
||||
WorkManager.getInstance(context).cancelUniqueWork("GradesUpdateWorker")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,240 +0,0 @@
|
|||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.controller
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jsoup.HttpStatusException
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
|
||||
import org.mosad.seil0.projectlaogai.util.GradeSubject
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
/**
|
||||
* Parse the qispos site the get all needed data for the grades fragment
|
||||
*/
|
||||
class QISPOSParser(val context: Context) {
|
||||
|
||||
private val className = this.javaClass.name
|
||||
private val baseURL = "https://notenverwaltung.hs-offenburg.de"
|
||||
private val loginPath = "/qispos/rds?state=user&type=1&category=auth.login&startpage=portal.vm&breadCrumbSource=portal"
|
||||
|
||||
/**
|
||||
* check if qispos is available
|
||||
* @return a http status code, 999 if no HttpStatusException supplied
|
||||
*/
|
||||
fun checkQISPOSStatus(): Int {
|
||||
return runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
val socketFactory = createSSLSocketFactory()
|
||||
|
||||
try {
|
||||
val res = Jsoup.connect(baseURL + loginPath)
|
||||
.sslSocketFactory(socketFactory)
|
||||
.followRedirects(true)
|
||||
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
|
||||
.execute()
|
||||
|
||||
Log.i(className, "Qispos status is: ${res.statusCode()}")
|
||||
res.statusCode()
|
||||
} catch (exHttp: HttpStatusException) {
|
||||
Log.w(className, "Qispos status is: ${exHttp.statusCode}")
|
||||
exHttp.statusCode
|
||||
} catch (ex: Exception) {
|
||||
Log.e(className, "Error while checking status", ex)
|
||||
999
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parse the html from readGrades()
|
||||
* @return a SortedMap, each entry is a semester, each semester has a ArrayList with subjects
|
||||
*/
|
||||
fun parseGrades(): SortedMap<String, ArrayList<GradeSubject>> {
|
||||
val gradesMap = HashMap<String, ArrayList<GradeSubject>>()
|
||||
val gradesListHtml = readGrades()
|
||||
|
||||
gradesListHtml?.select("table > tbody > tr")?.forEach {
|
||||
val row = it.select("td.tabelle1_alignleft,td.tabelle1_aligncenter,td.tabelle1_alignright")
|
||||
|
||||
// only real subjects will be selected
|
||||
if(row.size >= 6 && row[0].text().length >=7) {
|
||||
val subject = GradeSubject(
|
||||
id = row[0].text(),
|
||||
name = row[1].text(),
|
||||
semester = row[2].text(),
|
||||
grade = if (row[3].text().isNotEmpty()) row[3].text() else row[4].text(),
|
||||
credits = row[5].text()
|
||||
)
|
||||
|
||||
if (gradesMap.containsKey(subject.semester)) {
|
||||
gradesMap[subject.semester]!!.add(subject)
|
||||
} else {
|
||||
gradesMap[subject.semester] = arrayListOf(subject)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return gradesMap.toSortedMap(compareBy<String>{
|
||||
val oText = it.substringAfter(" ")
|
||||
|
||||
// if WS, substring before / and add 0.5 to compareBy
|
||||
if (oText.contains("/")) {
|
||||
oText.substringBefore("/").toInt() + 0.5
|
||||
} else {
|
||||
oText.toDouble()
|
||||
}
|
||||
}.thenBy { it })
|
||||
}
|
||||
|
||||
/**
|
||||
* read the grades html from qispos
|
||||
* @return the grades list as html element or null
|
||||
*/
|
||||
private fun readGrades(): Element?{
|
||||
|
||||
val credentials = EncryptedPreferences.readCredentials(context)
|
||||
val username = credentials.first.substringBefore("@")
|
||||
val password = credentials.second
|
||||
|
||||
return runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val socketFactory = createSSLSocketFactory()
|
||||
|
||||
// login, asdf = username, fdsa = password, wtf
|
||||
val list = mapOf(
|
||||
Pair("asdf", username),
|
||||
Pair("fdsa", password),
|
||||
Pair("submit", "Anmelden")
|
||||
)
|
||||
|
||||
// login and get session cookies
|
||||
val res = Jsoup.connect(baseURL + loginPath)
|
||||
.sslSocketFactory(socketFactory)
|
||||
.followRedirects(true)
|
||||
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
|
||||
.data(list)
|
||||
.postDataCharset("UTF-8")
|
||||
.execute()
|
||||
Log.i(className, "Login status is: ${res.statusCode()} (${res.statusMessage()})")
|
||||
|
||||
val loginCookies = res.cookies()
|
||||
|
||||
// grades root document and url
|
||||
val rootHtml =Jsoup.parse(res.body())
|
||||
val gradesRootLink =
|
||||
rootHtml.select("li.menueListStyle > a.auflistung").last()!!.attr("abs:href")
|
||||
|
||||
// parse grades url
|
||||
val gradesHtml = Jsoup.connect(gradesRootLink)
|
||||
.sslSocketFactory(socketFactory)
|
||||
.followRedirects(true)
|
||||
.cookies(loginCookies)
|
||||
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
|
||||
.get()
|
||||
|
||||
val gradesNextLink = gradesHtml.select("li.treelist > a.regular").attr("abs:href")
|
||||
|
||||
val gradesNextHtml = Jsoup.connect(gradesNextLink)
|
||||
.sslSocketFactory(socketFactory)
|
||||
.followRedirects(true)
|
||||
.cookies(loginCookies)
|
||||
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
|
||||
.get()
|
||||
|
||||
val gradesListLink = gradesNextHtml.selectFirst("li.treelist > ul > li")!!.selectFirst("a")!!.attr("abs:href")
|
||||
|
||||
// get the grades list
|
||||
val gradesListHtml = Jsoup.connect(gradesListLink)
|
||||
.sslSocketFactory(socketFactory)
|
||||
.followRedirects(true)
|
||||
.cookies(loginCookies)
|
||||
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
|
||||
.get()
|
||||
|
||||
Log.i(className, "Read html length: ${gradesListHtml.body().html().length}")
|
||||
gradesListHtml.body()
|
||||
} catch (ex: Exception) {
|
||||
Log.e(className, "Error while loading Qispos", ex)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* since the HS has a fucked up tls setup we need to work around that
|
||||
*/
|
||||
private fun createSSLSocketFactory(): SSLSocketFactory {
|
||||
// Load CAs from an InputStream
|
||||
// (could be from a resource or ByteArrayInputStream or ...)
|
||||
val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
|
||||
val caInput = context.resources.openRawResource(R.raw.notenverwaltung_hs_offenburg_de)
|
||||
val ca = caInput.use {
|
||||
cf.generateCertificate(it) as X509Certificate
|
||||
}
|
||||
|
||||
// Create a KeyStore containing our trusted CAs
|
||||
val keyStoreType = KeyStore.getDefaultType()
|
||||
val keyStore = KeyStore.getInstance(keyStoreType).apply {
|
||||
load(null, null)
|
||||
setCertificateEntry("ca", ca)
|
||||
}
|
||||
|
||||
// Create a TrustManager that trusts the CAs inputStream our KeyStore
|
||||
val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
|
||||
val tmf = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
|
||||
init(keyStore)
|
||||
}
|
||||
|
||||
// Create an SSLContext that uses our TrustManager
|
||||
val sslContext = SSLContext.getInstance("TLS").apply {
|
||||
init(null, tmf.trustManagers, null)
|
||||
}
|
||||
|
||||
return sslContext.socketFactory
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -24,18 +24,14 @@ package org.mosad.seil0.projectlaogai.controller.cache
|
|||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.security.crypto.EncryptedFile
|
||||
import androidx.security.crypto.MasterKey
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonParser
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import kotlinx.coroutines.*
|
||||
import org.mosad.seil0.projectlaogai.controller.QISPOSParser
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
|
||||
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.timetableCacheTime
|
||||
import org.mosad.seil0.projectlaogai.util.*
|
||||
|
@ -202,23 +198,6 @@ class CacheController(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update the encrypted grades file
|
||||
*/
|
||||
fun updateGrades(context: Context): Job {
|
||||
val file = File(context.filesDir, "grades_encrypted")
|
||||
val parser = QISPOSParser(context)
|
||||
|
||||
return CoroutineScope(Dispatchers.IO).launch {
|
||||
if (parser.checkQISPOSStatus() == 200) {
|
||||
// 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,
|
||||
* called on addSubject or removeSubject
|
||||
|
@ -240,28 +219,6 @@ class CacheController(private val context: Context) {
|
|||
Log.e(className, "failed to write file \"${file.absoluteFile}\"", ex)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
// only write one file at a time
|
||||
synchronized(this) {
|
||||
if (file.exists()) { file.delete() }
|
||||
|
||||
encryptedFile.openFileOutput().bufferedWriter().use {
|
||||
it.write(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -411,48 +368,4 @@ class CacheController(private val context: Context) {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* read the encrypted grades file, don't keep them
|
||||
* in CacheController for security reasons
|
||||
* @return the grades as SortedMap if the file exists, else a empty SortedMap
|
||||
*/
|
||||
fun readGrades(): SortedMap<String, java.util.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()
|
||||
|
||||
val map: HashMap<String, ArrayList<GradeSubject>> = encryptedFile.openFileInput().bufferedReader().use {
|
||||
GsonBuilder().create()
|
||||
.fromJson(it, object : TypeToken<HashMap<String, ArrayList<GradeSubject>>>() {}.type)
|
||||
}
|
||||
|
||||
// same sorting as qispos parser
|
||||
return map.toSortedMap(compareBy<String>{
|
||||
val oText = it.substringAfter(" ")
|
||||
|
||||
if (oText.contains("/")) {
|
||||
oText.substringBefore("/").toInt() + 0.5
|
||||
} else {
|
||||
oText.toDouble()
|
||||
}
|
||||
}.thenBy { it })
|
||||
}
|
||||
|
||||
return sortedMapOf()
|
||||
}
|
||||
|
||||
}
|
|
@ -1,228 +0,0 @@
|
|||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.fragments
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.*
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
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.Preferences
|
||||
import org.mosad.seil0.projectlaogai.databinding.FragmentGradesBinding
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.GradeLinearLayout
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
/**
|
||||
* The grades fragment class
|
||||
* contains all needed parts to display and the grades screen
|
||||
*/
|
||||
class GradesFragment : Fragment() {
|
||||
|
||||
private lateinit var parser: QISPOSParser
|
||||
private lateinit var binding: FragmentGradesBinding
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = FragmentGradesBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.refreshLayoutGrades.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
|
||||
initActions()
|
||||
|
||||
parser = QISPOSParser(requireContext())// init the parser
|
||||
|
||||
if (checkCredentials()) {
|
||||
lifecycleScope.launch(Dispatchers.Default) {
|
||||
// if the cache is older than 24hr, update blocking
|
||||
val currentTime = System.currentTimeMillis() / 1000
|
||||
withContext(Dispatchers.Main) {
|
||||
if ((currentTime - Preferences.gradesCacheTime) > 86400 && checkQisposStatus()) {
|
||||
binding.refreshLayoutGrades.isRefreshing = true
|
||||
CacheController.updateGrades(requireContext()).join()
|
||||
}
|
||||
}
|
||||
|
||||
addGrades()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* initialize the actions
|
||||
*/
|
||||
private fun initActions() = lifecycleScope.launch(Dispatchers.Default) {
|
||||
binding.refreshLayoutGrades.setOnRefreshListener {
|
||||
binding.linLayoutGrades.removeAllViews() // clear layout
|
||||
// TODO add loading textView
|
||||
|
||||
lifecycleScope.launch(Dispatchers.Default) {
|
||||
CacheController.updateGrades(requireContext()).join()
|
||||
addGrades()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the credentials are set, if not show a login dialog
|
||||
*/
|
||||
private fun checkCredentials(): Boolean {
|
||||
val credentials = EncryptedPreferences.readCredentials(requireContext())
|
||||
var credentialsPresent = false
|
||||
|
||||
// if there is no password set, show the login dialog
|
||||
if (credentials.first.isEmpty() || credentials.second.isEmpty()) {
|
||||
LoginDialog(this.requireContext())
|
||||
.positiveButton {
|
||||
EncryptedPreferences.saveCredentials(email, password, context)
|
||||
addGrades()
|
||||
}
|
||||
.negativeButton {
|
||||
binding.txtViewLoading.text = resources.getString(R.string.credentials_missing)
|
||||
}
|
||||
.show {
|
||||
email = EncryptedPreferences.email
|
||||
password = ""
|
||||
}
|
||||
} else {
|
||||
credentialsPresent = true
|
||||
}
|
||||
|
||||
return credentialsPresent
|
||||
}
|
||||
|
||||
/**
|
||||
* check if qispos is available, if not show an error
|
||||
*/
|
||||
private fun checkQisposStatus(): Boolean {
|
||||
val statusCode = parser.checkQISPOSStatus()
|
||||
|
||||
// show error if the status code is not 200
|
||||
if (statusCode != 200) {
|
||||
val infoText = resources.getString(when(statusCode) {
|
||||
503 -> R.string.qispos_unavailable
|
||||
else -> R.string.qispos_generic_error
|
||||
})
|
||||
|
||||
val img = ResourcesCompat.getDrawable(resources, R.drawable.ic_error_outline_black_24dp, null)?.apply {
|
||||
bounds = Rect(0, 0, 75, 75)
|
||||
}
|
||||
|
||||
binding.txtViewLoading.apply {
|
||||
text = infoText
|
||||
setCompoundDrawables(null, null, null, img)
|
||||
}
|
||||
}
|
||||
|
||||
return statusCode == 200
|
||||
}
|
||||
|
||||
/**
|
||||
* add the grades to the layout, async
|
||||
* TODO this is slow as hell
|
||||
*/
|
||||
private fun addGrades() = lifecycleScope.launch(Dispatchers.Default) {
|
||||
val addGradesTime = measureTimeMillis {
|
||||
val grades = CacheController(requireContext()).readGrades()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.linLayoutGrades.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(requireContext())
|
||||
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) {
|
||||
binding.linLayoutGrades.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) {
|
||||
binding.linLayoutGrades.addView(txtViewLegal)
|
||||
binding.refreshLayoutGrades.isRefreshing = false
|
||||
}
|
||||
|
||||
}
|
||||
Log.i(javaClass.name, "added grades in $addGradesTime ms")
|
||||
}
|
||||
|
||||
}
|
|
@ -34,7 +34,6 @@ import com.afollestad.materialdialogs.MaterialDialog
|
|||
import com.afollestad.materialdialogs.WhichButton
|
||||
import com.afollestad.materialdialogs.actions.getActionButton
|
||||
import com.afollestad.materialdialogs.list.listItemsMultiChoice
|
||||
import com.afollestad.materialdialogs.list.listItemsSingleChoice
|
||||
import de.psdev.licensesdialog.LicensesDialog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.joinAll
|
||||
|
@ -42,7 +41,6 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.withContext
|
||||
import org.mosad.seil0.projectlaogai.BuildConfig
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.controller.GradesController
|
||||
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
|
||||
import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.coursesList
|
||||
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
|
||||
|
@ -80,11 +78,6 @@ class SettingsFragment : Fragment() {
|
|||
binding.textUser.text = EncryptedPreferences.email.ifEmpty { resources.getString(R.string.sample_user) }
|
||||
binding.textCourse.text = Preferences.course.courseName
|
||||
binding.textAboutDesc.text = resources.getString(R.string.about_version, BuildConfig.VERSION_NAME, getString(R.string.build_time))
|
||||
binding.textGradesSyncDesc.text = if (Preferences.gradesSyncInterval == 0) {
|
||||
resources.getString(R.string.grades_sync_desc_never)
|
||||
} else {
|
||||
resources.getString(R.string.grades_sync_desc, Preferences.gradesSyncInterval)
|
||||
}
|
||||
binding.switchBuffet.isChecked = Preferences.showBuffet // init switch
|
||||
|
||||
val outValue = TypedValue()
|
||||
|
@ -278,38 +271,6 @@ class SettingsFragment : Fragment() {
|
|||
// }
|
||||
// }
|
||||
|
||||
binding.linLayoutGradesSync.setOnClickListener {
|
||||
val items = resources.getStringArray(R.array.syncInterval).toList()
|
||||
val initial = when (Preferences.gradesSyncInterval) {
|
||||
1 -> 1
|
||||
3 -> 2
|
||||
6 -> 3
|
||||
12 -> 4
|
||||
else -> 0
|
||||
}
|
||||
|
||||
MaterialDialog(requireContext())
|
||||
.title(R.string.grades_sync)
|
||||
.listItemsSingleChoice(items = items, initialSelection = initial) { _, index, _ ->
|
||||
val interval = when (index) {
|
||||
1 -> 1
|
||||
2 -> 3
|
||||
3 -> 6
|
||||
4 -> 12
|
||||
else -> 0
|
||||
}
|
||||
|
||||
Preferences.saveGradesSync(requireContext(), interval)
|
||||
GradesController.updateBackgroundSync(requireContext())
|
||||
|
||||
binding.textGradesSyncDesc.text = if (Preferences.gradesSyncInterval == 0) {
|
||||
resources.getString(R.string.grades_sync_desc_never)
|
||||
} else {
|
||||
resources.getString(R.string.grades_sync_desc, Preferences.gradesSyncInterval)
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
|
||||
binding.switchBuffet.setOnClickListener {
|
||||
Preferences.saveShowBuffet(requireContext(), binding.switchBuffet.isChecked)
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.uicomponents
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import org.mosad.seil0.projectlaogai.databinding.LinearlayoutGradeBinding
|
||||
|
||||
class GradeLinearLayout(context: Context?): LinearLayout(context) {
|
||||
|
||||
private val binding = LinearlayoutGradeBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
|
||||
var subjectName = ""
|
||||
var grade = ""
|
||||
var subSubjectName = ""
|
||||
var subGrade = ""
|
||||
|
||||
fun set(func: GradeLinearLayout.() -> Unit): GradeLinearLayout = apply {
|
||||
func()
|
||||
|
||||
binding.textSubject.text = subjectName
|
||||
binding.textGrade.text = grade
|
||||
binding.textSubSubject.text = subSubjectName
|
||||
binding.textSubGrade.text = subGrade
|
||||
}
|
||||
|
||||
fun disableDivider() {
|
||||
binding.dividerGrade.visibility = View.GONE
|
||||
}
|
||||
|
||||
fun disableSubSubject() {
|
||||
binding.linearSubSubject.visibility = View.GONE
|
||||
}
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.worker
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.mosad.seil0.projectlaogai.MainActivity
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.controller.GradesController
|
||||
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
|
||||
import org.mosad.seil0.projectlaogai.util.NotificationUtils
|
||||
import org.mosad.seil0.projectlaogai.util.NotificationUtils.Companion.CHANNEL_ID_GRADES
|
||||
|
||||
class GradesUpdateWorker(val context: Context, params: WorkerParameters): Worker(context, params) {
|
||||
|
||||
override fun doWork(): Result {
|
||||
|
||||
// check if credentials are present, if not do nothing
|
||||
val credentials = EncryptedPreferences.readCredentials(context)
|
||||
if (credentials.first.isEmpty() || credentials.second.isEmpty()) {
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
val notificationIdChecking = NotificationUtils.getId()
|
||||
val builderChecking = NotificationCompat.Builder(context, CHANNEL_ID_GRADES)
|
||||
.setSmallIcon(R.drawable.ic_grading_black_24dp)
|
||||
.setContentTitle(context.getString(R.string.notification_grades))
|
||||
.setContentText(context.getString(R.string.notification_grades_updating_desc))
|
||||
.setNotificationSilent()
|
||||
NotificationManagerCompat.from(context).apply {
|
||||
notify(notificationIdChecking, builderChecking.build())
|
||||
}
|
||||
|
||||
// get old grades
|
||||
val oldGrades = CacheController(context).readGrades()
|
||||
|
||||
// get update from qispos
|
||||
runBlocking { CacheController.updateGrades(context).join() }
|
||||
val newGrades = CacheController(context).readGrades()
|
||||
|
||||
// check for changes
|
||||
val diff = GradesController().diffGrades(oldGrades, newGrades)
|
||||
|
||||
// show message
|
||||
if (diff.isNotEmpty()) {
|
||||
val text = if (diff.size < 2) {
|
||||
context.getString(R.string.notification_grades_single_desc, diff.first().name)
|
||||
} else {
|
||||
context.getString(R.string.notification_grades_multiple_desc, diff.first().name, (diff.size - 1))
|
||||
}
|
||||
|
||||
val intent = Intent(context, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
action = context.getString(R.string.intent_action_gradesFragment)
|
||||
}
|
||||
|
||||
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
|
||||
|
||||
val builder = NotificationCompat.Builder(context, CHANNEL_ID_GRADES)
|
||||
.setSmallIcon(R.drawable.ic_grading_black_24dp)
|
||||
.setContentTitle(context.getString(R.string.notification_grades))
|
||||
.setContentText(text)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
|
||||
// if there are multiple subjects, use BigText
|
||||
if (diff.size > 1)
|
||||
builder.setStyle(NotificationCompat.BigTextStyle().bigText(text))
|
||||
|
||||
NotificationManagerCompat.from(context).apply {
|
||||
notify(NotificationUtils.getId(), builder.build())
|
||||
}
|
||||
}
|
||||
|
||||
// remove scanning notification
|
||||
NotificationManagerCompat.from(context).apply {
|
||||
cancel(notificationIdChecking)
|
||||
}
|
||||
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/linLayout_grade"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="7dp"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingRight="7dp"
|
||||
android:paddingBottom="3dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linear_subject"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_subject"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/sample_subject"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_grade"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/sample_grade"
|
||||
android:textAlignment="textEnd"
|
||||
android:textSize="15sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linear_sub_subject"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="7dp"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingEnd="0dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_sub_subject"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/sample_sub_subject" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_sub_grade"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/sample_grade_state"
|
||||
android:textAlignment="textEnd" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider_grade"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:background="?dividerColor" />
|
||||
</LinearLayout>
|
|
@ -1,38 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?themePrimary"
|
||||
tools:context=".fragments.GradesFragment">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/refreshLayout_Grades"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView_Grades"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_Grades"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_Loading"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="100dp"
|
||||
android:padding="7dp"
|
||||
android:text="@string/loading_from_hs"
|
||||
android:textAlignment="center"
|
||||
android:textSize="15sp" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</FrameLayout>
|
|
@ -18,7 +18,8 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="11dp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardView_Info"
|
||||
|
@ -340,35 +341,6 @@
|
|||
android:layout_height="1dp"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_GradesSync"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:padding="7dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_grades_sync"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/grades_sync"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_grades_sync_desc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/grades_sync_desc" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider8"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/switch_buffet"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -20,10 +20,6 @@
|
|||
android:id="@+id/nav_moodle"
|
||||
android:icon="@drawable/ic_school_black_24dp"
|
||||
android:title="@string/moodle"/>
|
||||
<item
|
||||
android:id="@+id/nav_grades"
|
||||
android:icon="@drawable/ic_grading_black_24dp"
|
||||
android:title="@string/grades" />
|
||||
<item
|
||||
android:id="@+id/nav_settings"
|
||||
android:icon="@drawable/ic_settings_black_24dp"
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mosad.seil0.projectlaogai.controller.GradesController
|
||||
import org.mosad.seil0.projectlaogai.util.GradeSubject
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
|
||||
class GradesControllerTest {
|
||||
|
||||
@Test
|
||||
fun diffGrades_noDiff() {
|
||||
val origFile = File(GradesControllerTest::class.java.getResource("/grades_orig.json")?.path!!)
|
||||
val diffFile = File(GradesControllerTest::class.java.getResource("/grades_orig.json")?.path!!)
|
||||
|
||||
val mapA: HashMap<String, ArrayList<GradeSubject>> = FileReader(origFile).use {
|
||||
GsonBuilder().create().fromJson(it, object : TypeToken<HashMap<String, ArrayList<GradeSubject>>>() {}.type)
|
||||
}
|
||||
|
||||
val mapB: HashMap<String, ArrayList<GradeSubject>> = FileReader(diffFile).use {
|
||||
GsonBuilder().create().fromJson(it, object : TypeToken<HashMap<String, ArrayList<GradeSubject>>>() {}.type)
|
||||
}
|
||||
|
||||
val exp = ArrayList<GradeSubject>()
|
||||
val ret = GradesController().diffGrades(mapA, mapB)
|
||||
|
||||
Assertions.assertEquals(exp, ret)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun diffGrades_diffSubject() {
|
||||
val origFile = File(GradesControllerTest::class.java.getResource("/grades_orig.json")?.path!!)
|
||||
val diffFile = File(GradesControllerTest::class.java.getResource("/grades_diff_subject.json")?.path!!)
|
||||
|
||||
val mapA: HashMap<String, ArrayList<GradeSubject>> = FileReader(origFile).use {
|
||||
GsonBuilder().create().fromJson(it, object : TypeToken<HashMap<String, ArrayList<GradeSubject>>>() {}.type)
|
||||
}
|
||||
|
||||
val mapB: HashMap<String, ArrayList<GradeSubject>> = FileReader(diffFile).use {
|
||||
GsonBuilder().create().fromJson(it, object : TypeToken<HashMap<String, ArrayList<GradeSubject>>>() {}.type)
|
||||
}
|
||||
|
||||
val exp = arrayListOf(
|
||||
GradeSubject("AI-3010", "Computernetze", "WiSe 18/19", "0,7", "2,0"),
|
||||
GradeSubject("AI-3020", "Datenbanksysteme 1", "WiSe 18/19", "1,7", "2,0"),
|
||||
GradeSubject("AI-3025", "Praktikum Datenbanksysteme", "WiSe 18/19", "bestanden", "3,0")
|
||||
)
|
||||
val ret = GradesController().diffGrades(mapA, mapB)
|
||||
|
||||
Assertions.assertEquals(exp, ret)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun diffGrades_diffSemester() {
|
||||
val origFile = File(GradesControllerTest::class.java.getResource("/grades_orig.json")?.path!!)
|
||||
val diffFile = File(GradesControllerTest::class.java.getResource("/grades_diff_semester.json")?.path!!)
|
||||
|
||||
val mapA: HashMap<String, ArrayList<GradeSubject>> = FileReader(origFile).use {
|
||||
GsonBuilder().create().fromJson(it, object : TypeToken<HashMap<String, ArrayList<GradeSubject>>>() {}.type)
|
||||
}
|
||||
|
||||
val mapB: HashMap<String, ArrayList<GradeSubject>> = FileReader(diffFile).use {
|
||||
GsonBuilder().create().fromJson(it, object : TypeToken<HashMap<String, ArrayList<GradeSubject>>>() {}.type)
|
||||
}
|
||||
|
||||
val exp = arrayListOf(
|
||||
GradeSubject("AI-2010", "Mathemaik 7", "SoSe 19", "1,7", "4,0"),
|
||||
GradeSubject("AI-2015", "Praktikum Mathemaik 7", "SoSe 19", "bestanden", "1,0")
|
||||
)
|
||||
val ret = GradesController().diffGrades(mapA, mapB)
|
||||
|
||||
Assertions.assertEquals(exp, ret)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue