Browse Source

Merge pull request #2 from naresh97/feature/exam_notifications

implemented exam grades and notifications
feature/exam_notifications v0.10.1
Nareshkumar Rao 4 years ago
committed by GitHub
parent
commit
acf8bfb292
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  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. 19
      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"
minSdkVersion 16
targetSdkVersion 30
versionCode 4
versionName "0.9.3"
versionCode 5
versionName "0.10.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -42,12 +42,13 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.android.volley:volley:1.2.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",
"filters": [],
"versionCode": 4,
"versionName": "0.9.3",
"versionCode": 5,
"versionName": "0.10.1",
"outputFile": "app-release.apk"
}
]

12
app/src/main/AndroidManifest.xml

@ -11,14 +11,18 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.EIWeblog">
<activity
android:name=".GradesActivity"
android:parentActivityName=".MainActivity"
android:theme="@style/Theme.EIWeblog.NoActionBar" />
<activity
android:name=".AboutActivity"
android:theme="@style/Theme.EIWeblog.NoActionBar"
android:parentActivityName=".MainActivity" />
android:parentActivityName=".MainActivity"
android:theme="@style/Theme.EIWeblog.NoActionBar" />
<activity
android:name=".NotificationSettingsActivity"
android:theme="@style/Theme.EIWeblog.NoActionBar"
android:parentActivityName=".MainActivity" />
android:parentActivityName=".MainActivity"
android:theme="@style/Theme.EIWeblog.NoActionBar" />
<activity
android:name=".MainActivity"
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)
Utilities.createNotificationChannel(this)
HISUtility.createNotificationChannel(this)
val uploadWorkRequest: WorkRequest =
PeriodicWorkRequestBuilder<UpdateWorker>(1, TimeUnit.HOURS)
@ -42,8 +43,6 @@ class MainActivity : AppCompatActivity() {
WorkManager.getInstance(this).enqueue(uploadWorkRequest)
Utilities.fetchRepoReleaseInformation(this, ::repoReleaseCallback)
}
private fun repoReleaseCallback(version: String, log: String, url: String?){
@ -97,4 +96,8 @@ class MainActivity : AppCompatActivity() {
val intent = Intent(this, AboutActivity::class.java)
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)
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()) {
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()
}
//Toast.makeText(this, "Notifications are set to $isChecked", Toast.LENGTH_SHORT).show()
}

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

@ -10,11 +10,11 @@ class UpdateWorker(private val context: Context, workerParams: WorkerParameters)
override fun doWork(): Result {
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()
}
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
@ -30,6 +30,17 @@ class UpdateWorker(private val context: Context, workerParams: WorkerParameters)
}
}
}
}
if(gradesNotificationsEnabled!!){
HISUtility.checkForUpdates(context) { gradeUpdates ->
if (gradeUpdates != null) {
for (grade in gradeUpdates) {
HISUtility.sendNotification(context, grade, gradeUpdates.indexOf(grade))
}
}
}
}
return Result.success()
}

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

@ -55,8 +55,6 @@ internal object Utilities {
}
}
val parser: XmlPullParser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
//Log.d("XMLLIST", responseStr )
@ -140,7 +138,7 @@ internal object Utilities {
}
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)
.setContentTitle(article.title)
.setStyle(NotificationCompat.BigTextStyle()
@ -158,10 +156,10 @@ internal object Utilities {
fun createNotificationChannel(context: Context?){
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 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
}
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_constraintTop_toTopOf="parent">
<TextView
android:layout_width="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_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -34,7 +32,7 @@
</androidx.appcompat.widget.Toolbar>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/notification_switch"
android:id="@+id/weblog_notification_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
@ -53,11 +51,34 @@
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp"
android:layout_marginRight="32dp"
android:text="@string/enable_notifications"
android:text="@string/enable_weblog_notifications_text"
android:textColor="@color/black"
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_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>

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:title="@string/about"
app:showAsAction="never" />
<item
android:onClick="showGrades"
android:title="@string/grades_title"
app:showAsAction="never" />
</menu>

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

@ -4,7 +4,7 @@
<string name="about_title">Info</string>
<string name="send_beer">Shick mir €5 für ein Bier über PayPal</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">
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
@ -20,4 +20,16 @@
<string name="download">Herunterladen</string>
<string name="cancel">Abbrechen</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>

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

@ -9,7 +9,7 @@
<string name="about_title">About</string>
<string name="send_beer">Send me €5 for beer on PayPal</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">
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
@ -21,4 +21,16 @@
<string name="download">Download</string>
<string name="cancel">Cancel</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>

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

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="fh_red">#E60028</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</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
source code of this project can be found on its GitHub repository.
</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="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_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="download">Download</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>

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

@ -2,7 +2,7 @@
<!-- Base application theme. -->
<style name="Theme.EIWeblog" parent="Theme.MaterialComponents.Light.DarkActionBar">
<!-- 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="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->

Loading…
Cancel
Save