Browse Source

implemented exam grades and notifications

pull/2/head
Nareshkumar Rao 4 years ago
parent
commit
d2847daed7
  1. 9
      app/build.gradle
  2. BIN
      app/release/app-release.apk
  3. 4
      app/release/output-metadata.json
  4. 12
      app/src/main/AndroidManifest.xml
  5. 113
      app/src/main/java/com/nareshkumarrao/eiweblog/GradesActivity.kt
  6. 173
      app/src/main/java/com/nareshkumarrao/eiweblog/HISUtility.kt
  7. 7
      app/src/main/java/com/nareshkumarrao/eiweblog/MainActivity.kt
  8. 21
      app/src/main/java/com/nareshkumarrao/eiweblog/NotificationSettingsActivity.kt
  9. 39
      app/src/main/java/com/nareshkumarrao/eiweblog/UpdateWorker.kt
  10. 10
      app/src/main/java/com/nareshkumarrao/eiweblog/Utilities.kt
  11. 46
      app/src/main/java/com/nareshkumarrao/eiweblog/ui/main/ItemGradesAdapter.kt
  12. 2
      app/src/main/res/layout/activity_about.xml
  13. 54
      app/src/main/res/layout/activity_grades.xml
  14. 31
      app/src/main/res/layout/activity_notification_settings.xml
  15. 64
      app/src/main/res/layout/dialog_login.xml
  16. 79
      app/src/main/res/layout/item_grades.xml
  17. 5
      app/src/main/res/menu/toolbar_menu.xml
  18. 14
      app/src/main/res/values-de/strings.xml
  19. 14
      app/src/main/res/values-en/strings.xml
  20. 1
      app/src/main/res/values/colors.xml
  21. 29
      app/src/main/res/values/strings.xml
  22. 2
      app/src/main/res/values/themes.xml

9
app/build.gradle

@ -11,8 +11,8 @@ android {
applicationId "com.nareshkumarrao.eiweblog" applicationId "com.nareshkumarrao.eiweblog"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 30
versionCode 4
versionName "0.9.3"
versionCode 5
versionName "0.10.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@ -42,12 +42,13 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.android.volley:volley:1.2.0' implementation 'com.android.volley:volley:1.2.0'
implementation "androidx.work:work-runtime-ktx:2.5.0" implementation "androidx.work:work-runtime-ktx:2.5.0"
implementation 'org.jsoup:jsoup:1.13.1'
implementation 'com.google.code.gson:gson:2.8.6'
} }

BIN
app/release/app-release.apk

Binary file not shown.

4
app/release/output-metadata.json

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

12
app/src/main/AndroidManifest.xml

@ -11,14 +11,18 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.EIWeblog"> android:theme="@style/Theme.EIWeblog">
<activity
android:name=".GradesActivity"
android:parentActivityName=".MainActivity"
android:theme="@style/Theme.EIWeblog.NoActionBar" />
<activity <activity
android:name=".AboutActivity" android:name=".AboutActivity"
android:theme="@style/Theme.EIWeblog.NoActionBar"
android:parentActivityName=".MainActivity" />
android:parentActivityName=".MainActivity"
android:theme="@style/Theme.EIWeblog.NoActionBar" />
<activity <activity
android:name=".NotificationSettingsActivity" android:name=".NotificationSettingsActivity"
android:theme="@style/Theme.EIWeblog.NoActionBar"
android:parentActivityName=".MainActivity" />
android:parentActivityName=".MainActivity"
android:theme="@style/Theme.EIWeblog.NoActionBar" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:label="@string/app_name" android:label="@string/app_name"

113
app/src/main/java/com/nareshkumarrao/eiweblog/GradesActivity.kt

@ -0,0 +1,113 @@
package com.nareshkumarrao.eiweblog
import android.app.AlertDialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.view.View
import android.widget.EditText
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.nareshkumarrao.eiweblog.ui.main.ItemArticleAdapter
import com.nareshkumarrao.eiweblog.ui.main.ItemGradesAdapter
class GradesActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_grades)
val myToolbar = findViewById<View>(R.id.grades_toolbar) as Toolbar
setSupportActionBar(myToolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowTitleEnabled(false)
findViewById<RecyclerView>(R.id.grades_recycler).apply {
layoutManager = LinearLayoutManager(this@GradesActivity)
adapter = ItemArticleAdapter(listOf())
}
findViewById<RecyclerView>(R.id.grades_recycler).addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
val swipeRefreshLayout = findViewById<SwipeRefreshLayout>(R.id.grades_swipe_refresh)
swipeRefreshLayout?.setOnRefreshListener {
HISUtility.fetchExamRows(this, ::updateExamRows)
}
val sharedPref = getSharedPreferences(
getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
val username = sharedPref?.getString(getString(R.string.username_key), null)
if( username == null ){
val loginDialog = LoginDialogFragment(this, false) {
HISUtility.fetchExamRows(this, ::updateExamRows)
}
loginDialog.show(supportFragmentManager, "loginDialog")
}else{
val savedRows = HISUtility.getSavedExamRows(this)
if(savedRows == null){
HISUtility.fetchExamRows(this, ::updateExamRows)
}else{
updateExamRows(savedRows)
}
}
}
private fun updateExamRows(examRows: List<ExamRow>?){
examRows ?: run {
val loginDialog = LoginDialogFragment(this, true){
HISUtility.fetchExamRows(this, ::updateExamRows)
}
loginDialog.show(supportFragmentManager, "loginDialog")
return
}
this@GradesActivity.runOnUiThread {
findViewById<RecyclerView>(R.id.grades_recycler)?.apply {
layoutManager = LinearLayoutManager(this@GradesActivity)
adapter = ItemGradesAdapter(examRows)
}
findViewById<SwipeRefreshLayout>(R.id.grades_swipe_refresh).isRefreshing=false
findViewById<ProgressBar>(R.id.gradesProgressBar).visibility=View.GONE
}
}
}
class LoginDialogFragment(val context: GradesActivity, private val isError: Boolean, val loginCallback: () -> Unit?) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
val inflater = requireActivity().layoutInflater
val dialogView = inflater.inflate(R.layout.dialog_login, null)
if(isError){
dialogView.findViewById<TextView>(R.id.errorText).visibility=View.VISIBLE
}else{
dialogView.findViewById<TextView>(R.id.errorText).visibility=View.INVISIBLE
}
builder.setView(dialogView)
.setPositiveButton(
R.string.login
) { _, _ ->
val username = dialogView.findViewById<EditText>(R.id.loginUsername).text.toString()
val password = dialogView.findViewById<EditText>(R.id.loginPassword).text.toString()
HISUtility.setUsernamePassword(context, username, password)
loginCallback()
}
.setNegativeButton(
R.string.cancel
) { _, _ ->
dialog?.cancel()
context.finish()
}
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}

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

@ -0,0 +1,173 @@
package com.nareshkumarrao.eiweblog
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 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
data class ExamRow(val name: String, val grade: String, val attempt: String, val date: String)
internal object HISUtility {
fun setUsernamePassword(context: Context?, username: String, password: String) {
val sharedPref = context?.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE)
if (sharedPref != null) {
with(sharedPref.edit()) {
putString(context.getString(R.string.username_key), username)
putString(context.getString(R.string.password_key), password)
apply()
}
}
}
fun checkForUpdates(context: Context?, callback: (examRows: List<ExamRow>?) -> Unit) {
val savedRows = getSavedExamRows(context) ?: run {
callback(null)
return
}
val newRows: MutableList<ExamRow> = mutableListOf()
fetchExamRows(context) { examRows ->
if (examRows != null) {
for (examRow in examRows) {
if (!savedRows.contains(examRow)) {
newRows.add(examRow)
}
}
}
callback(newRows)
}
}
fun getSavedExamRows(context: Context?): List<ExamRow>? {
val sharedPref = context?.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE)
val examRowsJson = sharedPref?.getString(context.getString(R.string.exam_rows_key), null)
?: return null
val examRowsType = object : TypeToken<List<ExamRow>>() {}.type
return Gson().fromJson(examRowsJson, examRowsType)
}
private fun saveExamRows(context: Context?, examRows: List<ExamRow>) {
val examRowsJson = Gson().toJson(examRows)
val sharedPref = context?.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE)
if (sharedPref != null) {
with(sharedPref.edit()) {
putString(context.getString(R.string.exam_rows_key), examRowsJson)
apply()
}
}
}
@Throws(LoginFailedException::class)
fun fetchExamRows(context: Context?, callback: (examRows: List<ExamRow>?) -> Unit): Unit? {
val sharedPref = context?.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE)
val username = sharedPref?.getString(context.getString(R.string.username_key), null)
?: return null
val password = sharedPref.getString(context.getString(R.string.password_key), null)
?: 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))
.method(Connection.Method.POST)
.userAgent("Mozilla")
.data(postData)
.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 selectStudiengangUnhide = Jsoup.connect(notenspiegelURL)
.userAgent("Mozilla")
.cookies(loginPage.cookies())
.get()
val selectStudiengangUnhideURL = selectStudiengangUnhide.select("a[href]:containsOwn(Abschluss)").first().attr("href")
val selectStudiengang = Jsoup.connect(selectStudiengangUnhideURL)
.userAgent("Mozilla")
.cookies(loginPage.cookies())
.get()
val studiengangURL = selectStudiengang.select("a[href]:containsOwn(Leistungen anzeigen)").first().attr("href")
val notenSpiegelPage = Jsoup.connect(studiengangURL)
.userAgent("Mozilla")
.cookies(loginPage.cookies())
.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 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)
}
saveExamRows(context, examRows)
callback(examRows)
}
return Thread(runnable).start()
}
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}"))
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true)
with(NotificationManagerCompat.from(context)) {
notify(id, builder.build())
}
}
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 importance = NotificationManager.IMPORTANCE_DEFAULT
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
notificationManager.createNotificationChannel(channel)
}
}
}
class LoginFailedException : Throwable()

7
app/src/main/java/com/nareshkumarrao/eiweblog/MainActivity.kt

@ -35,6 +35,7 @@ class MainActivity : AppCompatActivity() {
setSupportActionBar(myToolbar) setSupportActionBar(myToolbar)
Utilities.createNotificationChannel(this) Utilities.createNotificationChannel(this)
HISUtility.createNotificationChannel(this)
val uploadWorkRequest: WorkRequest = val uploadWorkRequest: WorkRequest =
PeriodicWorkRequestBuilder<UpdateWorker>(1, TimeUnit.HOURS) PeriodicWorkRequestBuilder<UpdateWorker>(1, TimeUnit.HOURS)
@ -42,8 +43,6 @@ class MainActivity : AppCompatActivity() {
WorkManager.getInstance(this).enqueue(uploadWorkRequest) WorkManager.getInstance(this).enqueue(uploadWorkRequest)
Utilities.fetchRepoReleaseInformation(this, ::repoReleaseCallback) Utilities.fetchRepoReleaseInformation(this, ::repoReleaseCallback)
} }
private fun repoReleaseCallback(version: String, log: String, url: String?){ private fun repoReleaseCallback(version: String, log: String, url: String?){
@ -97,4 +96,8 @@ class MainActivity : AppCompatActivity() {
val intent = Intent(this, AboutActivity::class.java) val intent = Intent(this, AboutActivity::class.java)
startActivity(intent) startActivity(intent)
} }
fun showGrades(item: MenuItem){
startActivity(Intent(this, GradesActivity::class.java))
}
} }

21
app/src/main/java/com/nareshkumarrao/eiweblog/NotificationSettingsActivity.kt

@ -18,16 +18,25 @@ class NotificationSettingsActivity : AppCompatActivity() {
supportActionBar?.setDisplayShowTitleEnabled(false) supportActionBar?.setDisplayShowTitleEnabled(false)
val sharedPref = getSharedPreferences(getString(R.string.preference_file_key), Context.MODE_PRIVATE) val sharedPref = getSharedPreferences(getString(R.string.preference_file_key), Context.MODE_PRIVATE)
val weblogResponse = sharedPref?.getBoolean(getString(R.string.enable_notifications_key), true)
val notificationSwitch = findViewById<SwitchCompat>(R.id.notification_switch)
notificationSwitch.isChecked = weblogResponse!!
notificationSwitch.setOnCheckedChangeListener { _, isChecked ->
val weblogResponse = sharedPref?.getBoolean(getString(R.string.enable_weblog_notifications_key), true)
val weblogNotificationSwitch = findViewById<SwitchCompat>(R.id.weblog_notification_switch)
weblogNotificationSwitch.isChecked = weblogResponse!!
weblogNotificationSwitch.setOnCheckedChangeListener { _, isChecked ->
with(sharedPref.edit()) { with(sharedPref.edit()) {
putBoolean(getString(R.string.enable_notifications_key), isChecked)
putBoolean(getString(R.string.enable_weblog_notifications_key), isChecked)
apply()
}
}
val gradeResponse = sharedPref.getBoolean(getString(R.string.enable_grades_notifications_key), true)
val gradeNotificationSwitch = findViewById<SwitchCompat>(R.id.grades_notification_switch)
gradeNotificationSwitch.isChecked = gradeResponse
gradeNotificationSwitch.setOnCheckedChangeListener { _, isChecked ->
with(sharedPref.edit()) {
putBoolean(getString(R.string.enable_grades_notifications_key), isChecked)
apply() apply()
} }
//Toast.makeText(this, "Notifications are set to $isChecked", Toast.LENGTH_SHORT).show()
} }

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

@ -10,23 +10,34 @@ class UpdateWorker(private val context: Context, workerParams: WorkerParameters)
override fun doWork(): Result { override fun doWork(): Result {
val sharedPref = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE) val sharedPref = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE)
val notificationsEnabled = sharedPref?.getBoolean(context.getString(R.string.enable_notifications_key), true)
if(!notificationsEnabled!!){
return Result.success()
}
Utilities.weblogList(context) { articles ->
val lastArticle = Utilities.getLatestRelevantArticle(articles)!!
val hashString = lastArticle.title + lastArticle.content + lastArticle.date
val oldHash = md5(hashString)
val weblogNotificationsEnabled = sharedPref?.getBoolean(context.getString(R.string.enable_weblog_notifications_key), true)
val gradesNotificationsEnabled = sharedPref?.getBoolean(context.getString(R.string.enable_grades_notifications_key), true)
if(weblogNotificationsEnabled!!){
Utilities.weblogList(context) { articles ->
val lastArticle = Utilities.getLatestRelevantArticle(articles)!!
val hashString = lastArticle.title + lastArticle.content + lastArticle.date
val oldHash = md5(hashString)
Utilities.fetchWeblogXML(applicationContext){newArticles ->
val lastNewArticle = Utilities.getLatestRelevantArticle(newArticles)!!
val newHashString = lastNewArticle.title + lastNewArticle.content + lastNewArticle.date
val newHash = md5(newHashString)
Utilities.fetchWeblogXML(applicationContext){newArticles ->
val lastNewArticle = Utilities.getLatestRelevantArticle(newArticles)!!
val newHashString = lastNewArticle.title + lastNewArticle.content + lastNewArticle.date
val newHash = md5(newHashString)
if(oldHash != newHash){
Utilities.sendNotification(context, lastNewArticle, newArticles.size)
}
}
}
}
if(oldHash != newHash){
Utilities.sendNotification(context, lastNewArticle, newArticles.size)
if(gradesNotificationsEnabled!!){
HISUtility.checkForUpdates(context) { gradeUpdates ->
if (gradeUpdates != null) {
for (grade in gradeUpdates) {
HISUtility.sendNotification(context, grade, gradeUpdates.indexOf(grade))
}
} }
} }
} }

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

@ -55,8 +55,6 @@ internal object Utilities {
} }
} }
val parser: XmlPullParser = Xml.newPullParser() val parser: XmlPullParser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false) parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
//Log.d("XMLLIST", responseStr ) //Log.d("XMLLIST", responseStr )
@ -140,7 +138,7 @@ internal object Utilities {
} }
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0) val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val builder = NotificationCompat.Builder(context!!, context.getString(R.string.channel_id))
val builder = NotificationCompat.Builder(context!!, context.getString(R.string.weblog_notification_channel_id))
.setSmallIcon(R.drawable.ic_stat_name) .setSmallIcon(R.drawable.ic_stat_name)
.setContentTitle(article.title) .setContentTitle(article.title)
.setStyle(NotificationCompat.BigTextStyle() .setStyle(NotificationCompat.BigTextStyle()
@ -158,10 +156,10 @@ internal object Utilities {
fun createNotificationChannel(context: Context?){ fun createNotificationChannel(context: Context?){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = context?.getString(R.string.channel_name)
val descriptionText = context?.getString(R.string.channel_description)
val name = context?.getString(R.string.weblog_notification_channel_name)
val descriptionText = context?.getString(R.string.weblog_notification_channel_description)
val importance = NotificationManager.IMPORTANCE_DEFAULT val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(context?.getString(R.string.channel_id), name, importance).apply {
val channel = NotificationChannel(context?.getString(R.string.weblog_notification_channel_id), name, importance).apply {
description = descriptionText description = descriptionText
} }
val notificationManager: NotificationManager = context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val notificationManager: NotificationManager = context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

46
app/src/main/java/com/nareshkumarrao/eiweblog/ui/main/ItemGradesAdapter.kt

@ -0,0 +1,46 @@
package com.nareshkumarrao.eiweblog.ui.main
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.nareshkumarrao.eiweblog.ExamRow
import com.nareshkumarrao.eiweblog.R
class ItemGradesAdapter(private val examRows: List<ExamRow>) : RecyclerView.Adapter<ItemGradesAdapter.ViewHolder>() {
inner class ViewHolder(inflater: LayoutInflater, parent: ViewGroup) : RecyclerView.ViewHolder(inflater.inflate(R.layout.item_grades, parent, false)) {
private var name: TextView? = null
private var attempt: TextView? = null
private var date: TextView? = null
private var grade: TextView? = null
init {
name = itemView.findViewById(R.id.examNameText)
attempt = itemView.findViewById(R.id.versuchText)
date = itemView.findViewById(R.id.examDateText)
grade = itemView.findViewById(R.id.gradeText)
}
fun bind(examRow: ExamRow) {
name?.text = examRow.name
attempt?.text = examRow.attempt
grade?.text = examRow.grade
date?.text = examRow.date
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemGradesAdapter.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return ViewHolder(inflater, parent)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val examRow = examRows[position]
holder.bind(examRow)
}
override fun getItemCount(): Int {
return examRows.size
}
}

2
app/src/main/res/layout/activity_about.xml

@ -15,8 +15,6 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

54
app/src/main/res/layout/activity_grades.xml

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="match_parent"
tools:context=".GradesActivity"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:theme="@style/Theme.EIWeblog.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/grades_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentInsetStart="0px">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/hsdsans_elektro"
android:minHeight="?actionBarSize"
android:padding="@dimen/appbar_padding"
android:paddingStart="0px"
android:paddingLeft="0px"
android:text="@string/grades_title"
android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
android:textColor="@color/black"
android:textSize="25sp"
tools:ignore="RtlSymmetry" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<ProgressBar
android:id="@+id/gradesProgressBar"
style="?android:attr/progressBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/grades_swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/grades_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>

31
app/src/main/res/layout/activity_notification_settings.xml

@ -15,8 +15,6 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -34,7 +32,7 @@
</androidx.appcompat.widget.Toolbar> </androidx.appcompat.widget.Toolbar>
<androidx.appcompat.widget.SwitchCompat <androidx.appcompat.widget.SwitchCompat
android:id="@+id/notification_switch"
android:id="@+id/weblog_notification_switch"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
@ -53,11 +51,34 @@
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
android:layout_marginRight="32dp" android:layout_marginRight="32dp"
android:text="@string/enable_notifications"
android:text="@string/enable_weblog_notifications_text"
android:textColor="@color/black" android:textColor="@color/black"
android:textSize="18sp" android:textSize="18sp"
app:layout_constraintEnd_toStartOf="@+id/notification_switch"
app:layout_constraintEnd_toStartOf="@+id/weblog_notification_switch"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/notification_toolbar" /> app:layout_constraintTop_toBottomOf="@+id/notification_toolbar" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/enable_grades_notifications_text"
android:textColor="@color/black"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="@+id/grades_notification_switch"
app:layout_constraintEnd_toEndOf="@+id/textView4"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/textView4"
app:layout_constraintTop_toTopOf="@+id/grades_notification_switch" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/grades_notification_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:checked="false"
app:layout_constraintEnd_toEndOf="@+id/weblog_notification_switch"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/weblog_notification_switch"
app:layout_constraintTop_toBottomOf="@+id/weblog_notification_switch" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

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

@ -0,0 +1,64 @@
<?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">
<TextView
android:id="@+id/loginTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:fontFamily="@font/hsdsans_regular"
android:text="@string/login_dialog_title"
android:textColor="@color/black"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/loginUsername"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/username"
android:inputType="text"
android:autofillHints="username"
app:layout_constraintEnd_toEndOf="@+id/errorText"
app:layout_constraintStart_toStartOf="@+id/loginTitle"
app:layout_constraintTop_toBottomOf="@+id/loginTitle" />
<EditText
android:id="@+id/loginPassword"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:hint="@string/password"
android:autofillHints="current-password"
android:inputType="textPassword"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/loginUsername"
app:layout_constraintStart_toStartOf="@+id/loginUsername"
app:layout_constraintTop_toBottomOf="@+id/loginUsername" />
<TextView
android:id="@+id/errorText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:fontFamily="@font/hsdsans_regular"
android:text="@string/error"
android:textColor="@color/fh_red"
android:textSize="18sp"
android:textStyle="bold"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/loginTitle" />
</androidx.constraintlayout.widget.ConstraintLayout>

79
app/src/main/res/layout/item_grades.xml

@ -0,0 +1,79 @@
<?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">
<TextView
android:id="@+id/examNameText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:text="G 19 Höhere Mathematik "
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@+id/gradeText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/versuch"
android:textColor="@color/black"
app:layout_constraintStart_toStartOf="@+id/examNameText"
app:layout_constraintTop_toBottomOf="@+id/examNameText" />
<TextView
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:text="@string/pruefungsdatum"
android:textColor="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/textView5"
app:layout_constraintTop_toBottomOf="@+id/textView5" />
<TextView
android:id="@+id/gradeText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:layout_marginRight="32dp"
android:text="4.0"
android:textColor="@color/black"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="@+id/textView6"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/versuchText"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/versuchText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:layout_marginRight="32dp"
android:text="1"
app:layout_constraintEnd_toStartOf="@+id/gradeText"
app:layout_constraintTop_toTopOf="@+id/textView5"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/examDateText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="05.02.2020"
app:layout_constraintEnd_toEndOf="@+id/versuchText"
app:layout_constraintTop_toTopOf="@+id/textView6"
tools:ignore="HardcodedText" />
</androidx.constraintlayout.widget.ConstraintLayout>

5
app/src/main/res/menu/toolbar_menu.xml

@ -9,4 +9,9 @@
android:onClick="showAbout" android:onClick="showAbout"
android:title="@string/about" android:title="@string/about"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:onClick="showGrades"
android:title="@string/grades_title"
app:showAsAction="never" />
</menu> </menu>

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

@ -4,7 +4,7 @@
<string name="about_title">Info</string> <string name="about_title">Info</string>
<string name="send_beer">Shick mir €5 für ein Bier über PayPal</string> <string name="send_beer">Shick mir €5 für ein Bier über PayPal</string>
<string name="notifications_title">Benachrichtigung</string> <string name="notifications_title">Benachrichtigung</string>
<string name="enable_notifications">Benachrichtige mich über Neuerungen auf der Weblog</string>
<string name="enable_weblog_notifications_text">Benachrichtige mich über Neuerungen auf der Weblog</string>
<string name="about_text"> <string name="about_text">
Ich bin Naresh und mache gerade mein Master in Automatisierungstechnik hier an der HSD.\n Ich bin Naresh und mache gerade mein Master in Automatisierungstechnik hier an der HSD.\n
\nIch habe diese App geschrieben, da die EI-Weblog für mich unzufriedenstellend war. Ich vergesse \nIch habe diese App geschrieben, da die EI-Weblog für mich unzufriedenstellend war. Ich vergesse
@ -20,4 +20,16 @@
<string name="download">Herunterladen</string> <string name="download">Herunterladen</string>
<string name="cancel">Abbrechen</string> <string name="cancel">Abbrechen</string>
<string name="update_dialog">Neues App-Version verfügbar</string> <string name="update_dialog">Neues App-Version verfügbar</string>
<string name="username">Benutzername</string>
<string name="password">Passwort</string>
<string name="grades_title">Notenspiegel</string>
<string name="versuch">Versuch:</string>
<string name="pruefungsdatum">Prüfungsdatum:</string>
<string name="login">Anmelden</string>
<string name="exam_results_notification">Neues Klausurergebnis!</string>
<string name="grades_notification_channel_name">Klausurergebnis</string>
<string name="grades_notification_channel_description">Benachrichtigung bei neuem Klausurergebnis</string>
<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>
</resources> </resources>

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

@ -9,7 +9,7 @@
<string name="about_title">About</string> <string name="about_title">About</string>
<string name="send_beer">Send me €5 for beer on PayPal</string> <string name="send_beer">Send me €5 for beer on PayPal</string>
<string name="notifications_title">Notifications</string> <string name="notifications_title">Notifications</string>
<string name="enable_notifications">Notify me when new updates are posted to the weblog</string>
<string name="enable_weblog_notifications_text">Notify me when new updates are posted to the weblog</string>
<string name="about_text"> <string name="about_text">
My name is Naresh and I am doing my Master\'s in Automation at the HSD.\n My name is Naresh and I am doing my Master\'s in Automation at the HSD.\n
\nI wrote this app because I was unsatisfied with the EI weblog that was always silently being \nI wrote this app because I was unsatisfied with the EI weblog that was always silently being
@ -21,4 +21,16 @@
<string name="download">Download</string> <string name="download">Download</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
<string name="update_dialog">New version of app available</string> <string name="update_dialog">New version of app available</string>
<string name="username">Username</string>
<string name="password">Password</string>
<string name="grades_title">Grades</string>
<string name="versuch">Attempt:</string>
<string name="pruefungsdatum">Exam date:</string>
<string name="login">Login</string>
<string name="exam_results_notification">New exam results out!</string>
<string name="grades_notification_channel_name">Exam Results</string>
<string name="grades_notification_channel_description">Notification when you get a new exam result</string>
<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>
</resources> </resources>

1
app/src/main/res/values/colors.xml

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="fh_red">#E60028</color> <color name="fh_red">#E60028</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color> <color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color> <color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color> <color name="teal_700">#FF018786</color>

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

@ -24,15 +24,34 @@
as contained within the terms of the GPLv3 license. More information about the license and the as contained within the terms of the GPLv3 license. More information about the license and the
source code of this project can be found on its GitHub repository. source code of this project can be found on its GitHub repository.
</string> </string>
<string name="channel_name" translatable="false">EI Weblog Notifications</string>
<string name="channel_description" translatable="false">Latest updates from the EI Weblog</string>
<string name="channel_id" translatable="false">com.nareshkumarrao.eiweblog.update</string>
<string name="weblog_notification_channel_name" translatable="false">EI Weblog Notifications</string>
<string name="weblog_notification_channel_description" translatable="false">Latest updates from the EI Weblog</string>
<string name="weblog_notification_channel_id" translatable="false">com.nareshkumarrao.eiweblog.weblogupdate</string>
<string name="notifications_title">Benachrichtigung</string> <string name="notifications_title">Benachrichtigung</string>
<string name="enable_notifications">Notify me when new updates are posted to the weblog</string>
<string name="enable_notifications_key" translatable="false">com.nareshkumarrao.notifications.key</string>
<string name="enable_weblog_notifications_text">Notify me when new updates are posted to the weblog</string>
<string name="enable_weblog_notifications_key" translatable="false">com.nareshkumarrao.weblog_notifications.key</string>
<string name="github_repository" translatable="false">GitHub Repository</string> <string name="github_repository" translatable="false">GitHub Repository</string>
<string name="github_api_releases" translatable="false">https://api.github.com/repos/naresh97/ei-weblog-android/releases</string> <string name="github_api_releases" translatable="false">https://api.github.com/repos/naresh97/ei-weblog-android/releases</string>
<string name="update_dialog">New version of app available</string> <string name="update_dialog">New version of app available</string>
<string name="download">Download</string> <string name="download">Download</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
<string name="ossc_login_post" translatable="false"><![CDATA[https://ossc.hs-duesseldorf.de/qisserver/rds?state=user&type=1&category=auth.login&startpage=portal.vm&breadCrumbSource=portal]]></string>
<string name="ossc_select_noten" translatable="false"><![CDATA[https://ossc.hs-duesseldorf.de/qisserver/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]]></string>
<string name="exam_rows_key" translatable="false">exam_rows</string>
<string name="username_key" translatable="false">username_key</string>
<string name="password_key" translatable="false">password_key</string>
<string name="username">Username</string>
<string name="password">Password</string>
<string name="grades_title">Notenspiegel</string>
<string name="versuch">Versuch:</string>
<string name="pruefungsdatum">Prüfungsdatum:</string>
<string name="login">Anmelden</string>
<string name="exam_results_notification">New exam results out!</string>
<string name="grades_notification_channel_name">Exam Results</string>
<string name="grades_notification_channel_description">Notification when you get a new exam result</string>
<string name="grades_notification_channel_id" translatable="false">com.nareshkumarrao.eiweblog.grades</string>
<string name="enable_grades_notifications_text">Notify me when I get new grades</string>
<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>
</resources> </resources>

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

@ -2,7 +2,7 @@
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="Theme.EIWeblog" parent="Theme.MaterialComponents.Light.DarkActionBar"> <style name="Theme.EIWeblog" parent="Theme.MaterialComponents.Light.DarkActionBar">
<!-- Primary brand color. --> <!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimary">@color/fh_red</item>
<item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item> <item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. --> <!-- Secondary brand color. -->

Loading…
Cancel
Save