Compare commits
67 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 | |||
6e9c63d3d4 | |||
c343735b57 | |||
9c5274dc06 | |||
b186a2e96e | |||
2cb4b72369 | |||
2d497d1a96 | |||
9d2de3fcb3 | |||
bed3f5d978 | |||
8f5a4dd1b3 | |||
9907d083e9 | |||
d4860b2a32 | |||
5ec2b53bce | |||
701a351e6e | |||
5cad924b26 |
@ -3,7 +3,7 @@ name: default
|
||||
|
||||
steps:
|
||||
- name: assembleRelease
|
||||
image: gradle:jdk8
|
||||
image: nextcloudci/android10:android-56
|
||||
commands:
|
||||
- gradle assembleRelease
|
||||
- ./gradlew assembleRelease
|
||||
|
||||
|
19
README.md
19
README.md
@ -1,16 +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.
|
||||
|
||||
[<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)
|
||||
@ -19,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 [@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
|
||||
|
@ -1,7 +1,5 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
android {
|
||||
@ -12,8 +10,8 @@ android {
|
||||
applicationId "org.mosad.seil0.projectlaogai"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 29
|
||||
versionCode 14
|
||||
versionName "0.5.0"
|
||||
versionCode 6000 // 0006000
|
||||
versionName "0.6.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resValue "string", "build_time", buildTime()
|
||||
setProperty("archivesBaseName", "projectlaogai-$versionName")
|
||||
@ -21,8 +19,8 @@ android {
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
shrinkResources false
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
@ -49,23 +47,27 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation 'org.jetbrains.anko:anko-commons:0.10.8'
|
||||
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-beta2'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
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() {
|
||||
|
9
app/proguard-rules.pro
vendored
9
app/proguard-rules.pro
vendored
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,17 @@
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.app.shortcuts"
|
||||
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
|
||||
|
BIN
app/src/main/ic_laogai_icon-playstore.png
Normal file
BIN
app/src/main/ic_laogai_icon-playstore.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
Before Width: | Height: | Size: 39 KiB |
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019 <seil0@mosad.xyz>
|
||||
* 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
|
||||
@ -30,6 +30,8 @@ import android.nfc.NfcAdapter
|
||||
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
|
||||
@ -42,18 +44,22 @@ 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
|
||||
|
||||
// TODO save the current fragment to show it when the app is restarted
|
||||
/**
|
||||
* TODO save the current fragment to show it when the app is restarted
|
||||
*/
|
||||
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
|
||||
|
||||
private var activeFragment: Fragment = HomeFragment() // the currently active fragment, home at the start
|
||||
private val className = "MainActivity"
|
||||
|
||||
private lateinit var adapter: NfcAdapter
|
||||
private lateinit var pendingIntent: PendingIntent
|
||||
@ -62,6 +68,8 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
private var useNFC = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
|
||||
|
||||
Aesthetic.attach(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
@ -72,11 +80,6 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
initAesthetic()
|
||||
initForegroundDispatch()
|
||||
|
||||
//init home fragment
|
||||
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
|
||||
fragmentTransaction.replace(R.id.fragment_container, activeFragment)
|
||||
fragmentTransaction.commit()
|
||||
|
||||
val toggle = ActionBarDrawerToggle(
|
||||
this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close
|
||||
)
|
||||
@ -85,9 +88,17 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
|
||||
nav_view.setNavigationItemSelectedListener(this)
|
||||
|
||||
// if we get an NFC read intent while the app is closed call readBalance
|
||||
if (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action)
|
||||
NFCMensaCard.readBalance(intent, this)
|
||||
// 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()
|
||||
"org.mosad.seil0.projectlaogai.fragments.TimeTableFragment" -> activeFragment = TimeTableFragment()
|
||||
"org.mosad.seil0.projectlaogai.fragments.MoodleFragment" -> activeFragment = MoodleFragment()
|
||||
}
|
||||
|
||||
// open the activeFragment, default is the HomeFragment
|
||||
fragmentTransaction.replace(R.id.fragment_container, activeFragment)
|
||||
fragmentTransaction.commit()
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
@ -145,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()
|
||||
}
|
||||
@ -163,22 +175,25 @@ 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)
|
||||
}
|
||||
println("startup completed in $startupTime ms")
|
||||
Log.i(className, "startup completed in $startupTime ms")
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -189,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,164 +0,0 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019 <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 com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonParser
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
|
||||
import org.mosad.seil0.projectlaogai.hsoparser.*
|
||||
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 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) {
|
||||
println("update mensa blocking")
|
||||
TCoRAPIController.getMensa(context).get()
|
||||
}
|
||||
|
||||
// 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) {
|
||||
println("updating timetable after sunday!")
|
||||
val jobA = TCoRAPIController.getTimetable(cCourse.courseName, 0, context)
|
||||
val jobB = TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
|
||||
|
||||
jobA.get()
|
||||
jobB.get()
|
||||
}
|
||||
|
||||
// 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())
|
||||
TCoRAPIController.getCoursesList(context).get()
|
||||
|
||||
val fileReader = FileReader(file)
|
||||
val bufferedReader = BufferedReader(fileReader)
|
||||
val coursesObject = JsonParser().parse(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()) {
|
||||
TCoRAPIController.getMensa(context).get()
|
||||
}
|
||||
|
||||
val fileReader = FileReader(file)
|
||||
val bufferedReader = BufferedReader(fileReader)
|
||||
val mensaObject = JsonParser().parse(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())
|
||||
TCoRAPIController.getTimetable(courseName, week, context).get()
|
||||
|
||||
val fileReader = FileReader(file)
|
||||
val bufferedReader = BufferedReader(fileReader)
|
||||
val timetableObject = JsonParser().parse(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)
|
||||
*/
|
||||
fun readStartCache(courseName: String) {
|
||||
readCoursesList(context)
|
||||
readMensa(context)
|
||||
readTimetable(courseName, 0, context)
|
||||
readTimetable(courseName, 1, context)
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019 <seil0@mosad.xyz>
|
||||
* 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
|
||||
@ -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 {
|
||||
@ -66,7 +67,7 @@ class NFCMensaCard {
|
||||
lookAtMe(context, data, settings.value).show()
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Log.i(className,"could not connect to tag", ex)
|
||||
Log.w(className,"could not connect to tag", ex)
|
||||
}
|
||||
|
||||
}
|
||||
@ -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,134 +0,0 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019 <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.jetbrains.anko.defaultSharedPreferences
|
||||
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.defaultSharedPreferences
|
||||
|
||||
// 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
|
||||
with (sharedPref.edit()) {
|
||||
putString(context.getString(R.string.save_key_course), cCourse.courseName)
|
||||
putString(context.getString(R.string.save_key_courseTTLink), cCourse.courseLink)
|
||||
apply()
|
||||
}
|
||||
|
||||
// save the primary color
|
||||
with (sharedPref.edit()) {
|
||||
putInt(context.getString(R.string.save_key_colorPrimary),
|
||||
cColorPrimary
|
||||
)
|
||||
apply()
|
||||
}
|
||||
|
||||
// save the accent color
|
||||
with (sharedPref.edit()) {
|
||||
putInt(context.getString(R.string.save_key_colorAccent),
|
||||
cColorAccent
|
||||
)
|
||||
apply()
|
||||
}
|
||||
|
||||
// save showBuffet
|
||||
with (sharedPref.edit()) {
|
||||
putBoolean(context.getString(R.string.save_key_showBuffet),
|
||||
cShowBuffet
|
||||
)
|
||||
apply()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// the load function
|
||||
fun load(context: Context) {
|
||||
val sharedPref = context.defaultSharedPreferences
|
||||
|
||||
// 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)
|
||||