Compare commits
53 Commits
Author | SHA1 | Date |
---|---|---|
|
28520bee74 | |
|
0c6a486dd9 | |
|
ec15c79b63 | |
|
4592d1984f | |
|
aae74cf9db | |
|
e19b0db39d | |
|
23fd52b0c5 | |
|
62a62049c3 | |
|
849698779b | |
|
a42a92a3c5 | |
|
be30e2f968 | |
|
c629b2aec2 | |
|
99ba87c3f6 | |
|
953b4825a9 | |
|
2807843d25 | |
|
638c321798 | |
|
42d938b0bc | |
|
9d7504bbaf | |
|
8ecfe8f120 | |
|
ff0c4ad1a7 | |
|
ea0caea91e | |
|
1f660bdd37 | |
|
be43d87b1a | |
|
dd5c7b3fb8 | |
|
fb3dab6dc3 | |
|
bcfdb83d14 | |
|
8c55366ec0 | |
|
6f68fbb73c | |
|
297dd77e79 | |
|
6a70f26807 | |
|
f6b00a8d81 | |
|
69ce9fef5a | |
|
2d753851c0 | |
|
6c0624c793 | |
|
7779296345 | |
|
3e2ef0521e | |
|
bdfeb46faf | |
|
18ca435764 | |
|
948f330ebe | |
|
72e9efb9e7 | |
|
bea1b47396 | |
|
34a68ff75d | |
|
1ba3f1fa87 | |
|
48544aef2f | |
|
bc09e33147 | |
|
6ec9b9f5e2 | |
|
a19173b499 | |
|
85dc3181fd | |
|
e46c234b6c | |
|
faa07966da | |
|
ccc0f0f2bc | |
|
e15bf95b85 | |
|
01677c04ef |
|
@ -3,7 +3,7 @@ name: default
|
|||
|
||||
steps:
|
||||
- name: assembleRelease
|
||||
image: nextcloudci/android:android-51
|
||||
image: nextcloudci/android10:android-56
|
||||
commands:
|
||||
- gradle assembleRelease
|
||||
- ./gradlew assembleRelease
|
||||
|
||||
|
|
18
README.md
18
README.md
|
@ -1,17 +1,21 @@
|
|||
[](https://drone.mosad.xyz/Seil0/ProjectLaogai)
|
||||

|
||||
[](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.
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="75">](https://f-droid.org/packages/org.mosad.seil0.projectlaogai/)
|
||||
[<img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" height="75">](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
|
||||
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.png" width=180>](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
|
|||
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.png)
|
||||
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa_dark.png" width=180>](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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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.** { <fields>; }
|
||||
|
||||
#Gson
|
||||
-keepattributes Signature
|
||||
-dontwarn sun.misc.**
|
||||
|
||||
#misc
|
||||
-dontwarn java.lang.instrument.ClassFileTransformer
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,14 @@
|
|||
android:resource="@xml/shortcuts" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".OnboardingActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.Light"
|
||||
android:screenOrientation="portrait"
|
||||
android:launchMode="singleTop">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
Before Width: | Height: | Size: 39 KiB |
|
@ -31,6 +31,7 @@ import android.nfc.NfcManager
|
|||
import android.nfc.tech.NfcA
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
|
@ -43,11 +44,12 @@ import com.afollestad.aesthetic.NavigationViewMode
|
|||
import com.google.android.material.navigation.NavigationView
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.android.synthetic.main.app_bar_main.*
|
||||
import org.mosad.seil0.projectlaogai.controller.CacheController
|
||||
import org.mosad.seil0.projectlaogai.controller.NFCMensaCard
|
||||
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.cache.CacheController
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorAccent
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorPrimary
|
||||
import org.mosad.seil0.projectlaogai.fragments.*
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
|
@ -86,7 +88,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
|||
|
||||
nav_view.setNavigationItemSelectedListener(this)
|
||||
|
||||
// based on the inent we get, call readBalance or open a Fragment
|
||||
// based on the intent we get, call readBalance or open a Fragment
|
||||
when (intent.action) {
|
||||
NfcAdapter.ACTION_TECH_DISCOVERED -> 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() {
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* 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<TextView>
|
||||
|
||||
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) {}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* 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<Course>()
|
||||
var timetables = ArrayList<TimetableCourseWeek>()
|
||||
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<List<Course>>() {}.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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
||||
|
|
|
@ -1,183 +0,0 @@
|
|||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* 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<String, ArrayList<GradeSubject>> {
|
||||
val gradesMap = HashMap<String, ArrayList<GradeSubject>>()
|
||||
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<String>{
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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)
|
||||