diff --git a/.drone.yml b/.drone.yml
index 4ea35a1..72f9f4e 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -3,7 +3,7 @@ name: default
steps:
- name: assembleRelease
- image: nextcloudci/android:android-51
+ image: nextcloudci/android10:android-56
commands:
- - gradle assembleRelease
+ - ./gradlew assembleRelease
diff --git a/README.md b/README.md
index 42ede7b..a16a7c2 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,21 @@
+[![Build Status](https://drone.mosad.xyz/api/badges/Seil0/ProjectLaogai/status.svg)](https://drone.mosad.xyz/Seil0/ProjectLaogai)
![fdroid version](https://img.shields.io/f-droid/v/org.mosad.seil0.projectlaogai.svg?style=flat-square)
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg?style=flat-square)](https://www.gnu.org/licenses/gpl-3.0)
-# ProjectLaogai "hso App"
-ProjectLaogai is a app to access the timetable and the mensa menu of Hochschule Offenburg.
+# Project Laogai
+Project Laogai is an app to access the timetable, grades (qispos) and the canteen menu of Hochschule Offenburg. Laogai uses the TCoR-API fot timetables and the canteen menu, wich makes acessing them super fast. To get the grades from Qispos, Laogai will ask for your login data, wich are stored encrypted on your device.
[](https://f-droid.org/packages/org.mosad.seil0.projectlaogai/)
[](https://play.google.com/store/apps/details?id=org.mosad.seil0.projectlaogai)
+
## Features
-* check out the mensa menu of this and next week
-* access your timetable
+* have your grades displayed directly in the app
+* show the timetable of your course
+* take a look at the canteen menu for the current and next week
* check the current balance of your mensa card
-* open moodle
-* probably some funny bugs
+* open moodle directly in the app
+
+Please report bugs and issues to support@mosad.xyz
## Screenshots
[](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.png)
@@ -20,4 +24,4 @@ ProjectLaogai is a app to access the timetable and the mensa menu of Hochschule
[](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.png)
[](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa_dark.png)
-ProjectLaogai © 2019-2020 [@Seil0](https://git.mosad.xyz/Seil0), a [mosad](http://www.mosad.xyz) Project
+ProjectLaogai © 2019-2020 [@Seil0](https://git.mosad.xyz/Seil0), a [mosad.xyz](http://www.mosad.xyz) Project
diff --git a/app/build.gradle b/app/build.gradle
index a634a62..679992e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -10,8 +10,8 @@ android {
applicationId "org.mosad.seil0.projectlaogai"
minSdkVersion 23
targetSdkVersion 29
- versionCode 15
- versionName "0.5.1"
+ versionCode 6000 // 0006000
+ versionName "0.6.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "build_time", buildTime()
setProperty("archivesBaseName", "projectlaogai-$versionName")
@@ -19,8 +19,8 @@ android {
buildTypes {
release {
- minifyEnabled false
- shrinkResources false
+ minifyEnabled true
+ shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
@@ -47,23 +47,27 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
- implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'
+ implementation 'androidx.core:core:1.3.1'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
- implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
- implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
- implementation 'com.google.android.material:material:1.1.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
+ implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
+ implementation 'androidx.security:security-crypto:1.1.0-alpha02'
+ implementation 'com.google.android.material:material:1.2.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.afollestad:aesthetic:1.0.0-beta05'
- implementation 'com.afollestad.material-dialogs:core:3.1.1'
- implementation 'com.afollestad.material-dialogs:color:3.1.1'
+ implementation 'com.afollestad.material-dialogs:core:3.3.0'
+ implementation 'com.afollestad.material-dialogs:color:3.3.0'
+ implementation 'com.afollestad.material-dialogs:bottomsheets:3.3.0'
implementation 'de.psdev.licensesdialog:licensesdialog:2.1.0'
- implementation 'org.apache.commons:commons-lang3:3.9'
+ implementation 'org.apache.commons:commons-lang3:3.11'
+ implementation 'org.jsoup:jsoup:1.13.1'
- testImplementation 'junit:junit:4.12'
- androidTestImplementation 'androidx.test:runner:1.2.0'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+ testImplementation 'junit:junit:4.13'
+ androidTestImplementation 'androidx.test:core:1.2.0'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
}
static def buildTime() {
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index f1b4245..368d355 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -15,7 +15,16 @@
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
+-dontobfuscate
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
+-keep class org.mosad.seil0.projectlaogai.util.** { ; }
+
+#Gson
+-keepattributes Signature
+-dontwarn sun.misc.**
+
+#misc
+-dontwarn java.lang.instrument.ClassFileTransformer
diff --git a/app/src/androidTest/java/org/mosad/seil0/projectlaogai/ExampleInstrumentedTest.kt b/app/src/androidTest/java/org/mosad/seil0/projectlaogai/ExampleInstrumentedTest.kt
index b34bfff..cf1a21e 100644
--- a/app/src/androidTest/java/org/mosad/seil0/projectlaogai/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/java/org/mosad/seil0/projectlaogai/ExampleInstrumentedTest.kt
@@ -1,7 +1,7 @@
package org.mosad.seil0.projectlaogai
-import androidx.test.InstrumentationRegistry
-import androidx.test.runner.AndroidJUnit4
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Test
import org.junit.runner.RunWith
@@ -18,7 +18,7 @@ class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
- val appContext = InstrumentationRegistry.getTargetContext()
+ val appContext = InstrumentationRegistry.getInstrumentation().context
assertEquals("org.mosad.seil0.projectlaogai", appContext.packageName)
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 93c91fa..737b74b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -28,6 +28,14 @@
android:resource="@xml/shortcuts" />
+
+
+
NFCMensaCard.readBalance(intent, this)
"org.mosad.seil0.projectlaogai.fragments.MensaFragment" -> activeFragment = MensaFragment()
@@ -154,6 +156,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
R.id.nav_mensa -> MensaFragment()
R.id.nav_timetable -> TimeTableFragment()
R.id.nav_moodle -> MoodleFragment()
+ R.id.nav_grades -> GradesFragment()
R.id.nav_settings -> SettingsFragment()
else -> HomeFragment()
}
@@ -172,8 +175,9 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
*/
private fun load() {
val startupTime = measureTimeMillis {
- PreferencesController.load(this) // load the settings, must be finished before doing anything else
+ Preferences.load(this) // load the settings, must be finished before doing anything else
CacheController(this) // load the cache
+ EncryptedPreferences.load(this)
}
Log.i(className, "startup completed in $startupTime ms")
}
@@ -181,13 +185,15 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
private fun initAesthetic() {
// If we haven't set any defaults, do that now
if (Aesthetic.isFirstTime) {
- // this is executed on the first app start, use this to show tutorial etc.
+ // set the default theme at the first app start
Aesthetic.config {
activityTheme(R.style.AppTheme_Light)
apply()
}
- SettingsFragment().selectCourse(this) // FIXME this is not working
+ // show the onboarding activity
+ startActivity(Intent(this, OnboardingActivity::class.java))
+ finish()
}
Aesthetic.config {
@@ -198,6 +204,13 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
apply()
}
+ // set theme color values
+ val out = TypedValue()
+ this.theme.resolveAttribute(R.attr.themePrimary, out, true)
+ Preferences.themePrimary = out.data
+
+ this.theme.resolveAttribute(R.attr.themeSecondary, out, true)
+ Preferences.themeSecondary = out.data
}
private fun initForegroundDispatch() {
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/OnboardingActivity.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/OnboardingActivity.kt
new file mode 100644
index 0000000..24d0466
--- /dev/null
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/OnboardingActivity.kt
@@ -0,0 +1,145 @@
+/**
+ * ProjectLaogai
+ *
+ * Copyright 2019-2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ */
+
+package org.mosad.seil0.projectlaogai
+
+import android.content.Intent
+import android.graphics.Color
+import android.os.Bundle
+import android.view.View
+import android.widget.EditText
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.text.HtmlCompat
+import androidx.viewpager.widget.ViewPager
+import com.afollestad.materialdialogs.callbacks.onDismiss
+import kotlinx.android.synthetic.main.activity_onboarding.*
+import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
+import org.mosad.seil0.projectlaogai.fragments.SettingsFragment
+import org.mosad.seil0.projectlaogai.onboarding.ViewPagerAdapter
+
+class OnboardingActivity : AppCompatActivity() {
+
+ companion object {
+ val layouts = intArrayOf(R.layout.fragment_on_welcome, R.layout.fragment_on_course, R.layout.fragment_on_login)
+ }
+ private lateinit var viewPager: ViewPager
+ private lateinit var viewPagerAdapter: ViewPagerAdapter
+ private lateinit var linLayoutDots: LinearLayout
+ private lateinit var dots: Array
+
+ private lateinit var editTextEmail: EditText
+ private lateinit var editTextPassword: EditText
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_onboarding)
+
+ viewPager = findViewById(R.id.viewPager)
+ linLayoutDots = findViewById(R.id.linLayout_Dots)
+
+ addBottomDots(0)
+
+ viewPagerAdapter = ViewPagerAdapter(this)
+ viewPager.adapter = viewPagerAdapter
+ viewPager.addOnPageChangeListener(viewPagerPageChangeListener)
+
+ // we don't use the skip button, instead we use the start button to skip the last fragment
+ btn_Skip.visibility = View.GONE
+ }
+
+ fun btnNextClick(@Suppress("UNUSED_PARAMETER")v: View) {
+ if (viewPager.currentItem < layouts.size - 1) {
+ viewPager.currentItem++
+ } else {
+ launchHomeScreen()
+ }
+ }
+
+ fun btnSkipClick(@Suppress("UNUSED_PARAMETER")v: View) {
+ launchHomeScreen()
+ }
+
+ fun btnSelectCourseClick(@Suppress("UNUSED_PARAMETER")v: View) {
+ SettingsFragment().selectCourse(this).show {
+ onDismiss {
+ btnNextClick(v) // show the next fragment
+ }
+ }
+ }
+
+ fun btnLoginClick(@Suppress("UNUSED_PARAMETER")v: View) {
+ editTextEmail = findViewById(R.id.editText_email)
+ editTextPassword = findViewById(R.id.editText_password)
+
+ // get login credentials from gui
+ val email = editTextEmail.text.toString()
+ val password = editTextPassword.text.toString()
+
+ // save the credentials
+ EncryptedPreferences.saveCredentials(email, password, this)
+
+ launchHomeScreen()
+ }
+
+ private fun addBottomDots(currentPage: Int) {
+ dots = Array(layouts.size) { TextView(this) }
+ linLayoutDots.removeAllViews()
+
+ dots.forEach {
+ it.text = HtmlCompat.fromHtml("•", HtmlCompat.FROM_HTML_MODE_LEGACY)
+ it.textSize = 35f
+ it.setTextColor(Color.parseColor("#cccccc"))
+ linLayoutDots.addView(it)
+ }
+
+ if (dots.isNotEmpty())
+ dots[currentPage].setTextColor(Color.parseColor("#000000"))
+ }
+
+ private fun launchHomeScreen() {
+ startActivity(Intent(this, MainActivity::class.java))
+ finish()
+ }
+
+ private var viewPagerPageChangeListener: ViewPager.OnPageChangeListener = object : ViewPager.OnPageChangeListener {
+
+ override fun onPageSelected(position: Int) {
+ addBottomDots(position)
+ // changing the next button text to skip for the login fragment
+ if (position == layouts.size - 1) {
+ btn_Next.text = getString(R.string.skip)
+ btn_Next.visibility = View.VISIBLE
+ btn_Skip.visibility = View.GONE
+ } else {
+ btn_Next.visibility = View.GONE
+ btn_Skip.visibility = View.GONE
+ }
+ }
+
+ override fun onPageScrolled(arg0: Int, arg1: Float, arg2: Int) {}
+ override fun onPageScrollStateChanged(arg0: Int) {}
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/CacheController.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/CacheController.kt
deleted file mode 100644
index 2ad54b1..0000000
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/CacheController.kt
+++ /dev/null
@@ -1,177 +0,0 @@
-/**
- * ProjectLaogai
- *
- * Copyright 2019-2020
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301, USA.
- *
- */
-
-package org.mosad.seil0.projectlaogai.controller
-
-import android.content.Context
-import android.util.Log
-import com.google.gson.Gson
-import com.google.gson.GsonBuilder
-import com.google.gson.JsonParser
-import com.google.gson.reflect.TypeToken
-import kotlinx.coroutines.*
-import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
-import org.mosad.seil0.projectlaogai.hsoparser.Course
-import org.mosad.seil0.projectlaogai.hsoparser.MensaMenu
-import org.mosad.seil0.projectlaogai.hsoparser.TimetableCourseWeek
-import java.io.BufferedReader
-import java.io.File
-import java.io.FileReader
-import java.util.*
-import kotlin.collections.ArrayList
-
-class CacheController(cont: Context) {
-
- private val className = "CacheController"
- private val context = cont
-
- init {
- val cal = Calendar.getInstance()
- val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
- val currentTime = System.currentTimeMillis() / 1000
-
- // check if we need to update the mensa data before displaying it
- readMensa(context)
- cal.time = Date(mensaMenu.meta.updateTime * 1000)
-
- // if a) it's monday and the last cache update was on sunday or b) the cache is older than 24hr, update blocking
- if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - mensaMenu.meta.updateTime) > 86400) {
- Log.i(className, "update mensa blocking")
- GlobalScope.launch(Dispatchers.Default) { TCoRAPIController.getMensa(context).join() }
- }
-
- // check if we need to update the timetables before displaying them
- readTimetable(cCourse.courseName, 0, context)
- cal.time = Date(timetables[0].meta.updateTime * 1000)
-
- // if a) it`s monday and the last cache update was not on a sunday or b) the cache is older than 24hr, update blocking
- if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - timetables[0].meta.updateTime) > 86400) {
- Log.i(className, "updating timetable after sunday!")
-
- GlobalScope.launch(Dispatchers.Default) {
- val threads = listOf(
- TCoRAPIController.getTimetable(cCourse.courseName, 0, context),
- TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
- )
- threads.joinAll()
- }
-
- TCoRAPIController.getTimetable(cCourse.courseName, 0, context)
- TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
- }
-
- // check if an update is necessary, not blocking
- if (currentTime - PreferencesController.coursesCacheTime > 86400)
- TCoRAPIController.getCoursesList(context)
-
- if (currentTime - PreferencesController.mensaCacheTime > 10800)
- TCoRAPIController.getMensa(context)
-
- if (currentTime - PreferencesController.timetableCacheTime > 10800) {
- TCoRAPIController.getTimetable(cCourse.courseName, 0, context)
- TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
- }
-
- readStartCache(cCourse.courseName)
- }
-
- companion object {
- var coursesList = ArrayList()
- var timetables = ArrayList()
- var mensaMenu = MensaMenu()
-
- /**
- * read the courses list from the cached file
- * add them to the coursesList object
- */
- private fun readCoursesList(context: Context) {
- val file = File(context.filesDir, "courses.json")
-
- // make sure the file exists
- if (!file.exists())
- runBlocking { TCoRAPIController.getCoursesList(context).join() }
-
- val fileReader = FileReader(file)
- val bufferedReader = BufferedReader(fileReader)
- val coursesObject = JsonParser.parseString(bufferedReader.readLine()).asJsonObject
-
- coursesList = Gson().fromJson(
- coursesObject.getAsJsonArray("courses"),
- object : TypeToken>() {}.type
- )
- }
-
- /**
- * get the MensaMenu object from the cached json,
- * if cache is empty create the cache file
- */
- fun readMensa(context: Context) {
- val file = File(context.filesDir, "mensa.json")
-
- // make sure the file exists
- if (!file.exists())
- runBlocking { TCoRAPIController.getMensa(context).join() }
-
- val fileReader = FileReader(file)
- val bufferedReader = BufferedReader(fileReader)
- val mensaObject = JsonParser.parseString(bufferedReader.readLine()).asJsonObject
-
- mensaMenu = GsonBuilder().create().fromJson(mensaObject, MensaMenu().javaClass)
- }
-
- /**
- * read the weeks timetable from the cached file
- * @param courseName the course name (e.g AI1)
- * @param week the week to read (0 for the current and so on)
- */
- fun readTimetable(courseName: String, week: Int, context: Context) {
- val file = File(context.filesDir, "timetable-$courseName-$week.json")
-
- // make sure the file exists
- if (!file.exists())
- runBlocking { TCoRAPIController.getTimetable(courseName, week, context).join() }
-
- val fileReader = FileReader(file)
- val bufferedReader = BufferedReader(fileReader)
- val timetableObject = JsonParser.parseString(bufferedReader.readLine()).asJsonObject
-
- // make sure you add the single weeks in the exact order!
- if (timetables.size > week) {
- timetables[week] = (Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass))
- } else {
- timetables.add(Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass))
- }
- }
- }
-
- /**
- * read coursesList, mensa (current and next week), timetable (current and next week)
- * @param courseName the course name (e.g AI1)
- */
- private fun readStartCache(courseName: String) {
- readCoursesList(context)
- readMensa(context)
- readTimetable(courseName, 0, context)
- readTimetable(courseName, 1, context)
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/NFCMensaCard.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/NFCMensaCard.kt
index c19db0a..af4b310 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/NFCMensaCard.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/NFCMensaCard.kt
@@ -35,6 +35,7 @@ import com.codebutler.farebot.card.desfire.DesfireFileSettings
import com.codebutler.farebot.card.desfire.DesfireProtocol
import kotlinx.android.synthetic.main.dialog_mensa_credit.*
import org.mosad.seil0.projectlaogai.R
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import java.lang.Exception
class NFCMensaCard {
@@ -84,13 +85,13 @@ class NFCMensaCard {
val dialog = MaterialDialog(context)
.customView(R.layout.dialog_mensa_credit)
- val current = if (!PreferencesController.oGiants) {
+ val current = if (!Preferences.oGiants) {
String.format("%.2f €", (currentRaw.toFloat() / 1000))
} else {
String.format("%.4f shm", (currentRaw.toFloat() * 0.0000075))
}
- val last = if (!PreferencesController.oGiants) {
+ val last = if (!Preferences.oGiants) {
String.format("%.2f €", (lastRaw.toFloat() / 1000))
} else {
String.format("%.4f shm", (lastRaw.toFloat() * 0.0000075))
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/PreferencesController.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/PreferencesController.kt
deleted file mode 100644
index 24c48ab..0000000
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/PreferencesController.kt
+++ /dev/null
@@ -1,183 +0,0 @@
-/**
- * ProjectLaogai
- *
- * Copyright 2019-2020
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301, USA.
- *
- */
-
-package org.mosad.seil0.projectlaogai.controller
-
-import android.content.Context
-import android.graphics.Color
-import org.mosad.seil0.projectlaogai.R
-import org.mosad.seil0.projectlaogai.hsoparser.Course
-
-/**
- * The PreferencesController class
- * contains all preferences and global variables that exist in this app
- */
-class PreferencesController {
-
- companion object {
- var coursesCacheTime: Long = 0
- var mensaCacheTime: Long = 0
- var timetableCacheTime: Long = 0
- var cColorPrimary: Int = Color.BLACK
- var cColorAccent: Int = Color.parseColor("#3F51B5")
- var cCourse = Course("https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0", "AI3")
- var cShowBuffet = true
- var oGiants = false
-
- // the save function
- fun save(context: Context) {
- val sharedPref = context.getSharedPreferences(
- context.getString(R.string.preference_file_key),
- Context.MODE_PRIVATE
- )
-
- // save the update times (cache)
- with (sharedPref.edit()) {
- putLong(context.getString(R.string.save_key_coursesCacheTime),
- coursesCacheTime
- )
- putLong(context.getString(R.string.save_key_mensaCacheTime),
- mensaCacheTime
- )
- putLong(context.getString(R.string.save_key_timetableCacheTime),
- timetableCacheTime
- )
- apply()
- }
-
- }
-
- /**
- * save the course locally
- */
- fun saveCourse(context: Context, course: Course) {
- val sharedPref = context.getSharedPreferences(
- context.getString(R.string.preference_file_key),
- Context.MODE_PRIVATE
- )
-
- with (sharedPref.edit()) {
- putString(context.getString(R.string.save_key_course), course.courseName)
- putString(context.getString(R.string.save_key_courseTTLink), course.courseLink)
- apply()
- }
-
- cCourse = course
- }
-
- /**
- * save the primary color
- */
- fun saveColorPrimary(context: Context, colorPrimary: Int) {
- val sharedPref = context.getSharedPreferences(
- context.getString(R.string.preference_file_key),
- Context.MODE_PRIVATE
- )
-
- with (sharedPref.edit()) {
- putInt(context.getString(R.string.save_key_colorPrimary),
- colorPrimary
- )
- apply()
- }
-
- cColorPrimary = colorPrimary
- }
-
- /**
- * save the accent color
- */
- fun saveColorAccent(context: Context, colorAccent: Int) {
- val sharedPref = context.getSharedPreferences(
- context.getString(R.string.preference_file_key),
- Context.MODE_PRIVATE
- )
-
- with (sharedPref.edit()) {
- putInt(context.getString(R.string.save_key_colorAccent),
- colorAccent
- )
- apply()
- }
-
- cColorAccent = colorAccent
- }
-
- /**
- * save showBuffet
- */
- fun saveShowBuffet(context: Context, showBuffet: Boolean) {
- val sharedPref = context.getSharedPreferences(
- context.getString(R.string.preference_file_key),
- Context.MODE_PRIVATE
- )
-
- with (sharedPref.edit()) {
- putBoolean(context.getString(R.string.save_key_showBuffet),
- showBuffet
- )
- apply()
- }
-
- cShowBuffet = showBuffet
- }
-
- // the load function
- fun load(context: Context) {
- val sharedPref = context.getSharedPreferences(
- context.getString(R.string.preference_file_key),
- Context.MODE_PRIVATE
- )
-
- // load the update times (cache)
- coursesCacheTime = sharedPref.getLong(context.getString(
- R.string.save_key_coursesCacheTime
- ), 0)
- mensaCacheTime = sharedPref.getLong(context.getString(
- R.string.save_key_mensaCacheTime
- ), 0)
- timetableCacheTime = sharedPref.getLong(context.getString(
- R.string.save_key_timetableCacheTime
- ), 0)
-
- // load saved course
- cCourse = Course(
- sharedPref.getString(context.getString(R.string.save_key_courseTTLink), "https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0")!!,
- sharedPref.getString(context.getString(R.string.save_key_course), "AI3")!!
- )
-
- // load saved colors
- cColorPrimary = sharedPref.getInt(context.getString(
- R.string.save_key_colorPrimary
- ), Color.BLACK)
- cColorAccent = sharedPref.getInt(context.getString(
- R.string.save_key_colorAccent
- ), Color.parseColor("#3F51B5"))
-
- // load showBuffet
- cShowBuffet = sharedPref.getBoolean(context.getString(
- R.string.save_key_showBuffet
- ), true)
- }
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/QISPOSParser.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/QISPOSParser.kt
new file mode 100644
index 0000000..c8c8c2d
--- /dev/null
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/QISPOSParser.kt
@@ -0,0 +1,236 @@
+/**
+ * ProjectLaogai
+ *
+ * Copyright 2019-2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ */
+
+package org.mosad.seil0.projectlaogai.controller
+
+import android.content.Context
+import android.util.Log
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.jsoup.HttpStatusException
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Element
+import org.mosad.seil0.projectlaogai.R
+import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
+import org.mosad.seil0.projectlaogai.util.GradeSubject
+import java.security.KeyStore
+import java.security.cert.CertificateFactory
+import java.security.cert.X509Certificate
+import java.util.*
+import javax.net.ssl.SSLContext
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.TrustManagerFactory
+import kotlin.collections.ArrayList
+import kotlin.collections.HashMap
+
+/**
+ * Parse the qispos site the get all needed data for the grades fragment
+ */
+class QISPOSParser(val context: Context) {
+
+ private val className = this.javaClass.name
+ private val baseURL = "https://notenverwaltung.hs-offenburg.de"
+ private val loginPath = "/qispos/rds?state=user&type=1&category=auth.login&startpage=portal.vm&breadCrumbSource=portal"
+
+ /**
+ * check if qispos is available
+ * @return a http status code, 999 if no HttpStatusException supplied
+ */
+ fun checkQISPOSStatus(): Int {
+ return runBlocking {
+ withContext(Dispatchers.IO) {
+ val socketFactory = createSSLSocketFactory()
+
+ try {
+ val res = Jsoup.connect(baseURL + loginPath)
+ .sslSocketFactory(socketFactory)
+ .followRedirects(true)
+ .referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
+ .execute()
+
+ res.statusCode()
+ } catch (exHttp: HttpStatusException) {
+ exHttp.statusCode
+ } catch (ex: Exception) {
+ Log.e(className, "Error while checking status", ex)
+ 999
+ }
+
+ }
+ }
+ }
+
+ /**
+ * parse the html from readGrades()
+ * @return a SortedMap, each entry is a semester, each semester has a ArrayList with subjects
+ */
+ fun parseGrades(): SortedMap> {
+ val gradesMap = HashMap>()
+ val gradesListHtml = readGrades()
+
+ gradesListHtml?.select("table > tbody > tr")?.forEach {
+ val row = it.select("td.tabelle1_alignleft,td.tabelle1_aligncenter,td.tabelle1_alignright")
+
+ // only real subjects will be selected
+ if(row.size >= 6 && row[0].text().length >=7) {
+ val subject = GradeSubject(
+ id = row[0].text(),
+ name = row[1].text(),
+ semester = row[2].text(),
+ grade = if (row[3].text().isNotEmpty()) row[3].text() else row[4].text(),
+ credits = row[5].text()
+ )
+
+ if (gradesMap.containsKey(subject.semester)) {
+ gradesMap[subject.semester]!!.add(subject)
+ } else {
+ gradesMap[subject.semester] = arrayListOf(subject)
+ }
+ }
+
+ }
+
+ // return the sorted map
+ return gradesMap.toSortedMap(compareBy{
+ val oText = it.substringAfter(" ")
+
+ if (oText.contains("/")) {
+ oText.substringBefore("/").toInt() + 0.5
+ } else {
+ oText.toDouble()
+ }
+ }.thenBy { it })
+ }
+
+ /**
+ * read the grades html from qispos
+ * @return the grades list as html element or null
+ */
+ private fun readGrades(): Element?{
+
+ val credentials = EncryptedPreferences.readCredentials(context)
+ val username = credentials.first.substringBefore("@")
+ val password = credentials.second
+
+ return runBlocking {
+ withContext(Dispatchers.IO) {
+ try {
+ val socketFactory = createSSLSocketFactory()
+
+ // login, asdf = username, fdsa = password, wtf
+ val list = mapOf(
+ Pair("asdf", username),
+ Pair("fdsa", password),
+ Pair("submit", "Anmelden")
+ )
+
+ // login and get session cookies
+ val res = Jsoup.connect(baseURL + loginPath)
+ .sslSocketFactory(socketFactory)
+ .followRedirects(true)
+ .referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
+ .data(list)
+ .postDataCharset("UTF-8")
+ .execute()
+ Log.i(className, "login status is: ${res.statusMessage()}: ${res.statusCode()}")
+
+ val loginCookies = res.cookies()
+
+ // grades root document and url
+ val rootHtml =Jsoup.parse(res.body())
+ val gradesRootLink = rootHtml.select("li.menueListStyle > a.auflistung").last().attr("abs:href")
+
+ // parse grades url
+ val gradesHtml = Jsoup.connect(gradesRootLink)
+ .sslSocketFactory(socketFactory)
+ .followRedirects(true)
+ .cookies(loginCookies)
+ .referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
+ .get()
+
+ val gradesNextLink = gradesHtml.select("li.treelist > a.regular").attr("abs:href")
+
+ val gradesNextHtml = Jsoup.connect(gradesNextLink)
+ .sslSocketFactory(socketFactory)
+ .followRedirects(true)
+ .cookies(loginCookies)
+ .referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
+ .get()
+
+ val gradesListLink = gradesNextHtml.selectFirst("li.treelist > ul > li").selectFirst("a").attr("abs:href")
+
+ // get the grades list
+ val gradesListHtml = Jsoup.connect(gradesListLink)
+ .sslSocketFactory(socketFactory)
+ .followRedirects(true)
+ .cookies(loginCookies)
+ .referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
+ .get()
+
+ gradesListHtml.body()
+ } catch (ex: Exception) {
+ Log.e(className, "error while loading qispos", ex)
+ null
+ }
+ }
+ }
+
+ }
+
+ /**
+ * since the HS has a fucked up tls setup we need to work around that
+ */
+ private fun createSSLSocketFactory(): SSLSocketFactory {
+ // Load CAs from an InputStream
+ // (could be from a resource or ByteArrayInputStream or ...)
+ val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
+ val caInput = context.resources.openRawResource(R.raw.notenverwaltung_hs_offenburg_de)
+ val ca = caInput.use {
+ cf.generateCertificate(it) as X509Certificate
+ }
+
+ // Create a KeyStore containing our trusted CAs
+ val keyStoreType = KeyStore.getDefaultType()
+ val keyStore = KeyStore.getInstance(keyStoreType).apply {
+ load(null, null)
+ setCertificateEntry("ca", ca)
+ }
+
+ // Create a TrustManager that trusts the CAs inputStream our KeyStore
+ val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
+ val tmf = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
+ init(keyStore)
+ }
+
+ // Create an SSLContext that uses our TrustManager
+ val sslContext = SSLContext.getInstance("TLS").apply {
+ init(null, tmf.trustManagers, null)
+ }
+
+ return sslContext.socketFactory
+ }
+
+
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/TCoRAPIController.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/TCoRAPIController.kt
index 9d90148..4b10817 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/TCoRAPIController.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/TCoRAPIController.kt
@@ -22,105 +22,95 @@
package org.mosad.seil0.projectlaogai.controller
-import android.content.Context
-import android.util.Log
+import com.google.gson.Gson
+import com.google.gson.JsonParser
+import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.*
-import org.json.JSONObject
-import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.coursesCacheTime
-import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.mensaCacheTime
-import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.timetableCacheTime
-import java.io.BufferedWriter
-import java.io.File
-import java.io.FileWriter
+import org.mosad.seil0.projectlaogai.util.*
import java.net.URL
-import kotlin.Exception
+/**
+ * This Controller calls the tcor api,
+ * all functions return tcor api objects
+ */
class TCoRAPIController {
companion object {
- private const val className = "TCoRAPIController"
private const val tcorBaseURL = "https://tcor.mosad.xyz"
/**
- * get the json object from tcor api and write it as file (cache)
+ * Get a array of all currently available courses at the tcor API.
+ * Read the json object from tcor api
*/
- fun getCoursesList(context: Context) = GlobalScope.launch {
- try {
- val url = URL("$tcorBaseURL/courseList")
- val file = File(context.filesDir, "courses.json")
- // read data from the API
- val coursesObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
-
- // write the json object to a file
- withContext(Dispatchers.IO) {
- val writer = BufferedWriter(FileWriter(file))
- writer.write(coursesObject.toString())
- writer.close()
- }
-
- // update cache time
- coursesCacheTime = System.currentTimeMillis() / 1000
- PreferencesController.save(context)
- } catch (ex: Exception) {
- Log.e(className, "failed to get /courseList", ex)
- }
+ fun getCourseListNEW(): CoursesList {
+ val url = URL("$tcorBaseURL/courseList")
+ return Gson().fromJson(url.readText(), CoursesList().javaClass)
}
/**
- * get the json object from tcor api and write it as file (cache)
+ * Get current and next weeks mensa menus from the tcor API.
+ * Read the json object from tcor api
*/
- fun getMensa(context: Context) = GlobalScope.launch {
- try {
- val url = URL("$tcorBaseURL/mensamenu")
- val file = File(context.filesDir, "mensa.json")
-
- // read data from the API
- val mensaObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
-
- // write the json object to a file
- withContext(Dispatchers.IO) {
- val writer = BufferedWriter(FileWriter(file))
- writer.write(mensaObject.toString())
- writer.close()
- }
-
- // update cache time
- mensaCacheTime = System.currentTimeMillis() / 1000
- PreferencesController.save(context)
- } catch (ex: Exception) {
- Log.e(className, "failed to get /mensamenu", ex)
- }
+ fun getMensaMenu(): MensaMenu {
+ val url = URL("$tcorBaseURL/mensamenu")
+ return Gson().fromJson(url.readText(), MensaMenu().javaClass)
}
/**
- * get the json object from tcor api and write it as file (cache)
+ * Get the timetable for "courseName" at week "week"
+ * Read the json object from tcor api
+ * @param courseName the course name (e.g AI1)
+ * @param week the week to update (0 for the current and so on)
*/
- fun getTimetable(courseName: String, week: Int, context: Context) = GlobalScope.launch {
- try {
- val url = URL("$tcorBaseURL/timetable?courseName=$courseName&week=$week")
- val file = File(context.filesDir, "timetable-$courseName-$week.json")
+ fun getTimetable(courseName: String, week: Int): TimetableWeek {
+ val url = URL("$tcorBaseURL/timetable?course=$courseName&week=$week")
+ val timetableCW = Gson().fromJson(url.readText(), TimetableCourseWeek().javaClass)
- // read data from the API
- val timetableObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
+ return TimetableWeek(
+ timetableCW.meta.weekIndex,
+ timetableCW.meta.weekNumberYear,
+ timetableCW.timetable.days
+ )
+ }
- // write the json object to a file
- withContext(Dispatchers.IO) {
- val writer = BufferedWriter(FileWriter(file))
- writer.write(timetableObject.toString())
- writer.close()
+ /**
+ * Get all lessons for a course at one week (async)
+ * @param courseName the course name
+ * @param week the week to look up
+ */
+ fun getSubjectListAsync(courseName: String, week: Int): Deferred> {
+ val url = URL("$tcorBaseURL/subjectList?course=$courseName&week=$week")
+
+ return GlobalScope.async {
+ Gson().fromJson(url.readText(), ArrayList()::class.java)
+ }
+ }
+
+ /**
+ * Get all occurrences of a lesson for a course at one week
+ * @param courseName the course name
+ * @param subject the subject to search for
+ * @param week the week to look up
+ */
+ fun getLessons(courseName: String, subject: String, week: Int): ArrayList {
+ val url = URL("$tcorBaseURL/lessons?course=$courseName&subject=$subject&week=$week")
+ var array: ArrayList
+
+ runBlocking {
+ withContext(Dispatchers.Default) {
+ array = Gson().fromJson(
+ JsonParser.parseString(url.readText()).asJsonArray,
+ object : TypeToken>() {}.type
+ )
}
-
- // update cache time
- timetableCacheTime = System.currentTimeMillis() / 1000
- PreferencesController.save(context)
- } catch (ex: Exception) {
- Log.e(className, "failed to get /timetable", ex)
}
+ return array
}
+
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/cache/CacheController.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/cache/CacheController.kt
new file mode 100644
index 0000000..3fc2581
--- /dev/null
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/cache/CacheController.kt
@@ -0,0 +1,375 @@
+/**
+ * ProjectLaogai
+ *
+ * Copyright 2019-2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ */
+
+package org.mosad.seil0.projectlaogai.controller.cache
+
+import android.content.Context
+import android.util.Log
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import com.google.gson.JsonParser
+import com.google.gson.reflect.TypeToken
+import kotlinx.coroutines.*
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
+import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.coursesCacheTime
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.mensaCacheTime
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.timetableCacheTime
+import org.mosad.seil0.projectlaogai.util.*
+import java.io.*
+import java.util.*
+import kotlin.collections.ArrayList
+
+/**
+ * The cacheController reads and updates the cache files.
+ * It contains the courseList and mensaMenu object, all timetable objects
+ * are located in TimetableController.
+ */
+class CacheController(cont: Context) {
+
+ private val className = "CacheController"
+ private val context = cont
+
+ init {
+ val cal = Calendar.getInstance()
+ val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
+ val currentTime = System.currentTimeMillis() / 1000
+
+ // check if we need to update the mensa data before displaying it
+ cal.time = Date(mensaCacheTime * 1000)
+
+ // if a) it's monday and the last cache update was on sunday or b) the cache is older than 24hr, update blocking
+ if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - mensaMenu.meta.updateTime) > 86400) {
+ Log.i(className, "update mensa blocking")
+ GlobalScope.launch(Dispatchers.Default) { updateMensaMenu(context).join() }
+ }
+
+ // check if we need to update the timetable before displaying it
+ cal.time = Date(timetableCacheTime * 1000)
+
+ // if a) it`s monday and the last cache update was not on a sunday or b) the cache is older than 24hr, update blocking
+ if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - timetableCacheTime) > 86400) {
+ Log.i(className, "updating timetable after sunday!")
+
+ GlobalScope.launch(Dispatchers.Default) {
+ val threads = listOf(
+ updateTimetable(cCourse.courseName, 0, context),
+ updateTimetable(cCourse.courseName, 1, context)
+ )
+ threads.joinAll()
+ }
+ }
+
+ updateCourseList(context)
+
+ readStartCache(cCourse.courseName) // initially read values from cache
+
+ // check if an update is necessary, not blocking
+ if (currentTime - coursesCacheTime > 86400)
+ updateCourseList(context)
+
+ if (currentTime - mensaCacheTime > 10800)
+ updateMensaMenu(context)
+
+ if (currentTime - timetableCacheTime > 10800) {
+ TimetableController.update(context)
+ }
+
+ }
+
+ companion object {
+ private const val className = "CacheController"
+ var coursesList = ArrayList()
+ var mensaMenu = MensaMenu()
+
+ /**
+ * update the course list, async
+ */
+ fun updateCourseList(context: Context): Job {
+ val file = File(context.filesDir, "courses.json")
+ var courseListUp = CoursesList()
+
+ return GlobalScope.launch(Dispatchers.IO) {
+ try {
+ courseListUp = TCoRAPIController.getCourseListNEW()
+ } catch (ex: Exception) {
+ Log.e(className, "could not load course list from tcor", ex)
+ }
+
+ // update coursesList array list
+ coursesList = courseListUp.courses
+
+ // save cache file and update time
+ save(file, Gson().toJson(courseListUp))
+ coursesCacheTime = System.currentTimeMillis() / 1000
+ Preferences.save(context)
+ }
+ }
+
+ /**
+ * update the mensa menu, async
+ */
+ fun updateMensaMenu(context: Context): Job {
+ val file = File(context.filesDir, "mensa.json")
+
+ return GlobalScope.launch(Dispatchers.IO) {
+ try {
+ mensaMenu = TCoRAPIController.getMensaMenu()
+ } catch (ex: Exception) {
+ Log.e(className, "could not load mensa menu from tcor", ex)
+ }
+
+ // save cache file and update time
+ save(file, Gson().toJson(mensaMenu))
+ mensaCacheTime = System.currentTimeMillis() / 1000
+ Preferences.save(context)
+ }
+ }
+
+ /**
+ * update the timetable for a week, async
+ * @param courseName the course name (e.g AI1)
+ * @param week the week to update (0 for the current and so on)
+ */
+ fun updateTimetable(courseName: String, week: Int, context: Context): Job {
+ val file = File(context.filesDir, "timetable-$courseName-$week.json")
+ var timetable = TimetableWeek()
+
+ // try to update timetable from tcor, async
+ return GlobalScope.launch(Dispatchers.IO) {
+ try {
+ timetable = TCoRAPIController.getTimetable(courseName, week)
+ } catch (ex: Exception) {
+ Log.e(className, "could not load timetable $courseName[$week] from tcor", ex)
+ }
+
+ // update timetable in TTC
+ if (TimetableController.timetable.size > week) {
+ TimetableController.timetable[week] = timetable
+ } else {
+ TimetableController.timetable.add(timetable)
+ }
+
+ // save cache file and update time
+ save(file, Gson().toJson(timetable))
+ timetableCacheTime = System.currentTimeMillis() / 1000
+ Preferences.save(context)
+ }
+ }
+
+ /**
+ * update all additional subject lessons, async
+ */
+ fun updateAdditionalLessons(context: Context): Job {
+ val fileLessons = File(context.filesDir, "additional_lessons.json")
+
+ return GlobalScope.launch(Dispatchers.IO) {
+ TimetableController.subjectMap.forEach { (courseName, subjects) ->
+ // update all subjects for a course
+ subjects.forEach {subject ->
+ try {
+ TCoRAPIController.getLessons(courseName, subject, 0).forEach { lesson ->
+ TimetableController.addLesson(courseName, subject, lesson)
+ }
+ } catch (ex: Exception) {
+ Log.e(className, "could not load $courseName: $subject", ex)
+ }
+ }
+ }
+
+ save(fileLessons, Gson().toJson(TimetableController.lessonMap))
+ }
+ }
+
+ /**
+ * save changes in lessonMap and subjectMap,
+ * called on addSubject or removeSubject
+ */
+ fun saveAdditionalSubjects(context: Context) {
+ val fileLessons = File(context.filesDir, "additional_lessons.json")
+ val fileSubjects = File(context.filesDir, "additional_subjects.json")
+
+ save(fileLessons, Gson().toJson(TimetableController.lessonMap))
+ save(fileSubjects, Gson().toJson(TimetableController.subjectMap))
+ }
+
+ private fun save(file: File, text: String) {
+ try {
+ val writer = BufferedWriter(FileWriter(file))
+ writer.write(text)
+ writer.close()
+ } catch (ex: Exception) {
+ Log.e(className, "failed to write file \"${file.absoluteFile}\"", ex)
+ }
+ }
+
+ }
+
+ /**
+ * read coursesList, mensa (current and next week), timetable (current and next week)
+ * @param courseName the course name (e.g AI1)
+ */
+ private fun readStartCache(courseName: String) {
+ try {
+ readCoursesList()
+ } catch (ex : Exception) {
+ Log.e(className, "Error while reading the course list", ex)
+ }
+
+ try {
+ readMensa()
+ } catch (ex : Exception) {
+ Log.e(className, "Error while reading the mensa menu", ex)
+ }
+
+ try {
+ readTimetable(courseName, 0)
+ } catch (ex : Exception) {
+ Log.e(className, "Error while reading timetable week 0", ex)
+ }
+
+ try {
+ readTimetable(courseName, 1)
+ } catch (ex : Exception) {
+ Log.e(className, "Error while reading timetable week 1", ex)
+ }
+
+ try {
+ readAdditionalSubjects()
+ } catch (ex : Exception) {
+ Log.e(className, "Error while reading additional subjects", ex)
+ }
+ }
+
+ /**
+ * read the courses list from the cached file
+ * add them to the coursesList object
+ */
+ private fun readCoursesList() {
+ val file = File(context.filesDir, "courses.json")
+
+ // make sure the file exists
+ if (!file.exists()) {
+ runBlocking { updateCourseList(context).join() }
+ return
+ }
+
+ coursesList = FileReader(file).use {
+ GsonBuilder().create().fromJson(BufferedReader(it).readLine(), CoursesList().javaClass).courses
+ }
+ }
+
+ /**
+ * get the MensaMenu object from the cached json,
+ * if cache is empty create the cache file
+ */
+ private fun readMensa() {
+ val file = File(context.filesDir, "mensa.json")
+
+ // make sure the file exists
+ if (!file.exists()) {
+ runBlocking { updateMensaMenu(context).join() }
+ return
+ }
+
+ mensaMenu = FileReader(file).use {
+ GsonBuilder().create().fromJson(BufferedReader(it).readLine(), MensaMenu().javaClass)
+ }
+ }
+
+ /**
+ * read the weeks timetable from the cached file
+ * @param courseName the course name (e.g AI1)
+ * @param week the week to read (0 for the current and so on)
+ */
+ private fun readTimetable(courseName: String, week: Int) {
+ val file = File(context.filesDir, "timetable-$courseName-$week.json")
+
+ // if the file does not exist, call updateTimetable blocking and return
+ if (!file.exists()) {
+ runBlocking { updateTimetable(courseName, week, context).join() }
+ return
+ }
+
+ val timetableObject = FileReader(file).use {
+ JsonParser.parseString(BufferedReader(it).readLine()).asJsonObject
+ }
+
+ // if its a TimetableCourseWeek object migrate to TimetableWeek TODO remove converting at version 0.8
+ val timetable = if(timetableObject.has("meta")) {
+ Log.i(Companion.className, "trying to migrate TimetableCourseWeek to TimetableWeek")
+ val timetableWC = Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass)
+ save(file, Gson().toJson(TimetableWeek(
+ timetableWC.meta.weekIndex,
+ timetableWC.meta.weekNumberYear,
+ timetableWC.timetable.days
+ )))
+ TimetableWeek(timetableWC.meta.weekIndex, timetableWC.meta.weekNumberYear, timetableWC.timetable.days)
+ } else {
+ Gson().fromJson(timetableObject, TimetableWeek().javaClass)
+ }
+
+ // update timetable in TTC
+ if (TimetableController.timetable.size > week) {
+ TimetableController.timetable[week] = timetable
+ } else {
+ TimetableController.timetable.add(timetable)
+ }
+ }
+
+ private fun readAdditionalSubjects() {
+ val fileLessons = File(context.filesDir, "additional_lessons.json")
+ val fileSubjects = File(context.filesDir, "additional_subjects.json")
+
+ // make sure the file exists
+ if (!fileLessons.exists() || !fileSubjects.exists()) {
+ return
+ }
+
+ // clear the maps before loading, just to be save
+ TimetableController.lessonMap.clear()
+ TimetableController.subjectMap.clear()
+
+ // read subjects and lessons from cache file
+ FileReader(fileLessons).use {
+ TimetableController.lessonMap.putAll(
+ GsonBuilder().create()
+ .fromJson(BufferedReader(it).readLine(), object : TypeToken>() {}.type)
+ )
+ }
+
+ FileReader(fileSubjects).use {
+ TimetableController.subjectMap.putAll(
+ GsonBuilder().create()
+ .fromJson(BufferedReader(it).readLine(), HashMap>().javaClass)
+ )
+ }
+
+ // add lessons to timetable
+ TimetableController.lessonMap.forEach { (_, lesson) ->
+ TimetableController.addLessonToTimetable(lesson)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/cache/TimetableController.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/cache/TimetableController.kt
new file mode 100644
index 0000000..2863bfe
--- /dev/null
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/cache/TimetableController.kt
@@ -0,0 +1,131 @@
+/**
+ * ProjectLaogai
+ *
+ * Copyright 2019-2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ */
+
+package org.mosad.seil0.projectlaogai.controller.cache
+
+import android.content.Context
+import kotlinx.coroutines.Job
+import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
+import org.mosad.seil0.projectlaogai.util.Lesson
+import org.mosad.seil0.projectlaogai.util.TimetableWeek
+
+/**
+ * The TimetableController contains the timetable, subjectMap
+ * and lessonMap objects. It also contains the additional subjects logic.
+ * All functions ro read or update cache files are located in the CacheController.
+ *
+ * TODO
+ * * add second week
+ * * add configurable week to addSubject() and removeSubject(), updateAdditionalLessons()
+ */
+class TimetableController {
+
+ companion object {
+ val timetable = ArrayList()
+ val lessonMap = HashMap() // the key is courseName-subject-lessonID
+ val subjectMap = HashMap>() // the key is courseName
+
+ /**
+ * update the main timetable and all additional subjects, async
+ */
+ fun update(context: Context): List {
+ return listOf(
+ CacheController.updateTimetable(cCourse.courseName, 0, context),
+ CacheController.updateTimetable(cCourse.courseName, 1, context),
+ CacheController.updateAdditionalLessons(context)
+ )
+ }
+
+ /**
+ * add a subject to the subjectMap and all it's lessons
+ * to the lessonMap
+ * @param courseName course to which the subject belongs
+ * @param subject the subjects name
+ */
+ fun addSubject(courseName: String, subject: String, context: Context) {
+ // add subject
+ if (subjectMap.containsKey(courseName)) {
+ subjectMap[courseName]?.add(subject)
+ } else {
+ subjectMap[courseName] = arrayListOf(subject)
+ }
+
+ // add concrete lessons
+ TCoRAPIController.getLessons(courseName, subject, 0).forEach { lesson ->
+ addLesson(courseName, subject, lesson)
+ }
+
+ CacheController.saveAdditionalSubjects(context)
+ }
+
+ /**
+ * remove a subject from the subjectMap and all it's lessons
+ * from the lessonMap
+ * @param courseName course to which the subject belongs
+ * @param subject the subjects name
+ */
+ fun removeSubject(courseName: String, subject: String, context: Context) {
+ // remove subject
+ subjectMap[courseName]?.remove(subject)
+
+ // remove concrete lessons
+ val iterator = lessonMap.iterator()
+ while (iterator.hasNext()) {
+ val it = iterator.next()
+ if(it.key.contains("$courseName-$subject")) {
+ // remove the lesson from the lessons list
+ iterator.remove() // use iterator to remove, otherwise ConcurrentModificationException
+
+ // remove the lesson from the timetable
+ val id = it.value.lessonID.split(".")
+ if(id.size == 3)
+ timetable[0].days[id[0].toInt()].timeslots[id[1].toInt()].remove(it.value)
+ }
+ }
+
+ CacheController.saveAdditionalSubjects(context)
+ }
+
+ /**
+ * add a lesson to the lessonMap, also add it to the timetable
+ */
+ fun addLesson(courseName: String, subject: String, lesson: Lesson) {
+ //the courseName, subject and lessonID, separator: -
+ val key = "$courseName-$subject-${lesson.lessonID}"
+ lessonMap[key] = lesson
+
+ addLessonToTimetable(lesson)
+ }
+
+ /**
+ * add a lesson to the timetable
+ */
+ fun addLessonToTimetable(lesson: Lesson) {
+ val id = lesson.lessonID.split(".")
+ if(id.size == 3)
+ timetable[0].days[id[0].toInt()].timeslots[id[1].toInt()].add(lesson)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/preferences/EncryptedPreferences.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/preferences/EncryptedPreferences.kt
new file mode 100644
index 0000000..3ac8825
--- /dev/null
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/preferences/EncryptedPreferences.kt
@@ -0,0 +1,110 @@
+/**
+ * ProjectLaogai
+ *
+ * Copyright 2019-2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ */
+
+package org.mosad.seil0.projectlaogai.controller.preferences
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.security.keystore.KeyGenParameterSpec
+import android.security.keystore.KeyProperties
+import android.util.Log
+import androidx.security.crypto.EncryptedSharedPreferences
+import androidx.security.crypto.MasterKey
+import org.mosad.seil0.projectlaogai.R
+
+object EncryptedPreferences {
+
+ var email = ""
+ internal set
+
+ /**
+ * save user email and password to encrypted preference
+ */
+ fun saveCredentials(email: String, password: String, context: Context) {
+ this.email = email
+
+ with (getEncryptedPreferences(context)?.edit()) {
+ this?.putString(context.getString(R.string.save_key_user_email), email)
+ this?.putString(context.getString(R.string.save_key_user_password), password)
+ this?.apply()
+ }
+
+ }
+
+ /**
+ * read user email and password from encrypted preference
+ * @return Pair(email, password)
+ */
+ fun readCredentials(context: Context): Pair {
+ return with (getEncryptedPreferences(context)) {
+ email = this?.getString(context.getString(R.string.save_key_user_email), "").toString()
+
+ Pair(
+ this?.getString(context.getString(R.string.save_key_user_email), "").toString(),
+ this?.getString(context.getString(R.string.save_key_user_password), "").toString()
+ )
+ }
+ }
+
+ /**
+ * initially load the stored values
+ */
+ fun load(context: Context) {
+ with(getEncryptedPreferences(context)) {
+ email = this?.getString(
+ context.getString(R.string.save_key_user_email), ""
+ ).toString()
+ }
+ }
+
+ /**
+ * create a encrypted shared preference
+ */
+ private fun getEncryptedPreferences(context: Context): SharedPreferences? {
+ return try {
+ val spec = KeyGenParameterSpec.Builder(MasterKey.DEFAULT_MASTER_KEY_ALIAS,
+ KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .setKeySize(MasterKey.DEFAULT_AES_GCM_MASTER_KEY_SIZE)
+ .build()
+
+ val masterKey = MasterKey.Builder(context)
+ .setKeyGenParameterSpec(spec)
+ .build()
+
+ EncryptedSharedPreferences.create(
+ context,
+ context.getString(R.string.encrypted_preference_file_key),
+ masterKey,
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
+ )
+ } catch (ex: Exception) {
+ Log.e(javaClass.name, "Could not create encrypted shared preference.", ex)
+ null
+ }
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/preferences/Preferences.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/preferences/Preferences.kt
new file mode 100644
index 0000000..86dadae
--- /dev/null
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/preferences/Preferences.kt
@@ -0,0 +1,192 @@
+/**
+ * ProjectLaogai
+ *
+ * Copyright 2019-2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ */
+
+package org.mosad.seil0.projectlaogai.controller.preferences
+
+import android.content.Context
+import android.graphics.Color
+import org.mosad.seil0.projectlaogai.R
+import org.mosad.seil0.projectlaogai.util.Course
+
+/**
+ * The PreferencesController class
+ * contains all preferences and global variables that exist in this app
+ */
+object Preferences {
+
+ var coursesCacheTime: Long = 0
+ var mensaCacheTime: Long = 0
+ var timetableCacheTime: Long = 0
+ var cColorPrimary: Int = Color.parseColor("#009688")
+ var cColorAccent: Int = Color.parseColor("#0096ff")
+ var cCourse = Course(
+ "https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0",
+ "AI3"
+ )
+ var cShowBuffet = true
+ var oGiants = false
+
+ // TODO move!
+ var themePrimary = 0
+ var themeSecondary = 0
+
+ // the save function
+ fun save(context: Context) {
+ val sharedPref = context.getSharedPreferences(
+ context.getString(R.string.preference_file_key),
+ Context.MODE_PRIVATE
+ )
+
+ // save the update times (cache)
+ with (sharedPref.edit()) {
+ putLong(context.getString(R.string.save_key_coursesCacheTime),
+ coursesCacheTime
+ )
+ putLong(context.getString(R.string.save_key_mensaCacheTime),
+ mensaCacheTime
+ )
+ putLong(context.getString(R.string.save_key_timetableCacheTime),
+ timetableCacheTime
+ )
+ apply()
+ }
+
+ }
+
+ /**
+ * save the course locally
+ */
+ fun saveCourse(context: Context, course: Course) {
+ val sharedPref = context.getSharedPreferences(
+ context.getString(R.string.preference_file_key),
+ Context.MODE_PRIVATE
+ )
+
+ with (sharedPref.edit()) {
+ putString(context.getString(R.string.save_key_course), course.courseName)
+ putString(context.getString(R.string.save_key_courseTTLink), course.courseLink)
+ apply()
+ }
+
+ cCourse = course
+ }
+
+ /**
+ * save the primary color
+ */
+ fun saveColorPrimary(context: Context, colorPrimary: Int) {
+ val sharedPref = context.getSharedPreferences(
+ context.getString(R.string.preference_file_key),
+ Context.MODE_PRIVATE
+ )
+
+ with (sharedPref.edit()) {
+ putInt(context.getString(R.string.save_key_colorPrimary),
+ colorPrimary
+ )
+ apply()
+ }
+
+ cColorPrimary = colorPrimary
+ }
+
+ /**
+ * save the accent color
+ */
+ fun saveColorAccent(context: Context, colorAccent: Int) {
+ val sharedPref = context.getSharedPreferences(
+ context.getString(R.string.preference_file_key),
+ Context.MODE_PRIVATE
+ )
+
+ with (sharedPref.edit()) {
+ putInt(context.getString(R.string.save_key_colorAccent),
+ colorAccent
+ )
+ apply()
+ }
+
+ cColorAccent = colorAccent
+ }
+
+ /**
+ * save showBuffet
+ */
+ fun saveShowBuffet(context: Context, showBuffet: Boolean) {
+ val sharedPref = context.getSharedPreferences(
+ context.getString(R.string.preference_file_key),
+ Context.MODE_PRIVATE
+ )
+
+ with (sharedPref.edit()) {
+ putBoolean(context.getString(R.string.save_key_showBuffet),
+ showBuffet
+ )
+ apply()
+ }
+
+ cShowBuffet = showBuffet
+ }
+
+ /**
+ * initially load the stored values
+ */
+ fun load(context: Context) {
+ val sharedPref = context.getSharedPreferences(
+ context.getString(R.string.preference_file_key),
+ Context.MODE_PRIVATE
+ )
+
+ // load the update times (cache)
+ coursesCacheTime = sharedPref.getLong(context.getString(
+ R.string.save_key_coursesCacheTime
+ ), 0)
+ mensaCacheTime = sharedPref.getLong(context.getString(
+ R.string.save_key_mensaCacheTime
+ ), 0)
+ timetableCacheTime = sharedPref.getLong(context.getString(
+ R.string.save_key_timetableCacheTime
+ ), 0)
+
+ // load saved course
+ cCourse = Course(
+ sharedPref.getString(context.getString(R.string.save_key_courseTTLink),
+ "https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0"
+ )!!,
+ sharedPref.getString(context.getString(R.string.save_key_course), "AI3")!!
+ )
+
+ // load saved colors
+ cColorPrimary = sharedPref.getInt(context.getString(
+ R.string.save_key_colorPrimary
+ ), cColorPrimary)
+ cColorAccent = sharedPref.getInt(context.getString(
+ R.string.save_key_colorAccent
+ ), cColorAccent)
+
+ // load showBuffet
+ cShowBuffet = sharedPref.getBoolean(context.getString(
+ R.string.save_key_showBuffet
+ ), true)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/GradesFragment.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/GradesFragment.kt
new file mode 100644
index 0000000..376be8d
--- /dev/null
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/GradesFragment.kt
@@ -0,0 +1,200 @@
+/**
+ * ProjectLaogai
+ *
+ * Copyright 2019-2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ */
+
+package org.mosad.seil0.projectlaogai.fragments
+
+import android.graphics.Rect
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.core.content.res.ResourcesCompat
+import androidx.fragment.app.Fragment
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+import kotlinx.android.synthetic.main.fragment_grades.*
+import kotlinx.coroutines.*
+import org.mosad.seil0.projectlaogai.R
+import org.mosad.seil0.projectlaogai.controller.QISPOSParser
+import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
+import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
+import org.mosad.seil0.projectlaogai.uicomponents.GradeLinearLayout
+import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
+
+/**
+ * The grades fragment class
+ * contains all needed parts to display and the grades screen
+ */
+class GradesFragment : Fragment() {
+
+ private lateinit var refreshLayoutGrades: SwipeRefreshLayout
+
+ private lateinit var parser: QISPOSParser
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ return inflater.inflate(R.layout.fragment_grades, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ refreshLayoutGrades = view.findViewById(R.id.refreshLayout_Grades)
+ refreshLayoutGrades.isEnabled = false // disable swipe
+ refreshLayoutGrades.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
+
+ parser = QISPOSParser(context!!)// init the parser
+
+ if (checkCredentials() && checkQisposStatus()) {
+ GlobalScope.launch(Dispatchers.Default) {
+ addGrades()
+ }
+ }
+ }
+
+ /**
+ * check if the credentials are set, if not show a login dialog
+ */
+ private fun checkCredentials(): Boolean {
+ val credentials = EncryptedPreferences.readCredentials(context!!)
+ var credentialsPresent = false
+
+ // if there is no password set, show the login dialog
+ if (credentials.first.isEmpty() || credentials.second.isEmpty()) {
+ LoginDialog(this.context!!)
+ .positiveButton {
+ EncryptedPreferences.saveCredentials(email, password, context)
+ addGrades()
+ }
+ .negativeButton {
+ txtView_Loading.text = resources.getString(R.string.credentials_missing)
+ }
+ .show {
+ email = EncryptedPreferences.email
+ password = ""
+ }
+ } else {
+ credentialsPresent = true
+ }
+
+ return credentialsPresent
+ }
+
+ /**
+ * check if qispos is available, if not show an error
+ */
+ private fun checkQisposStatus(): Boolean {
+ val statusCode = parser.checkQISPOSStatus()
+
+ // show error if the status code is not 200
+ if (statusCode != 200) {
+ val infoText = resources.getString(when(statusCode) {
+ 503 -> R.string.qispos_unavailable
+ else -> R.string.qispos_generic_error
+ })
+
+ val img = ResourcesCompat.getDrawable(resources, R.drawable.ic_error_outline_black_24dp, null)?.apply {
+ bounds = Rect(0, 0, 75, 75)
+ }
+
+ txtView_Loading?.apply {
+ text = infoText
+ setCompoundDrawables(null, null, null, img)
+ }
+ }
+
+ return statusCode == 200
+ }
+
+ // add the grades to the layout, async
+ private fun addGrades() = GlobalScope.launch(Dispatchers.Default) {
+ withContext(Dispatchers.Main) {
+ refreshLayout_Grades.isRefreshing = true
+ }
+
+ val grades = parser.parseGrades()
+
+ withContext(Dispatchers.Main) {
+ linLayout_Grades.removeAllViews() // clear layout
+ }
+
+ // for each semester add a new card
+ grades.forEach { semester ->
+ val usedSubjects = ArrayList()
+ val semesterCard = DayCardView(context!!)
+ semesterCard.setDayHeading(semester.key)
+
+ // for each subject add a new linLayout
+ semester.value.forEachIndexed { index, subject ->
+ if (usedSubjects.contains(subject.name)) {
+ return@forEachIndexed
+ }
+
+ // get the first sub subjects
+ val subSubject = semester.value.firstOrNull {
+ it.name.contains(subject.name) && it.name != subject.name
+ }
+
+ // if sub subject is not null, add it to used subjects
+ subSubject?.let {
+ usedSubjects.add(it.name)
+ }
+
+ val subjectLayout = GradeLinearLayout(context).set {
+ subjectName = subject.name
+ grade = subject.grade
+ subSubjectName = subSubject?.name.toString()
+ subGrade = subSubject?.grade.toString()
+ }
+
+ // disable sub-subject if not set
+ if (subSubject == null)
+ subjectLayout.disableSubSubject()
+
+ // disable divider if last element
+ if (index == semester.value.lastIndex || semester.value.indexOf(subSubject) == semester.value.lastIndex)
+ subjectLayout.disableDivider()
+
+ semesterCard.getLinLayoutDay().addView(subjectLayout)
+ }
+
+ // without context we can't access the view
+ withContext(Dispatchers.Main) {
+ linLayout_Grades.addView(semesterCard)
+ }
+ }
+
+
+
+ val txtViewLegal = TextView(context).apply {
+ text = resources.getString(R.string.without_guarantee)
+ textAlignment = View.TEXT_ALIGNMENT_CENTER
+ }
+
+ // stop refreshing and show legal warning
+ withContext(Dispatchers.Main) {
+ linLayout_Grades.addView(txtViewLegal)
+ refreshLayout_Grades.isRefreshing = false
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/HomeFragment.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/HomeFragment.kt
index bfd233a..43bc072 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/HomeFragment.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/HomeFragment.kt
@@ -33,13 +33,13 @@ import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.R
-import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaMenu
-import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.timetables
-import org.mosad.seil0.projectlaogai.hsoparser.Meal
-import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar
-import org.mosad.seil0.projectlaogai.hsoparser.TimetableDay
+import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.mensaMenu
+import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
+import org.mosad.seil0.projectlaogai.util.Meal
+import org.mosad.seil0.projectlaogai.util.TimetableDay
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
+import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
import java.text.SimpleDateFormat
import java.util.*
@@ -53,13 +53,14 @@ class HomeFragment : Fragment() {
private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault())
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- val view: View = inflater.inflate(R.layout.fragment_home, container, false)
+ return inflater.inflate(R.layout.fragment_home, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
addMensaMenu()
addTimeTable()
-
- // Inflate the layout for this fragment
- return view
}
/**
@@ -119,7 +120,7 @@ class HomeFragment : Fragment() {
withContext(Dispatchers.Main) {
- if (isAdded && timetables.isNotEmpty()) {
+ if (isAdded && TimetableController.timetable.isNotEmpty()) {
try {
val dayCardView = findNextDay(NotRetardedCalendar.getDayOfWeekIndex())
linLayout_Home.addView(dayCardView)
@@ -144,12 +145,13 @@ class HomeFragment : Fragment() {
var dayIndexSearch = startDayIndex
var weekIndexSearch = 0
- while (dayTimetable == null && weekIndexSearch < timetables.size) {
+ while (dayTimetable == null && weekIndexSearch < TimetableController.timetable.size) {
for (dayIndex in dayIndexSearch..5) {
- dayTimetable = timetables[weekIndexSearch].timetable.days[dayIndex]
+ dayTimetable = TimetableController.timetable[weekIndexSearch].days[dayIndex]
// some wired calendar magic, calculate the correct date to be shown ((timetable week - current week * 7) + (dayIndex - dayIndex of current week)
- val daysToAdd = (timetables[weekIndexSearch].meta.weekNumberYear - NotRetardedCalendar.getWeekOfYear()) * 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex())
+ val daysToAdd = ((TimetableController.timetable[weekIndexSearch].weekNumberYear - NotRetardedCalendar.getWeekOfYear())
+ * 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex()))
dayCardView.addTimetableDay(dayTimetable, daysToAdd)
// if there are no lessons don't show the dayCardView
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/MensaFragment.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/MensaFragment.kt
index 7bbc9d9..4d83cea 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/MensaFragment.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/MensaFragment.kt
@@ -33,14 +33,15 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.mosad.seil0.projectlaogai.R
-import org.mosad.seil0.projectlaogai.controller.CacheController
-import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaMenu
-import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cShowBuffet
-import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
-import org.mosad.seil0.projectlaogai.hsoparser.MensaWeek
-import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar
+import org.mosad.seil0.projectlaogai.controller.cache.CacheController
+import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.mensaMenu
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cShowBuffet
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
+import org.mosad.seil0.projectlaogai.uicomponents.TextViewInfo
+import org.mosad.seil0.projectlaogai.util.MensaWeek
+import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
/**
* The mensa controller class
@@ -49,11 +50,15 @@ import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
class MensaFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ return inflater.inflate(R.layout.fragment_mensa, container, false)
+ }
- val view: View = inflater.inflate(R.layout.fragment_mensa, container, false)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
- // init actions
- refreshAction()
+ refreshLayout_Mensa.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
+
+ refreshAction() // init actions
GlobalScope.launch(Dispatchers.Default) {
val dayCurrent = if(NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
@@ -63,10 +68,13 @@ class MensaFragment : Fragment() {
addWeek(mensaMenu.nextWeek, 0)
}
-
- // TODO should we show a info if there is no more food this & next week?
-
- return view
+ // show a info if there are no more menus
+ if (linLayout_Mensa.childCount == 0) {
+ val txtViewInfo = TextViewInfo(context!!).set {
+ txt = resources.getString(R.string.no_more_meals)
+ }
+ linLayout_Mensa.addView(txtViewInfo)
+ }
}
/**
@@ -77,7 +85,6 @@ class MensaFragment : Fragment() {
private fun addWeek(menusWeek: MensaWeek, dayStart: Int) = GlobalScope.launch(Dispatchers.Default) {
withContext(Dispatchers.Main) {
-
// only add the days dayStart to Fri since the mensa is closed on Sat/Sun
for (dayIndex in dayStart..4) {
var helpMeal = MealLinearLayout(context)
@@ -98,10 +105,9 @@ class MensaFragment : Fragment() {
helpMeal.disableDivider()
- if(dayCardView.getLinLayoutDay().childCount > 1)
+ if(dayCardView.getLinLayoutDay().childCount > 2)
linLayout_Mensa.addView(dayCardView)
}
-
}
}
@@ -123,9 +129,7 @@ class MensaFragment : Fragment() {
* refresh the mensa cache and update the mensa screen
*/
private fun updateMensaScreen() = GlobalScope.launch(Dispatchers.Default) {
- // update the cache
- TCoRAPIController.getMensa(context!!).join() // blocking since we want the new data
- CacheController.readMensa(context!!)
+ CacheController.updateMensaMenu(context!!).join() // blocking since we want the new data
withContext(Dispatchers.Main) {
// remove all menus from the layout
@@ -139,6 +143,14 @@ class MensaFragment : Fragment() {
addWeek(mensaMenu.nextWeek, 0)
refreshLayout_Mensa.isRefreshing = false
+
+ // show a info if there are no more menus
+ if (linLayout_Mensa.childCount == 0) {
+ val txtViewInfo = TextViewInfo(context!!).set {
+ txt = resources.getString(R.string.no_more_meals)
+ }
+ linLayout_Mensa.addView(txtViewInfo)
+ }
}
}
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/MoodleFragment.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/MoodleFragment.kt
index 7cbde41..b94199f 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/MoodleFragment.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/MoodleFragment.kt
@@ -42,8 +42,11 @@ class MoodleFragment : Fragment() {
private lateinit var webSettings: WebSettings
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ return inflater.inflate(R.layout.fragment_moodle, container, false)
+ }
- val view: View = inflater.inflate(R.layout.fragment_moodle, container, false)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
webView = view.findViewById(R.id.webView)
webView.loadUrl("https://elearning.hs-offenburg.de/moodle/")
@@ -52,7 +55,5 @@ class MoodleFragment : Fragment() {
//webSettings.setJavaScriptEnabled(true) // Enable Javascript
webView.webViewClient = WebViewClient() // Force links and redirects to open in the WebView instead of in a browser
-
- return view
}
}
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/SettingsFragment.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/SettingsFragment.kt
index 059f5e9..4599d51 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/SettingsFragment.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/SettingsFragment.kt
@@ -29,28 +29,35 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
-import android.widget.Switch
+import android.widget.TextView
+import androidx.appcompat.widget.SwitchCompat
import androidx.fragment.app.Fragment
import com.afollestad.aesthetic.Aesthetic
import com.afollestad.materialdialogs.MaterialDialog
+import com.afollestad.materialdialogs.WhichButton
+import com.afollestad.materialdialogs.actions.getActionButton
+import com.afollestad.materialdialogs.callbacks.onDismiss
import com.afollestad.materialdialogs.color.colorChooser
import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.list.listItems
+import com.afollestad.materialdialogs.list.listItemsMultiChoice
import com.afollestad.materialdialogs.list.listItemsSingleChoice
import de.psdev.licensesdialog.LicensesDialog
import kotlinx.android.synthetic.main.fragment_settings.*
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.BuildConfig
import org.mosad.seil0.projectlaogai.R
-import org.mosad.seil0.projectlaogai.controller.CacheController
-import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.coursesList
-import org.mosad.seil0.projectlaogai.controller.PreferencesController
-import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorAccent
-import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorPrimary
-import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
-import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cShowBuffet
-import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
-import org.mosad.seil0.projectlaogai.hsoparser.DataTypes
+import org.mosad.seil0.projectlaogai.controller.cache.CacheController
+import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.coursesList
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
+import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
+import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorAccent
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorPrimary
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cShowBuffet
+import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
+import org.mosad.seil0.projectlaogai.util.DataTypes
import java.util.*
/**
@@ -61,19 +68,30 @@ class SettingsFragment : Fragment() {
private lateinit var linLayoutUser: LinearLayout
private lateinit var linLayoutCourse: LinearLayout
+ private lateinit var linLayoutManageLessons: LinearLayout
private lateinit var linLayoutAbout: LinearLayout
private lateinit var linLayoutLicence: LinearLayout
private lateinit var linLayoutTheme: LinearLayout
private lateinit var linLayoutPrimaryColor: LinearLayout
private lateinit var linLayoutAccentColor: LinearLayout
- private lateinit var switchBuffet: Switch
+ private lateinit var switchBuffet: SwitchCompat
+ private lateinit var txtViewCourse: TextView
+
+ private var selectedTheme = DataTypes.Theme.Light
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ return inflater.inflate(R.layout.fragment_settings, container, false)
+ }
- val view: View = inflater.inflate(R.layout.fragment_settings, container, false)
+ /**
+ * initialize the settings gui
+ */
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
linLayoutUser = view.findViewById(R.id.linLayout_User)
linLayoutCourse = view.findViewById(R.id.linLayout_Course)
+ linLayoutManageLessons = view.findViewById(R.id.linLayout_ManageLessons)
linLayoutAbout = view.findViewById(R.id.linLayout_About)
linLayoutLicence = view.findViewById(R.id.linLayout_Licence)
linLayoutTheme = view.findViewById(R.id.linLayout_Theme)
@@ -81,16 +99,13 @@ class SettingsFragment : Fragment() {
linLayoutAccentColor = view.findViewById(R.id.linLayout_AccentColor)
switchBuffet = view.findViewById(R.id.switch_buffet)
+ // if we call txtView_Course via KAE view binding it'll result in a NPE in the onDismissed call
+ txtViewCourse = view.findViewById(R.id.txtView_Course)
+
initActions()
- // Inflate the layout for this fragment
- return view
- }
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- // initialize the settings gui
+ txtView_User.text = EncryptedPreferences.email.ifEmpty { resources.getString(R.string.sample_user) }
txtView_Course.text = cCourse.courseName
txtView_AboutDesc.text = resources.getString(R.string.about_version, BuildConfig.VERSION_NAME, getString(R.string.build_time))
switch_buffet.isChecked = cShowBuffet // init switch
@@ -100,18 +115,21 @@ class SettingsFragment : Fragment() {
when(outValue.string) {
"light" -> {
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryLight, activity!!.theme))
- txtView_SelectedTheme.text = resources.getString(R.string.themeLight)
+ selectedTheme = DataTypes.Theme.Light
+ selectedTheme.string = resources.getString(R.string.themeLight)
}
"dark" -> {
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryDark, activity!!.theme))
- txtView_SelectedTheme.text = resources.getString(R.string.themeDark)
+ selectedTheme = DataTypes.Theme.Dark
+ selectedTheme.string = resources.getString(R.string.themeDark)
}
"black" -> {
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryDark, activity!!.theme))
- txtView_SelectedTheme.text = resources.getString(R.string.themeBlack)
+ selectedTheme = DataTypes.Theme.Black
+ selectedTheme.string = resources.getString(R.string.themeBlack)
}
}
-
+ txtView_SelectedTheme.text = selectedTheme.string
}
/**
@@ -120,15 +138,51 @@ class SettingsFragment : Fragment() {
private fun initActions() {
linLayoutUser.setOnClickListener {
// open a new dialog
+ LoginDialog(context!!)
+ .positiveButton {
+ EncryptedPreferences.saveCredentials(email, password, context)
+ }
+ .show {
+ email = EncryptedPreferences.email
+ password = ""
+ }
}
linLayoutUser.setOnLongClickListener {
- PreferencesController.oGiants = true // enable easter egg
+ Preferences.oGiants = true // enable easter egg
return@setOnLongClickListener true
}
linLayoutCourse.setOnClickListener {
- selectCourse(context!!)
+ selectCourse(context!!).show {
+ onDismiss {
+ txtViewCourse.text = cCourse.courseName // update txtView after the dialog is dismissed
+ }
+ }
+ }
+
+ linLayoutManageLessons.setOnClickListener {
+ val lessons = ArrayList()
+ TimetableController.subjectMap.forEach { pair ->
+ pair.value.forEach {
+ lessons.add("${pair.key} - $it")
+ }
+ }
+
+ MaterialDialog(context!!).show {
+ title(R.string.manage_lessons)
+ positiveButton(R.string.delete)
+ negativeButton(R.string.cancel)
+ getActionButton(WhichButton.POSITIVE).updateTextColor(cColorAccent)
+ getActionButton(WhichButton.NEGATIVE).updateTextColor(cColorAccent)
+
+ listItemsMultiChoice(items = lessons) { _, _, items ->
+ items.forEach {
+ val list = it.split(" - ")
+ TimetableController.removeSubject(list[0], list[1], context)
+ }
+ }
+ }
}
linLayoutAbout.setOnClickListener {
@@ -171,8 +225,10 @@ class SettingsFragment : Fragment() {
resources.getString(R.string.themeDark),
resources.getString(R.string.themeBlack)
)
+
MaterialDialog(context!!).show {
- listItemsSingleChoice(items = themes) { _, index, _ ->
+ title(R.string.theme)
+ listItemsSingleChoice(items = themes, initialSelection = selectedTheme.ordinal) { _, index, _ ->
Aesthetic.config {
when (index) {
0 -> activityTheme(R.style.AppTheme_Light)
@@ -189,7 +245,6 @@ class SettingsFragment : Fragment() {
linLayoutPrimaryColor.setOnClickListener {
// open a new color chooser dialog
MaterialDialog(context!!)
- .title(R.string.primary_color)
.colorChooser(DataTypes().primaryColors, allowCustomArgb = true, initialSelection = cColorPrimary) { _, color ->
view_PrimaryColor.setBackgroundColor(color)
Aesthetic.config {
@@ -197,17 +252,19 @@ class SettingsFragment : Fragment() {
colorPrimaryDark(color)
apply()
}
-
- PreferencesController.saveColorPrimary(context!!, color)
+ Preferences.saveColorPrimary(context!!, color)
}
- .positiveButton(R.string.select)
- .show()
+ .show {
+ title(R.string.primary_color)
+ positiveButton(R.string.select)
+ getActionButton(WhichButton.POSITIVE).updateTextColor(cColorAccent)
+ }
+
}
linLayoutAccentColor.setOnClickListener {
// open a new color chooser dialog
MaterialDialog(context!!)
- .title(R.string.accent_color)
.colorChooser(DataTypes().accentColors, allowCustomArgb = true, initialSelection = cColorAccent) { _, color ->
view_AccentColor.setBackgroundColor(color)
Aesthetic.config {
@@ -215,55 +272,53 @@ class SettingsFragment : Fragment() {
apply()
}
- PreferencesController.saveColorAccent(context!!, color)
+ Preferences.saveColorAccent(context!!, color)
+ }
+ .show{
+ title(R.string.accent_color)
+ positiveButton(R.string.select)
+ getActionButton(WhichButton.POSITIVE).updateTextColor(cColorAccent)
}
- .positiveButton(R.string.select)
- .show()
}
switchBuffet.setOnClickListener {
- PreferencesController.saveShowBuffet(context!!, switchBuffet.isChecked)
+ Preferences.saveShowBuffet(context!!, switchBuffet.isChecked)
}
}
- fun selectCourse(context: Context) {
+ fun selectCourse(context: Context) : MaterialDialog {
val courseNameList = ArrayList()
coursesList.forEach { (_, courseName) ->
courseNameList.add(courseName)
}
- // open a new dialog
- MaterialDialog(context)
+ // return a new course selection dialog
+ return MaterialDialog(context)
.title(R.string.select_course)
.listItems(items = courseNameList) { _, index, _ ->
- val dialog = MaterialDialog(context).cancelable(false)
+ val loadingDialog = MaterialDialog(context).cancelable(false)
.cancelOnTouchOutside(false)
.customView(R.layout.dialog_loading)
- dialog.show()
+ loadingDialog.show()
GlobalScope.launch(Dispatchers.Default) {
- PreferencesController.saveCourse(context, coursesList[index]) // save the course
+ Preferences.saveCourse(context, coursesList[index]) // save the course
// update current & next weeks timetable
val threads = listOf(
- TCoRAPIController.getTimetable(cCourse.courseName, 0, context),
- TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
+ CacheController.updateTimetable(cCourse.courseName, 0, context),
+ CacheController.updateTimetable(cCourse.courseName, 1, context)
)
threads.joinAll() // blocking since we want the new data
- CacheController.readTimetable(cCourse.courseName, 0, context)
- CacheController.readTimetable(cCourse.courseName, 1, context)
-
withContext(Dispatchers.Main) {
- dialog.dismiss()
- txtView_Course.text = cCourse.courseName // update txtView
+ loadingDialog.dismiss()
}
}
}
- .show()
}
}
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/TimeTableFragment.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/TimeTableFragment.kt
index 66b9148..ce1736d 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/TimeTableFragment.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/TimeTableFragment.kt
@@ -28,17 +28,17 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ScrollView
import androidx.fragment.app.Fragment
-import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.android.synthetic.main.fragment_timetable.*
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.R
-import org.mosad.seil0.projectlaogai.controller.CacheController
-import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.timetables
-import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
-import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
-import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar
+import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
+import org.mosad.seil0.projectlaogai.controller.cache.TimetableController.Companion.timetable
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
+import org.mosad.seil0.projectlaogai.uicomponents.dialogs.AddSubjectDialog
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
+import org.mosad.seil0.projectlaogai.uicomponents.TextViewInfo
+import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
/**
* The timetable controller class
@@ -47,55 +47,57 @@ import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
class TimeTableFragment : Fragment() {
private lateinit var scrollViewTimetable: ScrollView
- private lateinit var faBtnAddLesson: FloatingActionButton
+ private lateinit var faBtnAddSubject: FloatingActionButton
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ return inflater.inflate(R.layout.fragment_timetable, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
- val view: View = inflater.inflate(R.layout.fragment_timetable, container, false)
scrollViewTimetable = view.findViewById(R.id.scrollView_Timetable)
- faBtnAddLesson = view.findViewById(R.id.faBtnAddLesson)
+ faBtnAddSubject = view.findViewById(R.id.faBtnAddSubject)
+ refreshLayout_Timetable.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
- // init actions
- initActions()
+ initActions() // init actions
- if (timetables[0].timetable.days.isNotEmpty() && timetables[1].timetable.days.isNotEmpty()) {
- addInitWeeks()
+ if (timetable.size > 1 && timetable[0].days.isNotEmpty() && timetable[1].days.isNotEmpty()) {
+ initTimetable()
} else {
- MaterialDialog(context!!)
- .title(R.string.error)
- .message(R.string.timetable_error)
- .show()
+ val txtViewInfo = TextViewInfo(context!!).set {
+ txt = resources.getString(R.string.timetable_generic_error)
+ }.showImage()
+ linLayout_Timetable.addView(txtViewInfo)
}
-
- return view
}
/**
* initialize the actions
*/
- private fun initActions() = GlobalScope.launch(Dispatchers.Default) {
+ private fun initActions() = GlobalScope.launch(Dispatchers.Main) {
- withContext(Dispatchers.Main) {
- refreshLayout_Timetable.setOnRefreshListener {
- updateTimetableScreen()
+ refreshLayout_Timetable.setOnRefreshListener {
+ runBlocking { TimetableController.update(context!!).joinAll() }
+ reloadTimetableUI()
+ }
+
+ // show the AddLessonDialog if the ftaBtn is clicked
+ faBtnAddSubject.setOnClickListener {
+ AddSubjectDialog(context!!)
+ .positiveButton {
+ TimetableController.addSubject(selectedCourse, selectedSubject, context)
+ runBlocking { reloadTimetableUI() }
+ }.show()
+ }
+
+ // hide the btnCardValue if the user is scrolling down
+ scrollViewTimetable.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
+ if (scrollY > oldScrollY) {
+ faBtnAddSubject.hide()
+ } else {
+ faBtnAddSubject.show()
}
-
- faBtnAddLesson.setOnClickListener {
- MaterialDialog(context!!)
- .title(text = resources.getString(R.string.add_lesson))
- .message(text = "wähle einen Studiengang aus:\n\nWähle eine Vorlesung aus: \n\n Diese Funktion ist noch nicht verfügbar")
- .show()
- }
-
- // hide the btnCardValue if the user is scrolling down
- scrollViewTimetable.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
- if (scrollY > oldScrollY) {
- faBtnAddLesson.hide()
- } else {
- faBtnAddLesson.show()
- }
- }
-
}
}
@@ -103,57 +105,43 @@ class TimeTableFragment : Fragment() {
/**
* add the current and next weeks lessons
*/
- private fun addInitWeeks() = GlobalScope.launch(Dispatchers.Default) {
+ private fun initTimetable() = GlobalScope.launch(Dispatchers.Default) {
val currentDayIndex = NotRetardedCalendar.getDayOfWeekIndex()
- addWeek(currentDayIndex, 5, 0).join() // add current week
- addWeek(0, currentDayIndex - 1, 1) // add next week
+ addTimetableWeek(currentDayIndex, 5, 0).join() // add current week
+ addTimetableWeek(0, currentDayIndex - 1, 1) // add next week
}
- private fun addWeek(startDayIndex: Int, dayEndIndex: Int, weekIndex: Int) = GlobalScope.launch(Dispatchers.Default) {
- val timetable = timetables[weekIndex].timetable
- val timetableMeta = timetables[weekIndex].meta
+ private fun addTimetableWeek(dayBegin: Int, dayEnd: Int, week: Int) = GlobalScope.launch(Dispatchers.Main) {
+ for (dayIndex in dayBegin..dayEnd) {
+ val dayCardView = DayCardView(context!!)
- withContext(Dispatchers.Main) {
+ // some wired calendar magic, calculate the correct date to be shown
+ // ((timetable week - current week * 7) + (dayIndex - dayIndex of current week)
+ val daysToAdd = ((timetable[week].weekNumberYear - NotRetardedCalendar.getWeekOfYear())
+ * 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex()))
+ dayCardView.addTimetableDay(timetable[week].days[dayIndex], daysToAdd)
- for (dayIndex in startDayIndex..dayEndIndex) {
- val dayCardView = DayCardView(context!!)
+ // if there are no lessons don't show the dayCardView
+ if (dayCardView.getLinLayoutDay().childCount > 1)
+ linLayout_Timetable.addView(dayCardView)
- // some wired calendar magic, calculate the correct date to be shown ((timetable week - current week * 7) + (dayIndex - dayIndex of current week)
- val daysToAdd =(timetableMeta.weekNumberYear - NotRetardedCalendar.getWeekOfYear()) * 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex())
- dayCardView.addTimetableDay(timetable.days[dayIndex], daysToAdd)
-
- // if there are no lessons don't show the dayCardView
- if (dayCardView.getLinLayoutDay().childCount > 1)
- linLayout_Timetable.addView(dayCardView)
-
- }
}
}
- private fun updateTimetableScreen() = GlobalScope.launch(Dispatchers.Default) {
- // update the cache
- val threads = listOf(
- TCoRAPIController.getTimetable(cCourse.courseName, 0, context!!),
- TCoRAPIController.getTimetable(cCourse.courseName, 1, context!!)
- )
- threads.joinAll() // blocking since we want the new data
-
- CacheController.readTimetable(cCourse.courseName, 0, context!!)
- CacheController.readTimetable(cCourse.courseName, 1, context!!)
-
+ /**
+ * clear linLayout_Timetable, add the updated timetable
+ */
+ private fun reloadTimetableUI() = GlobalScope.launch(Dispatchers.Default) {
withContext(Dispatchers.Main) {
- // remove all menus from the layout
+ // remove all lessons from the layout
linLayout_Timetable.removeAllViews()
// add the refreshed timetables
val dayIndex = NotRetardedCalendar.getDayOfWeekIndex()
- // add current week
- addWeek(dayIndex, 5, 0).join()
-
- // add next week
- addWeek(0, dayIndex - 1, 1)
+ addTimetableWeek(dayIndex, 5, 0).join() // add current week
+ addTimetableWeek(0, dayIndex - 1, 1) // add next week
refreshLayout_Timetable.isRefreshing = false
}
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/onboarding/ViewPagerAdapter.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/onboarding/ViewPagerAdapter.kt
new file mode 100644
index 0000000..ed99643
--- /dev/null
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/onboarding/ViewPagerAdapter.kt
@@ -0,0 +1,59 @@
+/**
+ * ProjectLaogai
+ *
+ * Copyright 2019-2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ */
+
+package org.mosad.seil0.projectlaogai.onboarding
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.viewpager.widget.PagerAdapter
+import org.mosad.seil0.projectlaogai.OnboardingActivity.Companion.layouts
+
+/**
+ * a PagerAdapter
+ */
+class ViewPagerAdapter(cont: Context) : PagerAdapter() {
+
+ private val context = cont
+
+ override fun isViewFromObject(view: View, `object`: Any): Boolean {
+ return view === `object`
+ }
+
+ override fun getCount(): Int {
+ return layouts.size
+ }
+
+ override fun instantiateItem(container: ViewGroup, position: Int): Any {
+ val view = LayoutInflater.from(context).inflate(layouts[position], container, false)
+ container.addView(view)
+
+ return view
+ }
+
+ override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
+ val view = `object` as View
+ container.removeView(view)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/DayCardView.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/DayCardView.kt
index f1c23c8..8c1cec5 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/DayCardView.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/DayCardView.kt
@@ -28,8 +28,8 @@ import android.widget.LinearLayout
import androidx.cardview.widget.CardView
import kotlinx.android.synthetic.main.cardview_day.view.*
import org.mosad.seil0.projectlaogai.R
-import org.mosad.seil0.projectlaogai.hsoparser.DataTypes
-import org.mosad.seil0.projectlaogai.hsoparser.TimetableDay
+import org.mosad.seil0.projectlaogai.util.DataTypes
+import org.mosad.seil0.projectlaogai.util.TimetableDay
import java.text.SimpleDateFormat
import java.util.*
@@ -65,13 +65,12 @@ class DayCardView(context: Context) : CardView(context) {
txtView_DayHeading.text = formatter.format(cal.time)
// for every timeslot of that timetable
- for ((tsIndex, timeslot) in timetable.timeslots.withIndex()) {
-
- for (lesson in timeslot) {
+ timetable.timeslots.forEachIndexed { tsIndex, timeslot ->
+ timeslot.forEach { lesson ->
if (lesson.lessonSubject.isNotEmpty()) {
val lessonLayout = LessonLinearLayout(context)
- lessonLayout.setLesson(lesson, DataTypes().getTime()[tsIndex])
+ lessonLayout.setLesson(lesson, DataTypes().times[tsIndex])
linLayout_Day.addView(lessonLayout)
if (lesson != timeslot.last()) {
@@ -81,6 +80,7 @@ class DayCardView(context: Context) : CardView(context) {
lastLesson = lessonLayout
}
}
+
}
lastLesson.disableDivider() // disable the divider for the last lesson of the day
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/GradeLinearLayout.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/GradeLinearLayout.kt
new file mode 100644
index 0000000..c308173
--- /dev/null
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/GradeLinearLayout.kt
@@ -0,0 +1,58 @@
+/**
+ * ProjectLaogai
+ *
+ * Copyright 2019-2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ */
+
+package org.mosad.seil0.projectlaogai.uicomponents
+
+import android.content.Context
+import android.view.View
+import android.widget.LinearLayout
+import kotlinx.android.synthetic.main.linearlayout_grade.view.*
+import org.mosad.seil0.projectlaogai.R
+
+class GradeLinearLayout(context: Context?): LinearLayout(context) {
+
+ var subjectName = ""
+ var grade = ""
+ var subSubjectName = ""
+ var subGrade = ""
+
+ init {
+ inflate(context, R.layout.linearlayout_grade, this)
+ }
+
+ fun set(func: GradeLinearLayout.() -> Unit): GradeLinearLayout = apply {
+ func()
+
+ txtView_subject.text = subjectName
+ txtView_grade.text = grade
+ txtView_sub_subject.text = subSubjectName
+ txtView_sub_grade.text = subGrade
+ }
+
+ fun disableDivider() {
+ divider_grade.visibility = View.GONE
+ }
+
+ fun disableSubSubject() {
+ linLayout_sub_subject.visibility = View.GONE
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/LessonLinearLayout.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/LessonLinearLayout.kt
index fbb92ba..2fc198d 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/LessonLinearLayout.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/LessonLinearLayout.kt
@@ -25,15 +25,14 @@ package org.mosad.seil0.projectlaogai.uicomponents
import android.content.Context
import android.view.View
import android.widget.LinearLayout
-import androidx.cardview.widget.CardView
import kotlinx.android.synthetic.main.linearlayout_lesson.view.*
import org.mosad.seil0.projectlaogai.R
-import org.mosad.seil0.projectlaogai.hsoparser.Lesson
+import org.mosad.seil0.projectlaogai.util.Lesson
class LessonLinearLayout(context: Context?) : LinearLayout(context) {
init {
- CardView.inflate(context, R.layout.linearlayout_lesson, this)
+ inflate(context, R.layout.linearlayout_lesson, this)
}
fun setLesson(lesson: Lesson, time: String) {
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/MealLinearLayout.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/MealLinearLayout.kt
index 3fea220..eb26bcd 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/MealLinearLayout.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/MealLinearLayout.kt
@@ -25,15 +25,14 @@ package org.mosad.seil0.projectlaogai.uicomponents
import android.content.Context
import android.view.View
import android.widget.LinearLayout
-import androidx.cardview.widget.CardView
import kotlinx.android.synthetic.main.linearlayout_meal.view.*
import org.mosad.seil0.projectlaogai.R
-import org.mosad.seil0.projectlaogai.hsoparser.Meal
+import org.mosad.seil0.projectlaogai.util.Meal
class MealLinearLayout(context: Context?): LinearLayout(context) {
init {
- CardView.inflate(context, R.layout.linearlayout_meal, this)
+ inflate(context, R.layout.linearlayout_meal, this)
}
fun setMeal(meal: Meal) {
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/TextViewInfo.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/TextViewInfo.kt
new file mode 100644
index 0000000..f62c1ee
--- /dev/null
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/TextViewInfo.kt
@@ -0,0 +1,63 @@
+/**
+ * ProjectLaogai
+ *
+ * Copyright 2019-2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ */
+
+package org.mosad.seil0.projectlaogai.uicomponents
+
+import android.content.Context
+import android.graphics.Rect
+import android.view.View
+import android.widget.LinearLayout
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.core.content.res.ResourcesCompat
+import org.mosad.seil0.projectlaogai.R
+
+
+class TextViewInfo(context: Context?): AppCompatTextView(context!!) {
+
+ var txt = ""
+ var txtSize = 15F
+ var txtAlignment = View.TEXT_ALIGNMENT_CENTER
+ var drawable = R.drawable.ic_error_outline_black_24dp
+ var params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
+
+ init {
+ params.setMargins(0,200,0,0)
+
+ }
+
+ fun set(func: TextViewInfo.() -> Unit): TextViewInfo = apply {
+ func()
+
+ text = txt
+ layoutParams = params
+ textSize = txtSize
+ textAlignment = txtAlignment
+ }
+
+ fun showImage(): TextViewInfo = apply {
+ val img = ResourcesCompat.getDrawable(resources, drawable, context.theme)?.apply {
+ bounds = Rect(0, 0, 75, 75)
+ }
+ setCompoundDrawables(null, null, null, img)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/dialogs/AddSubjectDialog.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/dialogs/AddSubjectDialog.kt
new file mode 100644
index 0000000..e43ebb1
--- /dev/null
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/dialogs/AddSubjectDialog.kt
@@ -0,0 +1,164 @@
+/**
+ * ProjectLaogai
+ *
+ * Copyright 2019-2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ */
+
+package org.mosad.seil0.projectlaogai.uicomponents.dialogs
+
+import android.content.Context
+import android.os.Build
+import android.view.View
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Spinner
+import com.afollestad.materialdialogs.MaterialDialog
+import com.afollestad.materialdialogs.WhichButton
+import com.afollestad.materialdialogs.actions.getActionButton
+import com.afollestad.materialdialogs.bottomsheets.BottomSheet
+import com.afollestad.materialdialogs.bottomsheets.setPeekHeight
+import com.afollestad.materialdialogs.customview.customView
+import com.afollestad.materialdialogs.customview.getCustomView
+import kotlinx.coroutines.runBlocking
+import org.mosad.seil0.projectlaogai.R
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
+import org.mosad.seil0.projectlaogai.controller.cache.CacheController
+import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
+import org.mosad.seil0.projectlaogai.util.Course
+import java.util.stream.Collectors
+
+/**
+ * This class creates a new AddLessonDialog.
+ */
+class AddSubjectDialog(val context: Context) {
+
+ private val dialog = MaterialDialog(context, BottomSheet())
+
+ private val spinnerCourses: Spinner
+ private val spinnerSubjects: Spinner
+
+ private val subjectsList = ArrayList()
+ private val courseNamesList = getCourseNames()
+
+ var selectedCourse = ""
+ var selectedSubject = ""
+
+ init {
+ dialog.title(R.string.add_lesson)
+ .message(R.string.add_lesson_desc)
+ .customView(R.layout.dialog_add_lesson)
+ .positiveButton(R.string.add)
+ .negativeButton(R.string.cancel)
+ .setPeekHeight(900)
+
+ spinnerCourses = dialog.getCustomView().findViewById(R.id.spinner_Courses)
+ spinnerSubjects = dialog.getCustomView().findViewById(R.id.spinner_Lessons)
+
+ // fix not working accent color
+ dialog.getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.cColorAccent)
+ dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.cColorAccent)
+
+ initSpinners()
+ }
+
+ fun positiveButton(func: AddSubjectDialog.() -> Unit): AddSubjectDialog = apply {
+ dialog.positiveButton {
+ func()
+ }
+ }
+
+ fun show() {
+ dialog.show()
+ }
+
+ fun show(func: AddSubjectDialog.() -> Unit): AddSubjectDialog = apply {
+ func()
+ this.show()
+ }
+
+ private fun initSpinners() {
+ setArrayAdapter(spinnerCourses, courseNamesList)
+ val lessonsAdapter = setArrayAdapter(spinnerSubjects, subjectsList)
+
+ // don't call onItemSelected() on spinnerCourses.onItemSelectedListener
+ spinnerCourses.setSelection(0,false)
+ spinnerCourses.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>, view: View, pos: Int, id: Long) {
+ selectedCourse = parent.getItemAtPosition(pos).toString()
+
+ // TODO show loading dialog while loading
+ val lessonSubjects = runBlocking {
+ TCoRAPIController.getSubjectListAsync(parent.getItemAtPosition(pos).toString(), 0).await()
+ }
+
+ lessonsAdapter.clear()
+ lessonsAdapter.addAll(lessonSubjects)
+ lessonsAdapter.notifyDataSetChanged()
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>) {
+ // Another interface callback
+ }
+ }
+
+ // don't call onItemSelected() on spinnerCourses.onItemSelectedListener
+ spinnerSubjects.setSelection(0,false)
+ spinnerSubjects.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>, view: View, pos: Int, id: Long) {
+ selectedSubject = parent.getItemAtPosition(pos).toString()
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>) {
+ // Another interface callback
+ }
+ }
+ }
+
+ /**
+ * set a new ArrayAdapter for a spinner with a list
+ * @param spinner the spinner you wish to set the adapter for
+ * @param list the list to set the adapter to
+ */
+ private fun setArrayAdapter(spinner: Spinner, list: List): ArrayAdapter {
+ return ArrayAdapter(context, android.R.layout.simple_spinner_item, list)
+ .also { adapter ->
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ spinner.adapter = adapter
+ }
+ }
+
+ /**
+ * get all course names from coursesList
+ * @return a list, containing all course names
+ */
+ private fun getCourseNames(): List {
+ val coursesNameList: List
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ coursesNameList = CacheController.coursesList.stream().map(Course::courseName).collect(
+ Collectors.toList())
+ } else {
+ coursesNameList = ArrayList()
+ CacheController.coursesList.forEach { course ->
+ coursesNameList.add(course.courseName)
+ }
+ }
+
+ return coursesNameList
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/dialogs/LoginDialog.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/dialogs/LoginDialog.kt
new file mode 100644
index 0000000..7300327
--- /dev/null
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/dialogs/LoginDialog.kt
@@ -0,0 +1,87 @@
+/**
+ * ProjectLaogai
+ *
+ * Copyright 2019-2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ */
+
+package org.mosad.seil0.projectlaogai.uicomponents.dialogs
+
+import android.content.Context
+import android.widget.EditText
+import com.afollestad.materialdialogs.MaterialDialog
+import com.afollestad.materialdialogs.WhichButton
+import com.afollestad.materialdialogs.actions.getActionButton
+import com.afollestad.materialdialogs.bottomsheets.BottomSheet
+import com.afollestad.materialdialogs.bottomsheets.setPeekHeight
+import com.afollestad.materialdialogs.customview.customView
+import com.afollestad.materialdialogs.customview.getCustomView
+import org.mosad.seil0.projectlaogai.R
+import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
+
+class LoginDialog(val context: Context) {
+
+ private val dialog = MaterialDialog(context, BottomSheet())
+
+ private val editTextEmail: EditText
+ private val editTextPassword: EditText
+
+ var email = ""
+ var password = ""
+
+ init {
+ dialog.title(R.string.login_heading)
+ .message(R.string.login_desc_on)
+ .customView(R.layout.dialog_login)
+ .positiveButton(R.string.save)
+ .negativeButton(R.string.cancel)
+ .setPeekHeight(900)
+
+ editTextEmail = dialog.getCustomView().findViewById(R.id.editText_email)
+ editTextPassword = dialog.getCustomView().findViewById(R.id.editText_password)
+
+ // fix not working accent color
+ dialog.getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.cColorAccent)
+ dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.cColorAccent)
+ }
+
+ fun positiveButton(func: LoginDialog.() -> Unit): LoginDialog = apply {
+ dialog.positiveButton {
+ email = editTextEmail.text.toString()
+ password = editTextPassword.text.toString()
+
+ func()
+ }
+ }
+
+ fun negativeButton(func: LoginDialog.() -> Unit): LoginDialog = apply {
+ dialog.negativeButton {
+ func()
+ }
+ }
+
+ fun show(func: LoginDialog.() -> Unit): LoginDialog = apply {
+ func()
+
+ editTextEmail.setText(email)
+ editTextPassword.setText(password)
+
+ dialog.show()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/hsoparser/DataTypes.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/util/DataTypes.kt
similarity index 65%
rename from app/src/main/java/org/mosad/seil0/projectlaogai/hsoparser/DataTypes.kt
rename to app/src/main/java/org/mosad/seil0/projectlaogai/util/DataTypes.kt
index f1c23f5..23ad117 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/hsoparser/DataTypes.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/util/DataTypes.kt
@@ -20,15 +20,13 @@
*
*/
-package org.mosad.seil0.projectlaogai.hsoparser
+package org.mosad.seil0.projectlaogai.util
import android.graphics.Color
-import java.util.*
import kotlin.collections.ArrayList
class DataTypes {
- val times =
- arrayOf("8.00 - 9.30", "9.45 - 11.15", "11.35 - 13.05", "14.00 -15.30", "15.45 - 17.15", "17.30 - 19.00")
+ val times = arrayOf("8.00 - 9.30", "9.45 - 11.15", "11.35 - 13.05", "14.00 -15.30", "15.45 - 17.15", "17.30 - 19.00")
val primaryColors = intArrayOf(
Color.parseColor("#E53935"),
@@ -60,7 +58,7 @@ class DataTypes {
Color.parseColor("#3F51B5"),
Color.parseColor("#3D5AFE"),
Color.parseColor("#2979FF"),
- Color.parseColor("#00B0FF"),
+ Color.parseColor("#0096FF"),
Color.parseColor("#00E5FF"),
Color.parseColor("#1DE9B6"),
Color.parseColor("#00E676"),
@@ -72,58 +70,20 @@ class DataTypes {
Color.parseColor("#000000")
)
- init {
- // do something
- }
-
- fun getTime(): Array {
- return times
- }
-
-}
-
-class NotRetardedCalendar {
- companion object {
- private val calendar = Calendar.getInstance()
-
- fun getDayOfWeekIndex(): Int {
- return when (calendar.get(Calendar.DAY_OF_WEEK)) {
- Calendar.MONDAY -> 0
- Calendar.TUESDAY -> 1
- Calendar.WEDNESDAY -> 2
- Calendar.THURSDAY -> 3
- Calendar.FRIDAY -> 4
- Calendar.SATURDAY -> 5
- Calendar.SUNDAY -> 6
- else -> 7
- }
- }
-
- fun getTomorrowWeekIndex(): Int {
- return when (calendar.get(Calendar.DAY_OF_WEEK)) {
- Calendar.MONDAY -> 1
- Calendar.TUESDAY -> 2
- Calendar.WEDNESDAY -> 3
- Calendar.THURSDAY -> 4
- Calendar.FRIDAY -> 5
- Calendar.SATURDAY -> 6
- Calendar.SUNDAY -> 0
- else -> 7
- }
- }
-
- fun getWeekOfYear(): Int {
- return when (calendar.get(Calendar.DAY_OF_WEEK)) {
- Calendar.SUNDAY -> Calendar.getInstance().get(Calendar.WEEK_OF_YEAR) - 1
- else -> Calendar.getInstance().get(Calendar.WEEK_OF_YEAR)
- }
- }
+ enum class Theme(var string: String) {
+ Light("Light"),
+ Dark("Dark"),
+ Black("Black")
}
}
// data classes for the course part
data class Course(val courseLink: String, val courseName: String)
+data class CoursesMeta(val updateTime: Long = 0, val totalCourses: Int = 0)
+
+data class CoursesList(val meta: CoursesMeta = CoursesMeta(), val courses: ArrayList = ArrayList())
+
// data classes for the Mensa part
data class Meal(val day: String, val heading: String, val parts: ArrayList, val additives: String)
@@ -137,16 +97,23 @@ data class MensaMenu(val meta: MensaMeta = MensaMeta(), val currentWeek: MensaWe
// data classes for the timetable part
data class Lesson(
+ val lessonID: String,
val lessonSubject: String,
val lessonTeacher: String,
val lessonRoom: String,
val lessonRemark: String
)
-data class TimetableDay(val timeslots: Array> = Array(6) { ArrayList() })
+data class TimetableDay(val timeslots: Array> = Array(6) { ArrayList() })
-data class TimetableWeek(val days: Array = Array(6) { TimetableDay() })
+data class TimetableWeek(val weekIndex: Int = 0, val weekNumberYear: Int = 0, val days: Array = Array(6) { TimetableDay() })
+
+// data classes for the qispos part
+data class GradeSubject(val id: String = "", val name: String = "", val semester: String = "", val grade: String = "", val credits: String = "")
+
+// TCoR
+data class TimetableWeekTCoR(val days: Array = Array(6) { TimetableDay() })
data class TimetableCourseMeta(val updateTime: Long = 0, val courseName: String = "", val weekIndex: Int = 0, val weekNumberYear: Int = 0, val link: String = "")
-data class TimetableCourseWeek(val meta: TimetableCourseMeta = TimetableCourseMeta(), var timetable: TimetableWeek = TimetableWeek())
+data class TimetableCourseWeek(val meta: TimetableCourseMeta = TimetableCourseMeta(), var timetable: TimetableWeekTCoR = TimetableWeekTCoR())
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/util/NotRetardedCalendar.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/util/NotRetardedCalendar.kt
new file mode 100644
index 0000000..651f2b5
--- /dev/null
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/util/NotRetardedCalendar.kt
@@ -0,0 +1,64 @@
+/**
+ * ProjectLaogai
+ *
+ * Copyright 2019-2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ */
+
+package org.mosad.seil0.projectlaogai.util
+
+import java.util.*
+
+class NotRetardedCalendar {
+ companion object {
+ private val calendar = Calendar.getInstance()
+
+ fun getDayOfWeekIndex(): Int {
+ return when (calendar.get(Calendar.DAY_OF_WEEK)) {
+ Calendar.MONDAY -> 0
+ Calendar.TUESDAY -> 1
+ Calendar.WEDNESDAY -> 2
+ Calendar.THURSDAY -> 3
+ Calendar.FRIDAY -> 4
+ Calendar.SATURDAY -> 5
+ Calendar.SUNDAY -> 6
+ else -> 7
+ }
+ }
+
+ fun getTomorrowWeekIndex(): Int {
+ return when (calendar.get(Calendar.DAY_OF_WEEK)) {
+ Calendar.MONDAY -> 1
+ Calendar.TUESDAY -> 2
+ Calendar.WEDNESDAY -> 3
+ Calendar.THURSDAY -> 4
+ Calendar.FRIDAY -> 5
+ Calendar.SATURDAY -> 6
+ Calendar.SUNDAY -> 0
+ else -> 7
+ }
+ }
+
+ fun getWeekOfYear(): Int {
+ return when (calendar.get(Calendar.DAY_OF_WEEK)) {
+ Calendar.SUNDAY -> Calendar.getInstance().get(Calendar.WEEK_OF_YEAR) - 1
+ else -> Calendar.getInstance().get(Calendar.WEEK_OF_YEAR)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/background_splash.xml b/app/src/main/res/drawable/background_splash.xml
index 9de6881..5924faa 100644
--- a/app/src/main/res/drawable/background_splash.xml
+++ b/app/src/main/res/drawable/background_splash.xml
@@ -1,12 +1,12 @@
-
+
-
+ android:src="@drawable/ic_splash_logo"/>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_add_black_24dp.xml b/app/src/main/res/drawable/ic_add_black_24dp.xml
new file mode 100644
index 0000000..be33fe4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_error_outline_black_24dp.xml b/app/src/main/res/drawable/ic_error_outline_black_24dp.xml
new file mode 100644
index 0000000..843f0a8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_error_outline_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_grading_black_24dp.xml b/app/src/main/res/drawable/ic_grading_black_24dp.xml
new file mode 100644
index 0000000..66f0b63
--- /dev/null
+++ b/app/src/main/res/drawable/ic_grading_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_menu_send.xml b/app/src/main/res/drawable/ic_menu_send.xml
deleted file mode 100644
index 9745066..0000000
--- a/app/src/main/res/drawable/ic_menu_send.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_menu_share.xml b/app/src/main/res/drawable/ic_menu_share.xml
deleted file mode 100644
index b3e39e2..0000000
--- a/app/src/main/res/drawable/ic_menu_share.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_settings_black_24dp.xml b/app/src/main/res/drawable/ic_settings_black_24dp.xml
index 0aafab0..e2a8f5f 100644
--- a/app/src/main/res/drawable/ic_settings_black_24dp.xml
+++ b/app/src/main/res/drawable/ic_settings_black_24dp.xml
@@ -1,9 +1,10 @@
-
+ android:height="24dp"
+ android:autoMirrored="true"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+
diff --git a/app/src/main/res/drawable/ic_splash_logo.png b/app/src/main/res/drawable/ic_splash_logo.png
new file mode 100644
index 0000000..ca96108
Binary files /dev/null and b/app/src/main/res/drawable/ic_splash_logo.png differ
diff --git a/app/src/main/res/drawable/side_nav_bar.xml b/app/src/main/res/drawable/side_nav_bar.xml
deleted file mode 100644
index 7f5aeda..0000000
--- a/app/src/main/res/drawable/side_nav_bar.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layouts/activities/layout/activity_onboarding.xml b/app/src/main/res/layouts/activities/layout/activity_onboarding.xml
new file mode 100644
index 0000000..ac637e9
--- /dev/null
+++ b/app/src/main/res/layouts/activities/layout/activity_onboarding.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layouts/activities/layout/dialog_login.xml b/app/src/main/res/layouts/activities/layout/dialog_login.xml
new file mode 100644
index 0000000..0c1b84c
--- /dev/null
+++ b/app/src/main/res/layouts/activities/layout/dialog_login.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layouts/activities/layout/linearlayout_grade.xml b/app/src/main/res/layouts/activities/layout/linearlayout_grade.xml
new file mode 100644
index 0000000..ccf4c5d
--- /dev/null
+++ b/app/src/main/res/layouts/activities/layout/linearlayout_grade.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layouts/activities/layout/linearlayout_lesson.xml b/app/src/main/res/layouts/activities/layout/linearlayout_lesson.xml
index 591ce3f..9b73dde 100644
--- a/app/src/main/res/layouts/activities/layout/linearlayout_lesson.xml
+++ b/app/src/main/res/layouts/activities/layout/linearlayout_lesson.xml
@@ -28,7 +28,7 @@
android:layout_weight="1"
android:gravity="end"
android:text="@string/a_time"
- android:textColor="@color/textSecondary" />
+ android:textColor="@color/textSecondaryLight" />
-
+ android:orientation="vertical"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark">
+ android:paddingTop="@dimen/nav_header_vertical_spacing"
+ app:srcCompat="@mipmap/ic_laogai_icon" />
+ android:textAppearance="@style/TextAppearance.AppCompat.Body1"
+ android:textColor="#FFFFFF" />
+ android:textColor="#ffffff" />
diff --git a/app/src/main/res/layouts/dialogs/layout/dialog_add_lesson.xml b/app/src/main/res/layouts/dialogs/layout/dialog_add_lesson.xml
new file mode 100644
index 0000000..b058e18
--- /dev/null
+++ b/app/src/main/res/layouts/dialogs/layout/dialog_add_lesson.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layouts/dialogs/layout/dialog_mensa_credit.xml b/app/src/main/res/layouts/dialogs/layout/dialog_mensa_credit.xml
index 9eeee12..60b973f 100644
--- a/app/src/main/res/layouts/dialogs/layout/dialog_mensa_credit.xml
+++ b/app/src/main/res/layouts/dialogs/layout/dialog_mensa_credit.xml
@@ -20,7 +20,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:layout_marginBottom="7dp"
- android:text="16,95 €"
+ android:text="@string/mensa_current"
android:textAlignment="center"
android:textSize="20sp" />
diff --git a/app/src/main/res/layouts/fragments/layout/fragment_grades.xml b/app/src/main/res/layouts/fragments/layout/fragment_grades.xml
new file mode 100644
index 0000000..ae133f4
--- /dev/null
+++ b/app/src/main/res/layouts/fragments/layout/fragment_grades.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layouts/fragments/layout/fragment_mensa.xml b/app/src/main/res/layouts/fragments/layout/fragment_mensa.xml
index 692f063..856d03b 100644
--- a/app/src/main/res/layouts/fragments/layout/fragment_mensa.xml
+++ b/app/src/main/res/layouts/fragments/layout/fragment_mensa.xml
@@ -1,26 +1,27 @@
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".fragments.MensaFragment"
+ android:background="?themePrimary">
+ android:id="@+id/refreshLayout_Mensa"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ android:id="@+id/scrollView_Mensa"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ android:id="@+id/linLayout_Mensa"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:animateLayoutChanges="false"
+ android:orientation="vertical" />
diff --git a/app/src/main/res/layouts/fragments/layout/fragment_on_course.xml b/app/src/main/res/layouts/fragments/layout/fragment_on_course.xml
new file mode 100644
index 0000000..3a69b16
--- /dev/null
+++ b/app/src/main/res/layouts/fragments/layout/fragment_on_course.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layouts/fragments/layout/fragment_on_login.xml b/app/src/main/res/layouts/fragments/layout/fragment_on_login.xml
new file mode 100644
index 0000000..9d8032a
--- /dev/null
+++ b/app/src/main/res/layouts/fragments/layout/fragment_on_login.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layouts/fragments/layout/fragment_on_welcome.xml b/app/src/main/res/layouts/fragments/layout/fragment_on_welcome.xml
new file mode 100644
index 0000000..ec30cde
--- /dev/null
+++ b/app/src/main/res/layouts/fragments/layout/fragment_on_welcome.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layouts/fragments/layout/fragment_settings.xml b/app/src/main/res/layouts/fragments/layout/fragment_settings.xml
index c33c639..afb46be 100644
--- a/app/src/main/res/layouts/fragments/layout/fragment_settings.xml
+++ b/app/src/main/res/layouts/fragments/layout/fragment_settings.xml
@@ -1,280 +1,345 @@
+ 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"
+ android:background="?themePrimary"
+ tools:context=".fragments.SettingsFragment">
+
+
-
+
+
-
+
+
-
+ android:orientation="vertical">
+
+
+
+
+
-
+
+
-
-
+ android:text="@string/user_desc" />
+
+ android:id="@+id/divider2"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?dividerColor" />
+
+
+
+ android:text="@string/sample_course"
+ android:textSize="16sp"
+ android:textStyle="bold" />
+
-
+ android:id="@+id/txtView_CourseDesc"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/course_desc" />
+
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?dividerColor" />
+
+
+
+ android:layout_height="wrap_content"
+ android:text="@string/manage_lessons"
+ android:textSize="16sp"
+ android:textStyle="bold" />
+
-
+ android:id="@+id/txtView_ManageLessonsDesc"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/manage_lessons_desc" />
+
+ android:id="@+id/divider7"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?dividerColor" />
+
+ android:id="@+id/linLayout_About"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="7dp"
+ android:orientation="vertical">
+
+ android:id="@+id/txtView_About"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/about_txtView"
+ android:textSize="16sp"
+ android:textStyle="bold" />
+
+
+
+
+
+
+
+
+
+
+
-
+
+
-
+ android:layout_margin="7dp"
+ android:paddingTop="3dp"
+ android:text="@string/settings"
+ android:textColor="?colorAccent"
+ android:textSize="18sp"
+ android:textStyle="bold"
+ android:typeface="sans" />
+
+ android:id="@+id/linLayout_Theme"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="7dp"
+ android:orientation="vertical">
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:textStyle="bold" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layouts/fragments/layout/fragment_timetable.xml b/app/src/main/res/layouts/fragments/layout/fragment_timetable.xml
index 94e22d4..00de1e1 100644
--- a/app/src/main/res/layouts/fragments/layout/fragment_timetable.xml
+++ b/app/src/main/res/layouts/fragments/layout/fragment_timetable.xml
@@ -1,6 +1,7 @@
+
+ android:id="@+id/faBtnAddSubject"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|center|end"
+ android:layout_marginEnd="11dp"
+ android:layout_marginBottom="11dp"
+ android:clickable="true"
+ android:elevation="7dp"
+ android:focusable="true"
+ android:src="@drawable/ic_add_black_24dp"
+ android:visibility="visible"
+ app:backgroundTint="?colorAccent"
+ app:fabSize="auto" />
\ No newline at end of file
diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml
index f0fdacc..bab9278 100644
--- a/app/src/main/res/menu/activity_main_drawer.xml
+++ b/app/src/main/res/menu/activity_main_drawer.xml
@@ -20,6 +20,10 @@
android:id="@+id/nav_moodle"
android:icon="@drawable/ic_school_black_24dp"
android:title="@string/moodle"/>
+
-
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_laogai_icon.png b/app/src/main/res/mipmap-hdpi/ic_laogai_icon.png
index 51a9b20..ced162e 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_laogai_icon.png and b/app/src/main/res/mipmap-hdpi/ic_laogai_icon.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_laogai_icon_foreground.png b/app/src/main/res/mipmap-hdpi/ic_laogai_icon_foreground.png
new file mode 100644
index 0000000..43e6b18
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_laogai_icon_foreground.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
deleted file mode 100644
index 11bc559..0000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_laogai_icon.png b/app/src/main/res/mipmap-mdpi/ic_laogai_icon.png
index 146265a..2ff86b9 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_laogai_icon.png and b/app/src/main/res/mipmap-mdpi/ic_laogai_icon.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_laogai_icon_foreground.png b/app/src/main/res/mipmap-mdpi/ic_laogai_icon_foreground.png
new file mode 100644
index 0000000..83df42a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_laogai_icon_foreground.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_laogai_icon_splash.png b/app/src/main/res/mipmap-mdpi/ic_laogai_icon_splash.png
deleted file mode 100644
index 4255977..0000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_laogai_icon_splash.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
deleted file mode 100644
index a5db2ba..0000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_laogai_icon.png b/app/src/main/res/mipmap-xhdpi/ic_laogai_icon.png
index 8644cf2..78f5e1e 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_laogai_icon.png and b/app/src/main/res/mipmap-xhdpi/ic_laogai_icon.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_laogai_icon_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_laogai_icon_foreground.png
new file mode 100644
index 0000000..8395710
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_laogai_icon_foreground.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
deleted file mode 100644
index 2b0c162..0000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_laogai_icon.png b/app/src/main/res/mipmap-xxhdpi/ic_laogai_icon.png
index 4255977..ca96108 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_laogai_icon.png and b/app/src/main/res/mipmap-xxhdpi/ic_laogai_icon.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_laogai_icon_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_laogai_icon_foreground.png
new file mode 100644
index 0000000..6161c83
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_laogai_icon_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
deleted file mode 100644
index 4d419b1..0000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_laogai_icon.png b/app/src/main/res/mipmap-xxxhdpi/ic_laogai_icon.png
index 49ccb84..e1f4451 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_laogai_icon.png and b/app/src/main/res/mipmap-xxxhdpi/ic_laogai_icon.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_laogai_icon_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_laogai_icon_foreground.png
new file mode 100644
index 0000000..8f06c7e
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_laogai_icon_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
deleted file mode 100644
index 8cb1cf1..0000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/raw/notenverwaltung_hs_offenburg_de.crt b/app/src/main/res/raw/notenverwaltung_hs_offenburg_de.crt
new file mode 100644
index 0000000..a0b949e
Binary files /dev/null and b/app/src/main/res/raw/notenverwaltung_hs_offenburg_de.crt differ
diff --git a/app/src/main/res/raw/notices.xml b/app/src/main/res/raw/notices.xml
index e05c044..4f1af48 100644
--- a/app/src/main/res/raw/notices.xml
+++ b/app/src/main/res/raw/notices.xml
@@ -3,7 +3,7 @@
Material Dialogs
https://github.com/afollestad/material-dialogs
- Copyright (C) Aidan Follestad
+ Copyright Aidan Follestad
Apache Software License 2.0
@@ -13,11 +13,17 @@
Apache Software License 2.0
- gson
+ Gson
https://github.com/google/gson
Copyright 2008 Google Inc.
Apache Software License 2.0
+
+ Jsoup
+ https://jsoup.org/
+ Copyright 2009 - 2020 Jonathan Hedley
+ MIT License
+
kotlinx.coroutines
https://github.com/Kotlin/kotlinx.coroutines
@@ -25,15 +31,21 @@
Apache Software License 2.0
- farebot part for desfire cards
+ FareBot part for desfire cards
https://github.com/codebutler/farebot
Copyright 2011-2012, 2014, 2016 Eric Butler
GNU General Public License 3.0
- Android Support Libraries
- http://developer.android.com/tools/support-library/index.html
- Copyright (C) 2016 The Android Open Source Project
+ AndroidX
+ https://developer.android.com/jetpack/androidx
+ Copyright 2018 The Android Open Source Project
+ Apache Software License 2.0
+
+
+ Material design icons
+ https://github.com/google/material-design-icons
+ Copyright Google Inc.
Apache Software License 2.0
diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml
index 1a787f2..1786355 100644
--- a/app/src/main/res/values-de-rDE/strings.xml
+++ b/app/src/main/res/values-de-rDE/strings.xml
@@ -8,8 +8,23 @@
Mensa
Stundenplan
Moodle
+ Noten
Einstellungen
+
+ überspringen
+ weiter
+ fertig
+ Willkommen bei Project Laogai.\nBevor wir loslegen können, richte bitte die App ein.
+ los geht\'s
+ Studiengang
+ Wähle deinen aktuellen Studiengang.\nZusätzliche Vorlesungen können später hinzugefügt werden.
+ Login
+ Project Laogai kann auf die Notenverwaltung zugreifen. Deine Login-Daten werden verschlüsselt auf deinem Gerät gespeichert. Diese Funktion wird "as is" und ohne Garantie bereitgestellt.
+ E-Mail
+ Passwort
+ login
+
Essen
Heute, %1$s
@@ -18,13 +33,29 @@
Diese Woche keine weitere Essensausgabe
heute keine Vorlesung!
+
+ Für diese und nächste Woche ist der Speiseplan leer.
+
Eine Vorlesung hinzufügen
+ Füge eine Vorlesung eines anderen Studiengangs zu deinem Stundenplan hinzu.
+ Studiengänge:
+ Vorlesungen:
+ Beim laden des Stundenplans ist ein Fehler aufgetreten.\n
+
+
+ Lade Daten von den Hochschul Servern.\nDas kann eine Weile dauern.
+ Diese Funktion benötigt deine Login-Daten. Bitte logge dich über die Einstellungen ein.
+ Die Notenverwaltung ist zur Zeit nicht ereichbar.\nVersuche es später noch einmal.\n
+ Error.\nVersuche es später noch einmal.\n
+ Alle Angaben ohne Gewähr.
Info
- Benutzer
+ Zum bearbeiten tippen
Tippe um den Kurs zu ändern
+ Bearbeite Zusätzliche Vorlesungen
+ Tippe um zusätzliche Vorlesungen zu bearbeiten
Über
Lizenzen
Design
@@ -32,23 +63,26 @@
Dunkel
Schwarz
Primärfarbe
- Zum Ändern tippen, Standard ist Schwarz.
+ Zum Ändern tippen, Standard ist Blaugrün.
Akzentfarbe
- Zum Ändern tippen, Standard ist Indigo.
+ Zum Ändern tippen, Standard ist Hellblau.
Buffet immer anzeigen
Wähle deinen Studiengang
lade Stundenplan …
+ hinzufügen
+ löschen
+ abbrechen
auswählen
- schließen
+ speichern
Mensa-Guthaben
aktuell: %1$s\n
letzte Abbuchung: %1$s
Fehler
- Stundenplan konnte nicht geladen werden!
+ Der Stundenplan konnte nicht geladen werden.
Mensa
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 92bab79..6bf9997 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,24 +1,18 @@
- #000000
- #000000
- #3f51b5
- #ffffff
-
- #ffffff
- #ffffff
- #000000
- #818181
+ #009688
+ #009688
+ #0096ff
+ #ffffff
#000000
#303030
- #424242
#ffffff
- #e0e0e0
+ #c0c0c0
#232323
- #ffffff
+ #f5f5f5
#ffffff
#000000
#818181
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1a03827..db3abf5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -10,8 +10,23 @@
Mensa
Timetable
Moodle
+ Grades
Settings
+
+ skip
+ next
+ start
+ Welcome to Project Laogai.\nBefore we can start, setup the App.
+ get started
+ Course
+ Select your current course.\nAdditional lessons can be added later.
+ Login
+ Project Laogai can connect to the Qispos Online-Portal. Your Login-Data will be stored encrypted on your device. This feature is provided "as is", without any guarantee.
+ E-Mail
+ Password
+ login
+
Meal
Today, %1$s
@@ -20,47 +35,69 @@
No more Food this week
"No lecture today!"
+
+ The menu is empty for this and the next week.
+
Add a lesson
+ Add a lesson from another course to your timetable.
+ Courses:
+ Lessons:
+ An error occurred while loading the timetable.\n
+
+
+ Loading data from the university servers.\nThis may take a while.
+ This feature needs your Login-Data to work. Please login via the settings.
+ The Qispos server is currently unavailable.\nPlease try again later.\n
+ Error.\nPlease try again later.\n
+ All information is supplied without guarantee.
Info
- User
+ Tap to edit
Tap to change course
- About
- "This software is made by @Seil0 and is published under the terms and conditions of GPL 3. For further information visit \ngit.mosad.xyz/Seil0/ProjectLaogai \n\n© 2018-2020 seil0@mosad.xyz "
+ Manage Additional Lessons
+ Tap to manage additional lessons
hso App by @Seil0
Version %1$s (%2$s)
+ About
+ "This software is made by @Seil0 and is published under the terms and conditions of GPL 3. For further information visit \ngit.mosad.xyz/Seil0/ProjectLaogai \n\n© 2018-2020 seil0@mosad.xyz "
Licenses
Theme
Light
Dark
Black
Primary color
- Tap to change, default is black.
+ Tap to change, default is teal blue.
Accent color
- Tap to change, default is indigo.
+ Tap to change, default is light blue.
Always show buffet
Select your course
loading timetable …
+ add
+ delete
+ @android:string/cancel
select
- close
+ save
Mensa credit
current: %1$s\n
last: %1$s
-
spinefield@stud.hs-offenburg.de
- SampleCourse 3
- Montag, 30.02
+ Everything
+ Earthbending
+ Applied Earthbending
+ 1,0
+ passed
+ Monday, 30.02
0.00 – 23.59
Error
- Could not load timetable!"
+ Could not load the timetable!"
Mensa
@@ -77,6 +114,7 @@
org.mosad.seil0.projectlaogai_preferences
+ org.mosad.seil0.projectlaogai_encrypted_preferences
org.mosad.seil0.projectlaogai.course
org.mosad.seil0.projectlaogai.courseTTLink
org.mosad.seil0.projectlaogai.colorPrimary
@@ -85,5 +123,24 @@
org.mosad.seil0.projectlaogai.coursesCacheTime
org.mosad.seil0.projectlaogai.mensaCacheTime
org.mosad.seil0.projectlaogai.timetableCacheTime
+ org.mosad.seil0.projectlaogai.user_email
+ org.mosad.seil0.projectlaogai.user_password
+
+
+
+
- AI-1
+ - AI-2
+ - AI-3
+ - AI-4
+ - AI-5
+ - AI-6
+
+
+
+ - Mathematik
+ - Verteilte Systemr
+ - EA 1
+ - IT Security
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 8981642..b06f414 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -14,7 +14,9 @@
- @color/themeSecondaryLight
- @color/textPrimaryLight
- @color/textPrimaryLight
+ - @color/textSecondaryLight
- @color/textPrimaryLight
+ - @color/textSecondaryLight
- @color/dividerLight
- @color/themeSecondaryLight
- @color/textPrimaryLight
@@ -27,7 +29,9 @@
- @color/themeSecondaryDark
- @color/textPrimaryDark
- @color/textPrimaryDark
+ - @color/textSecondaryDark
- @color/textPrimaryDark
+ - @color/textSecondaryDark
- @color/dividerDark
- @color/themeSecondaryDark
- @color/textPrimaryDark
@@ -40,17 +44,15 @@
- @color/themePrimaryDark
- @color/textPrimaryDark
- @color/textPrimaryDark
+ - @color/textSecondaryDark
- @color/textPrimaryDark
+ - @color/textSecondaryDark
- @color/dividerDark
- @color/themeSecondaryDark
- @color/textPrimaryDark
- @color/textPrimaryDark
-
diff --git a/build.gradle b/build.gradle
index 81605cb..982f6ef 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,13 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.3.61'
+ ext.kotlin_version = '1.4.0'
repositories {
google()
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.5.3'
+ classpath 'com.android.tools.build:gradle:4.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
diff --git a/etc/drawable_resources/laogai_icon.svg b/etc/drawable_resources/laogai_icon.svg
new file mode 100644
index 0000000..40f4fe9
--- /dev/null
+++ b/etc/drawable_resources/laogai_icon.svg
@@ -0,0 +1,46 @@
+
+
diff --git a/fastlane/metadata/android/de-DE/full_description.txt b/fastlane/metadata/android/de-DE/full_description.txt
index 71eca07..e5ffcee 100644
--- a/fastlane/metadata/android/de-DE/full_description.txt
+++ b/fastlane/metadata/android/de-DE/full_description.txt
@@ -1,8 +1,10 @@
-ProjectLaogai ist eine App um den Vorlesungsplan und den Mensa-Speiseplan der Hochschule Offenburg anzuzeigen.
+ProjectLaogai ist eine App um den Vorlesungsplan, die Notenverwaltung und den Mensa-Speiseplan der Hochschule Offenburg anzuzeigen.
Features:
-* schaue was es diese und nächste Woche in der Mensa zu Essen gibt
-* schaue dir deinen Vorlesungsplan an
-* lass dir das aktuelle Guthaben der Mensakarte anzeigen
+* lass dir deine Noten direkt in der App anzeigen
+* zeige den Stundenplan deines Studiengangs an
+* schau dir den Mensa Speiseplan der aktuellen und der nächsten Woche an
+* schaue dir das aktuelle Guthaben deiner Mensakarte an
* öffne moodle direkt in der App
-* viele lustige Bugs
+
+Bitte melde Fehler und Probleme an support@mosad.xyz
diff --git a/fastlane/metadata/android/en-US/changelogs/6000.txt b/fastlane/metadata/android/en-US/changelogs/6000.txt
new file mode 100644
index 0000000..a9537e3
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/6000.txt
@@ -0,0 +1,9 @@
+Here comes version 0.6.0 "anthropomorphised Athena"
+
+* new: the new Onboarding Process allows you to select your course and login to the qispos system
+* new: it's now possible to access the grades from qispos
+* new: you can now add additinal lessons wich are not part of your cours timetable
+* change: new primary and accent colors to match the mosad.xyz design
+* change: updated some libraries
+* fix: compatibility with tcor version 1.2.2 and higher
+* fix: crash on first start, if tcor is not reachable
diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
index c080ed0..d235b70 100644
--- a/fastlane/metadata/android/en-US/full_description.txt
+++ b/fastlane/metadata/android/en-US/full_description.txt
@@ -1,8 +1,10 @@
-ProjectLaogai is an app to access the timetable and the mensa menu of Hochschule Offenburg.
+ProjectLaogai is an app to access the timetable, grades (qispos) and the canteen menu of Hochschule Offenburg.
Features:
-* check out the mensa menu of this and next week
-* access your timetable
+* have your grades displayed directly in the app
+* show the timetable of your course
+* take a look at the canteen menu for the current and next week
* check the current balance of your mensa card
-* open moodle
-* probably some funny bugs
+* open moodle directly in the app
+
+Please report bugs and issues to support@mosad.xyz
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/ProjectLaogai_Settings.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/ProjectLaogai_Settings.png
index 8ec09ce..3d005f7 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/ProjectLaogai_Settings.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/ProjectLaogai_Settings.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/ProjectLaogai_Settings_dark.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/ProjectLaogai_Settings_dark.png
index c19082d..8adef60 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/ProjectLaogai_Settings_dark.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/ProjectLaogai_Settings_dark.png differ
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 5c2d1cf..62d4c05 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 9492014..6c9a224 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 83f2acf..fbd7c51 100755
--- a/gradlew
+++ b/gradlew
@@ -82,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -129,6 +130,7 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@@ -154,19 +156,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
else
eval `echo args$i`="\"$arg\""
fi
- i=$((i+1))
+ i=`expr $i + 1`
done
case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@@ -175,14 +177,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
-APP_ARGS=$(save "$@")
+APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
- cd "$(dirname "$0")"
-fi
-
exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index 24467a1..a9f778a 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@@ -81,6 +84,7 @@ set CMD_LINE_ARGS=%*
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%