Browse Source

grades ui polishing & sorting fix

pull/46/head
Jannik 2 years ago
parent
commit
638c321798
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
  1. 2
      app/build.gradle
  2. 72
      app/src/main/java/org/mosad/seil0/projectlaogai/controller/QISPOSParser.kt
  3. 3
      app/src/main/java/org/mosad/seil0/projectlaogai/controller/preferences/EncryptedPreferences.kt
  4. 78
      app/src/main/java/org/mosad/seil0/projectlaogai/fragments/GradesFragment.kt
  5. 3
      app/src/main/java/org/mosad/seil0/projectlaogai/fragments/SettingsFragment.kt
  6. 10
      app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/GradeLinearLayout.kt
  7. 10
      app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/dialogs/LoginDialog.kt
  8. 9
      app/src/main/res/drawable/ic_grading_black_24dp.xml
  9. 37
      app/src/main/res/layouts/activities/layout/linearlayout_grade.xml
  10. 28
      app/src/main/res/layouts/fragments/layout/fragment_on_login.xml
  11. 2
      app/src/main/res/layouts/fragments/layout/fragment_settings.xml
  12. 2
      app/src/main/res/menu/activity_main_drawer.xml
  13. 8
      app/src/main/res/values-de-rDE/strings.xml
  14. 16
      app/src/main/res/values/strings.xml

2
app/build.gradle

@ -11,7 +11,7 @@ android {
minSdkVersion 23
targetSdkVersion 29
versionCode 15
versionName "0.5.92"
versionName "0.5.93-RC1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "build_time", buildTime()
setProperty("archivesBaseName", "projectlaogai-$versionName")

72
app/src/main/java/org/mosad/seil0/projectlaogai/controller/QISPOSParser.kt

@ -35,34 +35,33 @@ 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
/**
* TODO context in constructor?
* Parse the qispos site the get all needed data for the grades fragment
*/
class QISPOSParser {
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"
//private val rootPath = "/qispos/rds?state=change&type=1&moduleParameter=studyPOSMenu&nextdir=change&next=menu.vm&subdir=applications&xml=menu&purge=y&navigationPosition=functions%2CstudyPOSMenu&breadcrumb=studyPOSMenu&topitem=functions&subitem=studyPOSMenu"
fun parseGrades(context: Context): HashMap<String, ArrayList<gradeSubject>> {
/**
* 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(context)
val gradesListHtml = readGrades()
gradesListHtml?.select("table > tbody > tr")?.forEach {
//val row = it.select("td.qis_konto")
val row = it.select("td.tabelle1_alignleft,td.tabelle1_aligncenter,td.tabelle1_alignright")
//println("-----------------------------------------------------------")
//println(it.select("td.qis_konto"))
//if(row.size >= 6) {
// only real subjects will be selected
if(row.size >= 6 && row[0].text().length >=7) {
val subject = gradeSubject(
@ -75,39 +74,30 @@ class QISPOSParser {
if (gradesMap.containsKey(subject.semester)) {
gradesMap[subject.semester]!!.add(subject)
println(subject.name)
} else {
gradesMap[subject.semester] = arrayListOf(subject)
println(subject.name)
}
println("ID: ${row[0].text()}")
println("Subject Name: ${row[1].text()}")
println("Semester: ${row[2].text()}")
println("Grade: ${row[3].text()}")
println("Credits: ${row[5].text()}")
println("------------------------------------------------------")
}
}
println(gradesMap)
gradesMap.forEach {
println("${it.key}: ${it.value}")
}
// TODO sort
println("finished parsing!")
// return the sorted map
return gradesMap.toSortedMap(compareBy<String>{
val oText = it.substringAfter(" ")
return gradesMap
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(context: Context): Element?{
private fun readGrades(): Element?{
val credentials = EncryptedPreferences.readCredentials(context)
val username = credentials.first.substringBefore("@")
@ -116,10 +106,7 @@ class QISPOSParser {
return runBlocking {
withContext(Dispatchers.IO) {
try {
println("querying qispos ...")
println("using $username with ********")
val socketFactory = createSSLSocketFactory(context)
val socketFactory = createSSLSocketFactory()
// login, asdf = username, fdsa = password, wtf
val list = mapOf(
@ -136,16 +123,13 @@ class QISPOSParser {
.data(list)
.postDataCharset("UTF-8")
.execute()
Log.i(className, "login status is: ${res.statusMessage()}: ${res.statusCode()}")
val loginCookies = res.cookies()
println("cookies: $loginCookies")
println("status is: ${res.statusMessage()}: ${res.statusCode()}")
// grades root document and url
val rootHtml =Jsoup.parse(res.body())
val gradesRootLink = rootHtml.select("li.menueListStyle > a.auflistung").last().attr("abs:href")
println(gradesRootLink)
// parse grades url
val gradesHtml = Jsoup.connect(gradesRootLink)
@ -156,7 +140,6 @@ class QISPOSParser {
.get()
val gradesNextLink = gradesHtml.select("li.treelist > a.regular").attr("abs:href")
println(gradesNextLink)
val gradesNextHtml = Jsoup.connect(gradesNextLink)
.sslSocketFactory(socketFactory)
@ -166,7 +149,6 @@ class QISPOSParser {
.get()
val gradesListLink = gradesNextHtml.selectFirst("li.treelist > ul > li").selectFirst("a").attr("abs:href")
println(gradesListLink)
// get the grades list
val gradesListHtml = Jsoup.connect(gradesListLink)
@ -179,7 +161,7 @@ class QISPOSParser {
//println(gradesListHtml.body())
gradesListHtml.body()
} catch (ex: Exception) {
Log.e(className, "could not load qispos", ex)
Log.e(className, "error while loading qispos", ex)
null
}
}
@ -187,7 +169,10 @@ class QISPOSParser {
}
private fun createSSLSocketFactory(context: Context): SSLSocketFactory {
/**
* 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")
@ -195,7 +180,6 @@ class QISPOSParser {
val ca = caInput.use {
cf.generateCertificate(it) as X509Certificate
}
println("ca=" + ca.subjectDN)
// Create a KeyStore containing our trusted CAs
val keyStoreType = KeyStore.getDefaultType()

3
app/src/main/java/org/mosad/seil0/projectlaogai/controller/preferences/EncryptedPreferences.kt

@ -71,8 +71,7 @@ object EncryptedPreferences {
fun load(context: Context) {
with(getEncryptedPreferences(context)) {
email = this?.getString(
context.getString(R.string.save_key_user_email),
context.getString(R.string.sample_user)
context.getString(R.string.save_key_user_email), ""
).toString()
}
}

78
app/src/main/java/org/mosad/seil0/projectlaogai/fragments/GradesFragment.kt

@ -20,13 +20,13 @@
*
*/
package org.mosad.seil0.projectlaogai.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import kotlinx.android.synthetic.main.fragment_grades.*
@ -36,11 +36,14 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.QISPOSParser
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.GradeLinearLayout
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
/**
* A simple [Fragment] subclass.
* The grades fragment class
* contains all needed parts to display and the grades screen
*/
class GradesFragment : Fragment() {
@ -52,20 +55,51 @@ class GradesFragment : Fragment() {
refreshLayoutGrades = view.findViewById(R.id.refreshLayout_Grades)
refreshLayoutGrades.isEnabled = false // disable swipe
GlobalScope.launch(Dispatchers.Default) {
if (checkCredentials()) {
GlobalScope.launch(Dispatchers.Default) {
addGrades()
}
}
return view
}
/**
* check if the credentials are set, if not show a login dialog
*/
private fun checkCredentials(): Boolean {
val credentials = EncryptedPreferences.readCredentials(context!!)
var credentialsPresent = false
// if there is no password set, show the login dialog
if (credentials.first.isEmpty() || credentials.second.isEmpty()) {
LoginDialog(this.context!!)
.positiveButton {
EncryptedPreferences.saveCredentials(email, password, context)
addGrades()
}
.negativeButton {
txtView_Loading.text = resources.getString(R.string.credentials_missing)
}
.show {
email = EncryptedPreferences.email
password = ""
}
} else {
credentialsPresent = true
}
return credentialsPresent
}
// add the grades to the layout, async
private fun addGrades() = GlobalScope.launch(Dispatchers.Default) {
withContext(Dispatchers.Main) {
refreshLayout_Grades.isRefreshing = true
}
val parser = QISPOSParser()
val grades = parser.parseGrades(context!!)
val parser = QISPOSParser(context!!)
val grades = parser.parseGrades()
withContext(Dispatchers.Main) {
linLayout_Grades.removeAllViews() // clear layout
@ -73,16 +107,40 @@ class GradesFragment : Fragment() {
// 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()
// FIXME this is broken
// disable divider if last element
//if (index == semester.value.lastIndex || semester.value.indexOf(subSubject) == semester.value.lastIndex)
if (index == semester.value.lastIndex)
subjectLayout.disableDivider()
@ -94,11 +152,17 @@ class GradesFragment : Fragment() {
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
}
//refreshLayoutGrades.isRefreshing = false
}
}

3
app/src/main/java/org/mosad/seil0/projectlaogai/fragments/SettingsFragment.kt

@ -105,7 +105,7 @@ class SettingsFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
// initialize the settings gui
txtView_User.text = EncryptedPreferences.email
txtView_User.text = EncryptedPreferences.email.ifEmpty { resources.getString(R.string.sample_user) }
txtView_Course.text = cCourse.courseName
txtView_AboutDesc.text = resources.getString(R.string.about_version, BuildConfig.VERSION_NAME, getString(R.string.build_time))
switch_buffet.isChecked = cShowBuffet // init switch
@ -140,7 +140,6 @@ class SettingsFragment : Fragment() {
// open a new dialog
LoginDialog(context!!)
.positiveButton {
println("Test: $password")
EncryptedPreferences.saveCredentials(email, password, context)
}
.show {

10
app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/GradeLinearLayout.kt

@ -33,6 +33,8 @@ class GradeLinearLayout(context: Context?): LinearLayout(context) {
var subjectName = ""
var grade = ""
var subSubjectName = ""
var subGrade = ""
init {
CardView.inflate(context, R.layout.linearlayout_grade, this)
@ -43,9 +45,15 @@ class GradeLinearLayout(context: Context?): LinearLayout(context) {
txtView_subject.text = subjectName
txtView_grade.text = grade
txtView_sub_subject.text = subSubjectName
txtView_sub_grade.text = subGrade
}
fun disableDivider() {
divider_grades.visibility = View.GONE
divider_grade.visibility = View.GONE
}
fun disableSubSubject() {
linLayout_sub_subject.visibility = View.GONE
}
}

10
app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/dialogs/LoginDialog.kt

@ -45,8 +45,8 @@ class LoginDialog(val context: Context) {
var password = ""
init {
dialog.title(R.string.grades_heading)
.message(R.string.grades_desc_on)
dialog.title(R.string.login_heading)
.message(R.string.login_desc_on)
.customView(R.layout.dialog_login)
.positiveButton(R.string.save)
.negativeButton(R.string.cancel)
@ -69,6 +69,12 @@ class LoginDialog(val context: Context) {
}
}
fun negativeButton(func: LoginDialog.() -> Unit): LoginDialog = apply {
dialog.negativeButton {
func()
}
}
fun show(func: LoginDialog.() -> Unit): LoginDialog = apply {
func()

9
app/src/main/res/drawable/ic_grading_black_24dp.xml

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M4,7h16v2H4V7zM4,13h16v-2H4V13zM4,17h7v-2H4V17zM4,21h7v-2H4V21zM15.41,18.17L14,16.75l-1.41,1.41L15.41,21L20,16.42L18.58,15L15.41,18.17zM4,3v2h16V3H4z"
android:fillColor="#000000"/>
</vector>

37
app/src/main/res/layouts/activities/layout/linearlayout_grade.xml

@ -10,6 +10,7 @@
android:paddingBottom="3dp">
<LinearLayout
android:id="@+id/linLayout_subject"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
@ -19,23 +20,51 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Mathematik 1"
android:text="@string/sample_subject"
android:textSize="15sp" />
<TextView
android:id="@+id/txtView_grade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:text="2,0"
android:text="@string/sample_grade"
android:textAlignment="textEnd"
android:textSize="15sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/linLayout_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/txtView_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/txtView_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_grades"
android:id="@+id/divider_grade"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="2dp"
android:layout_marginTop="3dp"
android:background="?dividerColor" />
</LinearLayout>

28
app/src/main/res/layouts/fragments/layout/fragment_on_login.xml

@ -36,22 +36,22 @@
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/txtView_Grades"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/grades_heading"
android:textAlignment="center"
android:textSize="26sp"
android:textStyle="bold" />
android:id="@+id/txtView_Grades"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/login_heading"
android:textAlignment="center"
android:textSize="26sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtView_GradesDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:text="@string/grades_desc_on"
android:textAlignment="center"
android:textSize="18sp" />
android:id="@+id/txtView_GradesDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:text="@string/login_desc_on"
android:textAlignment="center"
android:textSize="18sp" />
<EditText
android:id="@+id/editText_email"

2
app/src/main/res/layouts/fragments/layout/fragment_settings.xml

@ -65,7 +65,7 @@
android:id="@+id/txtView_UserDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/user" />
android:text="@string/user_desc" />
</LinearLayout>
<View

2
app/src/main/res/menu/activity_main_drawer.xml

@ -22,7 +22,7 @@
android:title="@string/moodle"/>
<item
android:id="@+id/nav_grades"
android:icon="@drawable/icon_checkmark_black"
android:icon="@drawable/ic_grading_black_24dp"
android:title="@string/grades" />
<item
android:id="@+id/nav_settings"

8
app/src/main/res/values-de-rDE/strings.xml

@ -19,8 +19,8 @@
<string name="get_started">los geht\'s</string>
<string name="course_heading">Studiengang</string>
<string name="course_desc_on">Wähle deinen aktuellen Studiengang.\nZusätzliche Vorlesungen können später hinzugefügt werden.</string>
<string name="grades_heading">Noten</string>
<string name="grades_desc_on">Project Laogai kann auf die Notenverwaltung zugreifen. Deine Login-Daten werden verschlüsselt auf deinem Gerät gespeichert. Diese Funktion wird "as is" und ohne Garantie bereitgestellt.</string>
<string name="login_heading">Login</string>
<string name="login_desc_on">Project Laogai kann auf die Notenverwaltung zugreifen. Deine Login-Daten werden verschlüsselt auf deinem Gerät gespeichert. Diese Funktion wird "as is" und ohne Garantie bereitgestellt.</string>
<string name="email">E-Mail</string>
<string name="password">Passwort</string>
<string name="login">login</string>
@ -41,10 +41,12 @@
<!-- fragment_grades -->
<string name="loading_from_hs">Lade Daten von den Hochschul Servern.\nDas kann eine Weile dauern.</string>
<string name="credentials_missing">Diese Funktion benötigt deine Login-Daten. Bitte logge dich über die Einstellungen ein.</string>
<string name="without_guarantee">Alle Angaben ohne Gewähr.</string>
<!-- fragment_settings -->
<string name="info">Info</string>
<string name="user">Benutzer</string>
<string name="user_desc">Zum bearbeiten tippen</string>
<string name="course_desc">Tippe um den Kurs zu ändern</string>
<string name="manage_lessons">Bearbeite Zusätzliche Vorlesungen</string>
<string name="manage_lessons_desc">Tippe um zusätzliche Vorlesungen zu bearbeiten</string>

16
app/src/main/res/values/strings.xml

@ -21,8 +21,8 @@
<string name="get_started">get started</string>
<string name="course_heading">Course</string>
<string name="course_desc_on">Select your current course.\nAdditional lessons can be added later.</string>
<string name="grades_heading">Grades</string>
<string name="grades_desc_on">Project Laogai can connect to the HIS Online-Portal. Your Login-Data will be stored encrypted on your device. This feature is provided "as is", without any warranty.</string>
<string name="login_heading">Login</string>
<string name="login_desc_on">Project Laogai can connect to the HIS Online-Portal. Your Login-Data will be stored encrypted on your device. This feature is provided "as is", without any guarantee.</string>
<string name="email">E-Mail</string>
<string name="password">Password</string>
<string name="login">login</string>
@ -43,10 +43,12 @@
<!-- fragment_grades -->
<string name="loading_from_hs">Loading data from the university servers.\nThis may take a while.</string>
<string name="credentials_missing">This feature needs your Login-Data to work. Please login via the settings.</string>
<string name="without_guarantee">All information is supplied without guarantee.</string>
<!-- fragment_settings -->
<string name="info">Info</string>
<string name="user">User</string>
<string name="user_desc">Tap to edit</string>
<string name="course_desc">Tap to change course</string>
<string name="manage_lessons">Manage Additional Lessons</string>
<string name="manage_lessons_desc">Tap to manage additional lessons</string>
@ -79,8 +81,12 @@
<!-- sample strings -->
<string name="sample_user" translatable="false">spinefield@stud.hs-offenburg.de</string>
<string name="sample_course" translatable="false">SampleCourse 3</string>
<string name="sample_date" translatable="false">Montag, 30.02</string>
<string name="sample_course" translatable="false">Everything</string>
<string name="sample_subject" translatable="false">Earthbending</string>
<string name="sample_sub_subject" translatable="false">Applied Earthbending</string>
<string name="sample_grade" translatable="false">1,0</string>
<string name="sample_grade_state" translatable="false">passed</string>
<string name="sample_date" translatable="false">Monday, 30.02</string>
<string name="a_time" translatable="false">0.00 – 23.59</string>
<!-- errors -->

Loading…
Cancel
Save

Du besuchst diese Seite mit einem veralteten IPv4-Internetzugang. Möglicherweise treten in Zukunft Probleme mit der Erreichbarkeit und Performance auf. Bitte frage deinen Internetanbieter oder Netzwerkadministrator nach IPv6-Unterstützung.
You are visiting this site with an outdated IPv4 internet access. You may experience problems with accessibility and performance in the future. Please ask your ISP or network administrator for IPv6 support.
Weitere Infos | More Information
Klicke zum schließen | Click to close