Compare commits

...

67 Commits

Author SHA1 Message Date
28520bee74
changelog: minor text fixes
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2020-09-04 11:08:50 +02:00
0c6a486dd9 Merge pull request 'version 0.6.0' (#46) from develop into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #46
2020-08-25 19:24:49 +02:00
ec15c79b63
update fastlane screenshot
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2020-08-25 12:05:22 +02:00
4592d1984f
release version 0.6.0
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-25 11:55:55 +02:00
aae74cf9db „README.md“ ändern
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-25 11:06:38 +02:00
e19b0db39d
add missing licenses to license dialog
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-25 10:52:59 +02:00
23fd52b0c5
fab: use plus as icon
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-24 20:22:46 +02:00
62a62049c3
code clean up
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-23 14:27:41 +02:00
849698779b
add icon as svg
All checks were successful
continuous-integration/drone/push Build is passing
* recreate all launcher icons
2020-08-23 14:06:28 +02:00
a42a92a3c5
update constraintlayout
All checks were successful
continuous-integration/drone/push Build is passing
* constraintlayout 2.0.0.rc1 -> 2.0.0
2020-08-22 20:35:44 +02:00
be30e2f968
enable optimisations and minifying for release builds
All checks were successful
continuous-integration/drone/push Build is passing
* closes #19
* update gradle wrapper to version 6.6
2020-08-22 20:20:23 +02:00
c629b2aec2
fix login button
All checks were successful
continuous-integration/drone/push Build is passing
* fix login button action in Onboarding
* add release changelog
* update description
* use new version code scheme
2020-08-21 19:03:28 +02:00
99ba87c3f6
init fragment in onViewCreated()
All checks were successful
continuous-integration/drone/push Build is passing
* closes #42
* show a info text if no mensa menus where found
* the refresh spinner respects the selecte theme now
2020-08-17 22:30:12 +02:00
953b4825a9
check qispos status and show message if unavailable
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-16 21:30:18 +02:00
2807843d25
fix grades divider shown when sub-subject was added
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-15 18:15:36 +02:00
638c321798
grades ui polishing & sorting fix
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-15 18:13:29 +02:00
42d938b0bc
fix background in fragment_grades
All checks were successful
continuous-integration/drone/push Build is passing
* disable swipe in fragment_grades
2020-08-14 21:57:09 +02:00
9d7504bbaf
add grades ui
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-14 21:41:43 +02:00
8ecfe8f120
QISPOSParser [Part 2]
All checks were successful
continuous-integration/drone/push Build is passing
* parse grades and store them in a HashMap per semester
2020-08-14 15:08:42 +02:00
ff0c4ad1a7
QISPOSParser [Part 1]
All checks were successful
continuous-integration/drone/push Build is passing
* get the grades url from qispos
2020-08-13 21:01:21 +02:00
ea0caea91e
save email and password to encrypted preference
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-12 11:00:22 +02:00
1f660bdd37
add login dialog
All checks were successful
continuous-integration/drone/push Build is passing
* first step towards a working his online integration
* rework AddSubjectDialog
* activate fragment_on_login
2020-08-11 17:09:46 +02:00
be43d87b1a
fix more lint warnings
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-10 15:11:36 +02:00
dd5c7b3fb8
fix some lint warnings
All checks were successful
continuous-integration/drone/push Build is passing
* remove unused resources
2020-08-10 14:51:40 +02:00
fb3dab6dc3
save additional subjects and load them on start
All checks were successful
continuous-integration/drone/push Build is passing
* the app now saves all aditional subjects and lessons and loads them on start
* udpate gradle 6.4.0 -> 6.5.1
* update coroutines 1.3.7 -> 1.3.8
* update several androidx libraries
2020-08-10 14:31:17 +02:00
bcfdb83d14
rework CacheController and TCoRAPIController
All checks were successful
continuous-integration/drone/push Build is passing
* all cache file stuff is now handled in the CacheController
* TCoRAPIController only handles API and Gson stuff
2020-08-07 12:14:50 +02:00
8c55366ec0
update libs
All checks were successful
continuous-integration/drone/push Build is passing
* gradle plugin 4.0.0 -> 4.0.1
2020-07-15 16:02:58 +02:00
6f68fbb73c „README.md“ ändern
All checks were successful
continuous-integration/drone/push Build is passing
2020-06-25 17:30:56 +02:00
297dd77e79
use gradle wrapper for drone
All checks were successful
continuous-integration/drone/push Build is passing
2020-06-25 17:25:58 +02:00
6a70f26807
update .drone.yml
Some checks failed
continuous-integration/drone/push Build is failing
2020-06-25 17:22:06 +02:00
f6b00a8d81
additional Subject are now added to the timetable 2020-06-12 00:51:50 +02:00
69ce9fef5a
code clean up 2020-06-11 21:51:46 +02:00
2d753851c0
more "Additional Lessons" work
* add TimetableController, this class will handle all timetable work (main + additional Subjects)
2020-06-11 21:41:05 +02:00
6c0624c793
make the app more tolerant about wrong API Data 2020-06-11 18:39:19 +02:00
7779296345
change default accent color, code clean up 2020-06-07 23:41:49 +02:00
3e2ef0521e
change primary color to reflect mosad.xyz design guidelines
* minor style fix: nav_header_main set background to black and text color to white
2020-06-07 23:30:41 +02:00
bdfeb46faf
add "Manage Lessons" Dialog to Settings, minor style fixes
* initially select the current theme in the select theme dialog
* set the correct accent color for all dialogs (needs to be done manually for every dialog)
2020-06-07 14:59:04 +02:00
18ca435764
update TCoRAPIController to API version 1.2.0 2020-06-06 20:56:45 +02:00
948f330ebe
TimeTableFragment clean up 2020-06-05 20:56:37 +02:00
72e9efb9e7
fix warings in TCoRAPIController 2020-06-05 19:17:49 +02:00
bea1b47396
complete & move AddLessonDialog to separate class 2020-06-05 16:12:53 +02:00
34a68ff75d
add courses to addLesson Dialog, read lessonSubjects from tcor 2020-05-29 16:56:15 +02:00
1ba3f1fa87
update gradle tool to 4.0.0 2020-05-28 23:27:16 +02:00
48544aef2f
add a complete version of the addLesson Dialog 2020-05-15 18:58:57 +02:00
bc09e33147
update gradle, constraintlayout & coroutines
* gradle 6.2.1 -> 6.4
* constraintlayout 2.0.0-beta4 -> 2.0.0-beta6
* coroutines 1.3.5 -> 1.3.6
2020-05-14 18:27:26 +02:00
6ec9b9f5e2
update commons-lang and coroutines
* commons-lang 3.9 -> 3.10
* coroutines 1.3.3 -> 1.3.5
2020-05-01 12:48:53 +02:00
a19173b499
update kotlin
* kotlin 1.3.70 -> 1.3.71
* gradle-plugin 3.6.1 -> 3.6.2
2020-04-02 17:15:34 +02:00
85dc3181fd
fix compatibility with tcor version 1.2.2 and higher
* update kotlin 1.3.61 -> 1.3.70
2020-03-03 20:13:47 +01:00
e46c234b6c
fix OnboardingActivity is not closed on start of MainActivity 2020-02-27 12:13:46 +01:00
faa07966da
fix crash on first start, if tcor is not reachable 2020-02-27 12:09:24 +01:00
ccc0f0f2bc
Onboarding [Part 2]
* call the onboarding activity on first start
* reworked selectCourse(() to work outside of the SettingsFragment
* closes #36
2020-02-25 15:25:45 +01:00
e15bf95b85
update to gradle 6.2.1 and gradle android plugin 3.6.0 2020-02-25 11:12:22 +01:00
01677c04ef
Onboarding [Part 1]
this Commit adds a Onboarding Activity, it is currently not usefull or fully working, see #36
2020-02-23 20:09:27 +01:00
6e9c63d3d4 Merge branch 'develop' of Seil0/ProjectLaogai into master [Hotfix] 2020-02-18 15:24:08 +01:00
c343735b57
fix crash on first startup 2020-02-18 15:18:36 +01:00
9c5274dc06 Merge branch 'develop' of Seil0/ProjectLaogai into master
release version 0.5.1
2020-02-05 14:15:04 +01:00
b186a2e96e
release version 0.5.1 2020-02-05 14:10:45 +01:00
2cb4b72369
anko code removed, coroutines are now used for asynchronous functions
* constraintlayout 2.0.0-beta3 -> 2.0.0-beta4
* material-components-android 1.0.0 -> 1.1.0
* this is the first release candidate for version 0.5.1
2020-02-04 19:58:07 +01:00
2d497d1a96
update gradle wrapper to version 6.0.1 2020-01-15 15:28:07 +01:00
9d2de3fcb3
don't use deprecated Gson methods 2020-01-15 15:08:05 +01:00
bed3f5d978
added Timetable and Moodle shortcut, cleaned up activity init 2020-01-15 15:00:05 +01:00
8f5a4dd1b3
reworked preference saving 2019-12-13 14:54:51 +01:00
9907d083e9
added a Mensa shortcut
* added a Mensa shortcut, to directly display the Mensa-Screen
* kotlin 1.3.60 -> 1.3.61
2019-11-28 16:38:25 +01:00
d4860b2a32
updated some libs
* kotlin 1.3.50 -> 1.3.60
* androidx.constraintlayout 2.0.0-beta2 -> 2.0.0-beta3
2019-11-14 23:11:49 +01:00
5ec2b53bce
added "Get it on Google Play" badge 2019-10-24 22:22:17 +02:00
701a351e6e
update .drone.yml
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2019-10-17 19:21:15 +02:00
5cad924b26
removed anko dependency from PreferenceController
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2019-10-17 19:19:47 +02:00
95 changed files with 3512 additions and 1066 deletions

View File

@ -3,7 +3,7 @@ name: default
steps:
- name: assembleRelease
image: gradle:jdk8
image: nextcloudci/android10:android-56
commands:
- gradle assembleRelease
- ./gradlew assembleRelease

View File

@ -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

View File

@ -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() {

View File

@ -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

View File

@ -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)
}
}

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

@ -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() {

View File

@ -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("&#8226;", 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) {}
}
}

View File

@ -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)
}
}

View File

@ -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))

View File

@ -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)
}
}
}

View File

@ -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)