Browse Source

added timeout and other error handling

feature/exam_notifications
Nareshkumar Rao 3 years ago
parent
commit
09f63cb4da
  1. 4
      app/build.gradle
  2. BIN
      app/release/app-release.apk
  3. 4
      app/release/output-metadata.json
  4. 150
      app/src/main/java/com/nareshkumarrao/eiweblog/HISUtility.kt
  5. 2
      app/src/main/java/com/nareshkumarrao/eiweblog/UpdateWorker.kt
  6. 19
      app/src/main/java/com/nareshkumarrao/eiweblog/Utilities.kt
  7. 15
      app/src/main/java/com/nareshkumarrao/eiweblog/ui/main/SectionsFragment.kt
  8. 1
      app/src/main/res/layout/dialog_login.xml
  9. 2
      app/src/main/res/values-de/strings.xml
  10. 2
      app/src/main/res/values-en/strings.xml
  11. 2
      app/src/main/res/values/strings.xml
  12. 1
      images/launcher.svg

4
app/build.gradle

@ -11,8 +11,8 @@ android {
applicationId "com.nareshkumarrao.eiweblog"
minSdkVersion 16
targetSdkVersion 30
versionCode 5
versionName "0.10.1"
versionCode 6
versionName "0.10.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

BIN
app/release/app-release.apk

Binary file not shown.

4
app/release/output-metadata.json

@ -10,8 +10,8 @@
{
"type": "SINGLE",
"filters": [],
"versionCode": 5,
"versionName": "0.10.1",
"versionCode": 6,
"versionName": "0.10.2",
"outputFile": "app-release.apk"
}
]

150
app/src/main/java/com/nareshkumarrao/eiweblog/HISUtility.kt

@ -1,17 +1,20 @@
package com.nareshkumarrao.eiweblog
import android.app.Activity
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.widget.Toast
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import org.jsoup.Connection
import org.jsoup.Jsoup
import java.net.SocketTimeoutException
data class ExamRow(val name: String, val grade: String, val attempt: String, val date: String)
@ -34,17 +37,17 @@ internal object HISUtility {
return
}
val newRows: MutableList<ExamRow> = mutableListOf()
fetchExamRows(context) { examRows ->
if (examRows != null) {
for (examRow in examRows) {
if (!savedRows.contains(examRow)) {
newRows.add(examRow)
}
val newRows: MutableList<ExamRow> = mutableListOf()
fetchExamRows(context) { examRows ->
if (examRows != null) {
for (examRow in examRows) {
if (!savedRows.contains(examRow)) {
newRows.add(examRow)
}
}
callback(newRows)
}
callback(newRows)
}
}
@ -77,75 +80,118 @@ internal object HISUtility {
?: return null
val runnable = Runnable {
val postData: MutableMap<String, String> = mutableMapOf()
postData["asdf"] = username
postData["fdsa"] = password
val loginPage = Jsoup.connect(context.getString(R.string.ossc_login_post))
try {
val postData: MutableMap<String, String> = mutableMapOf()
postData["asdf"] = username
postData["fdsa"] = password
val loginPage = Jsoup.connect(context.getString(R.string.ossc_login_post))
.method(Connection.Method.POST)
.userAgent("Mozilla")
.data(postData)
.timeout(60000)
.execute()
val selectNotenspiegel = Jsoup.connect(context.getString(R.string.ossc_select_noten))
.userAgent("Mozilla")
.cookies(loginPage.cookies())
.get()
val notenspiegelURL = selectNotenspiegel.select("a[href]:containsOwn(Notenspiegel)").first()?.attr("href")
?: kotlin.run {
callback(null)
return@Runnable
}
val selectNotenspiegel =
Jsoup.connect(context.getString(R.string.ossc_select_noten))
.userAgent("Mozilla")
.cookies(loginPage.cookies())
.timeout(60000)
.get()
val notenspiegelURL =
selectNotenspiegel.select("a[href]:containsOwn(Notenspiegel)").first()
?.attr("href")
?: kotlin.run {
callback(null)
return@Runnable
}
val selectStudiengangUnhide = Jsoup.connect(notenspiegelURL)
val selectStudiengangUnhide = Jsoup.connect(notenspiegelURL)
.userAgent("Mozilla")
.cookies(loginPage.cookies())
.timeout(60000)
.get()
val selectStudiengangUnhideURL = selectStudiengangUnhide.select("a[href]:containsOwn(Abschluss)").first().attr("href")
val selectStudiengangUnhideURL =
selectStudiengangUnhide.select("a[href]:containsOwn(Abschluss)").first()
.attr("href")
val selectStudiengang = Jsoup.connect(selectStudiengangUnhideURL)
val selectStudiengang = Jsoup.connect(selectStudiengangUnhideURL)
.userAgent("Mozilla")
.cookies(loginPage.cookies())
.timeout(60000)
.get()
val studiengangURL = selectStudiengang.select("a[href]:containsOwn(Leistungen anzeigen)").first().attr("href")
val studiengangURL =
selectStudiengang.select("a[href]:containsOwn(Leistungen anzeigen)").first()
.attr("href")
val notenSpiegelPage = Jsoup.connect(studiengangURL)
val notenSpiegelPage = Jsoup.connect(studiengangURL)
.userAgent("Mozilla")
.cookies(loginPage.cookies())
.timeout(60000)
.get()
val allGradesRows = notenSpiegelPage.select("div.fixedContainer > table > tbody > tr")
val examRows: MutableList<ExamRow> = mutableListOf()
for (row in allGradesRows) {
if (row.select("td.tabelle1_alignleft").size < 1) {
continue
val allGradesRows =
notenSpiegelPage.select("div.fixedContainer > table > tbody > tr")
val examRows: MutableList<ExamRow> = mutableListOf()
for (row in allGradesRows) {
if (row.select("td.tabelle1_alignleft").size < 1) {
continue
}
val columns = row.select("td")
if (columns.size < 1) {
continue
}
val examRow = ExamRow(
columns[1].text(),
columns[3].text(),
columns[6].text(),
columns[7].text()
)
examRows.add(examRow)
}
val columns = row.select("td")
if (columns.size < 1) {
continue
saveExamRows(context, examRows)
callback(examRows)
} catch (e: Exception) {
if (context is Activity) context.runOnUiThread {
when (e) {
is SocketTimeoutException -> {
Toast.makeText(
context,
context.getString(R.string.ossc_timeout_message),
Toast.LENGTH_LONG
).show()
}
else -> {
Toast.makeText(context, e.localizedMessage, Toast.LENGTH_LONG).show()
}
}
context.finish()
}
val examRow = ExamRow(columns[1].text(), columns[3].text(), columns[6].text(), columns[7].text())
examRows.add(examRow)
}
saveExamRows(context, examRows)
callback(examRows)
}
return Thread(runnable).start()
}
fun sendNotification(context: Context?, examRow: ExamRow, id:Int) {
fun sendNotification(context: Context?, examRow: ExamRow, id: Int) {
val intent = Intent(context, NotificationSettingsActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val builder = NotificationCompat.Builder(context!!, context.getString(R.string.grades_notification_channel_id))
.setSmallIcon(R.drawable.ic_stat_name)
.setContentTitle(context.getString(R.string.exam_results_notification))
.setStyle(NotificationCompat.BigTextStyle()
.bigText("${examRow.name}: ${examRow.grade}"))
val builder = NotificationCompat.Builder(
context!!,
context.getString(R.string.grades_notification_channel_id)
)
.setSmallIcon(R.drawable.ic_stat_name)
.setContentTitle(context.getString(R.string.exam_results_notification))
.setStyle(
NotificationCompat.BigTextStyle()
.bigText("${examRow.name}: ${examRow.grade}")
)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true)
@ -155,15 +201,21 @@ internal object HISUtility {
}
fun createNotificationChannel(context: Context?){
fun createNotificationChannel(context: Context?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = context?.getString(R.string.grades_notification_channel_name)
val descriptionText = context?.getString(R.string.grades_notification_channel_description)
val descriptionText =
context?.getString(R.string.grades_notification_channel_description)
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(context?.getString(R.string.grades_notification_channel_id), name, importance).apply {
val channel = NotificationChannel(
context?.getString(R.string.grades_notification_channel_id),
name,
importance
).apply {
description = descriptionText
}
val notificationManager: NotificationManager = context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationManager: NotificationManager =
context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}

2
app/src/main/java/com/nareshkumarrao/eiweblog/UpdateWorker.kt

@ -16,11 +16,13 @@ class UpdateWorker(private val context: Context, workerParams: WorkerParameters)
if(weblogNotificationsEnabled!!){
Utilities.weblogList(context) { articles ->
articles ?: return@weblogList
val lastArticle = Utilities.getLatestRelevantArticle(articles)!!
val hashString = lastArticle.title + lastArticle.content + lastArticle.date
val oldHash = md5(hashString)
Utilities.fetchWeblogXML(applicationContext){newArticles ->
newArticles ?: return@fetchWeblogXML
val lastNewArticle = Utilities.getLatestRelevantArticle(newArticles)!!
val newHashString = lastNewArticle.title + lastNewArticle.content + lastNewArticle.date
val newHash = md5(newHashString)

19
app/src/main/java/com/nareshkumarrao/eiweblog/Utilities.kt

@ -22,22 +22,22 @@ import java.io.StringReader
internal object Utilities {
fun weblogList(context: Context?, function: (d: List<Article>) -> Unit){
fun weblogList(context: Context?, function: (d: List<Article>?) -> Unit) {
val sharedPref = context?.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE)
val weblogResponse = sharedPref?.getString(context.getString(R.string.weblog_response_key), null)
if (weblogResponse == null){
if (weblogResponse == null) {
fetchWeblogXML(context, function)
return
}
val parser: XmlPullParser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
parser.setInput( StringReader(weblogResponse) )
parser.setInput(StringReader(weblogResponse))
parser.nextTag()
function(parseXML(parser))
}
fun fetchWeblogXML(context: Context?, function: (d: List<Article>) -> Unit) {
fun fetchWeblogXML(context: Context?, callback: (d: List<Article>?) -> Unit) {
val queue = Volley.newRequestQueue(context)
@ -49,7 +49,7 @@ internal object Utilities {
val sharedPref = context?.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE)
if (sharedPref != null) {
with (sharedPref.edit()) {
with(sharedPref.edit()) {
putString(context.getString(R.string.weblog_response_key), responseStr)
apply()
}
@ -58,14 +58,17 @@ internal object Utilities {
val parser: XmlPullParser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
//Log.d("XMLLIST", responseStr )
parser.setInput( StringReader(responseStr) )
parser.setInput(StringReader(responseStr))
parser.nextTag()
val articles = parseXML(parser)
function(articles)
callback(articles)
},
{ error -> Log.e("XMLLIST", error.toString()) })
{ error ->
Log.e("XMLLIST", error.toString())
callback(null)
})
queue.add(stringRequest)
}

15
app/src/main/java/com/nareshkumarrao/eiweblog/ui/main/SectionsFragment.kt

@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -48,12 +49,18 @@ class SectionsFragment : Fragment() {
}
}
private fun updateView(get_articles: List<Article>){
this.swipeRefreshLayout?.isRefreshing=false
private fun updateView(get_articles: List<Article>?) {
this.swipeRefreshLayout?.isRefreshing = false
if (get_articles == null) {
Toast.makeText(context, getString(R.string.load_weblog_error_message), Toast.LENGTH_LONG).show()
return
}
val articles: MutableList<Article> = mutableListOf()
val title = arguments?.getString(ARG_SECTION_NAME) ?: return
for (article in get_articles){
if(article.category == title){
for (article in get_articles) {
if (article.category == title) {
articles.add(article)
}
}

1
app/src/main/res/layout/dialog_login.xml

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

2
app/src/main/res/values-de/strings.xml

@ -32,4 +32,6 @@
<string name="enable_grades_notifications_text">Benachrichtige mich bei neuem Klausurergebnis</string>
<string name="login_dialog_title">Anmelden bei OSSC</string>
<string name="error">FEHLER!</string>
<string name="ossc_timeout_message">Timeout-Fehler bei OSSC. Versuch mal später.</string>
<string name="load_weblog_error_message">Weblog kann nicht aktualisiert werden. Versuch mal später.</string>
</resources>

2
app/src/main/res/values-en/strings.xml

@ -33,4 +33,6 @@
<string name="enable_grades_notifications_text">Notify me when I get new grades</string>
<string name="login_dialog_title">Login to OSSC</string>
<string name="error">ERROR!</string>
<string name="ossc_timeout_message">OSSC has timed out. Try again later.</string>
<string name="load_weblog_error_message">Could not load new weblog data. Try again later.</string>
</resources>

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

@ -54,4 +54,6 @@
<string name="enable_grades_notifications_key" translatable="false">com.nareshkumarrao.eiweblog.grades_notifications.key</string>
<string name="login_dialog_title">Login to OSSC</string>
<string name="error">ERROR!</string>
<string name="ossc_timeout_message">OSSC has timed out. Try again later.</string>
<string name="load_weblog_error_message">Could not load new weblog data. Try again later.</string>
</resources>

1
images/launcher.svg

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--suppress ALL -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Loading…
Cancel
Save