Compare commits

..

67 Commits

Author SHA1 Message Date
Jannik 28520bee74
changelog: minor text fixes
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2020-09-04 11:08:50 +02:00
Jannik 0c6a486dd9 Merge pull request 'version 0.6.0' (#46) from develop into master
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
Reviewed-on: #46
2020-08-25 19:24:49 +02:00
Jannik ec15c79b63
update fastlane screenshot
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2020-08-25 12:05:22 +02:00
Jannik 4592d1984f
release version 0.6.0
continuous-integration/drone/push Build is passing Details
2020-08-25 11:55:55 +02:00
Jannik aae74cf9db „README.md“ ändern
continuous-integration/drone/push Build is passing Details
2020-08-25 11:06:38 +02:00
Jannik e19b0db39d
add missing licenses to license dialog
continuous-integration/drone/push Build is passing Details
2020-08-25 10:52:59 +02:00
Jannik 23fd52b0c5
fab: use plus as icon
continuous-integration/drone/push Build is passing Details
2020-08-24 20:22:46 +02:00
Jannik 62a62049c3
code clean up
continuous-integration/drone/push Build is passing Details
2020-08-23 14:27:41 +02:00
Jannik 849698779b
add icon as svg
continuous-integration/drone/push Build is passing Details
* recreate all launcher icons
2020-08-23 14:06:28 +02:00
Jannik a42a92a3c5
update constraintlayout
continuous-integration/drone/push Build is passing Details
* constraintlayout 2.0.0.rc1 -> 2.0.0
2020-08-22 20:35:44 +02:00
Jannik be30e2f968
enable optimisations and minifying for release builds
continuous-integration/drone/push Build is passing Details
* closes #19
* update gradle wrapper to version 6.6
2020-08-22 20:20:23 +02:00
Jannik c629b2aec2
fix login button
continuous-integration/drone/push Build is passing Details
* fix login button action in Onboarding
* add release changelog
* update description
* use new version code scheme
2020-08-21 19:03:28 +02:00
Jannik 99ba87c3f6
init fragment in onViewCreated()
continuous-integration/drone/push Build is passing Details
* 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
Jannik 953b4825a9
check qispos status and show message if unavailable
continuous-integration/drone/push Build is passing Details
2020-08-16 21:30:18 +02:00
Jannik 2807843d25
fix grades divider shown when sub-subject was added
continuous-integration/drone/push Build is passing Details
2020-08-15 18:15:36 +02:00
Jannik 638c321798
grades ui polishing & sorting fix
continuous-integration/drone/push Build is passing Details
2020-08-15 18:13:29 +02:00
Jannik 42d938b0bc
fix background in fragment_grades
continuous-integration/drone/push Build is passing Details
* disable swipe in fragment_grades
2020-08-14 21:57:09 +02:00
Jannik 9d7504bbaf
add grades ui
continuous-integration/drone/push Build is passing Details
2020-08-14 21:41:43 +02:00
Jannik 8ecfe8f120
QISPOSParser [Part 2]
continuous-integration/drone/push Build is passing Details
* parse grades and store them in a HashMap per semester
2020-08-14 15:08:42 +02:00
Jannik ff0c4ad1a7
QISPOSParser [Part 1]
continuous-integration/drone/push Build is passing Details
* get the grades url from qispos
2020-08-13 21:01:21 +02:00
Jannik ea0caea91e
save email and password to encrypted preference
continuous-integration/drone/push Build is passing Details
2020-08-12 11:00:22 +02:00
Jannik 1f660bdd37
add login dialog
continuous-integration/drone/push Build is passing Details
* first step towards a working his online integration
* rework AddSubjectDialog
* activate fragment_on_login
2020-08-11 17:09:46 +02:00
Jannik be43d87b1a
fix more lint warnings
continuous-integration/drone/push Build is passing Details
2020-08-10 15:11:36 +02:00
Jannik dd5c7b3fb8
fix some lint warnings
continuous-integration/drone/push Build is passing Details
* remove unused resources
2020-08-10 14:51:40 +02:00
Jannik fb3dab6dc3
save additional subjects and load them on start
continuous-integration/drone/push Build is passing Details
* 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
Jannik bcfdb83d14
rework CacheController and TCoRAPIController
continuous-integration/drone/push Build is passing Details
* 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
Jannik 8c55366ec0
update libs
continuous-integration/drone/push Build is passing Details
* gradle plugin 4.0.0 -> 4.0.1
2020-07-15 16:02:58 +02:00
Jannik 6f68fbb73c „README.md“ ändern
continuous-integration/drone/push Build is passing Details
2020-06-25 17:30:56 +02:00
Jannik 297dd77e79
use gradle wrapper for drone
continuous-integration/drone/push Build is passing Details
2020-06-25 17:25:58 +02:00
Jannik 6a70f26807
update .drone.yml
continuous-integration/drone/push Build is failing Details
2020-06-25 17:22:06 +02:00
Jannik f6b00a8d81
additional Subject are now added to the timetable 2020-06-12 00:51:50 +02:00
Jannik 69ce9fef5a
code clean up 2020-06-11 21:51:46 +02:00
Jannik 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
Jannik 6c0624c793
make the app more tolerant about wrong API Data 2020-06-11 18:39:19 +02:00
Jannik 7779296345
change default accent color, code clean up 2020-06-07 23:41:49 +02:00
Jannik 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
Jannik 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
Jannik 18ca435764
update TCoRAPIController to API version 1.2.0 2020-06-06 20:56:45 +02:00
Jannik 948f330ebe
TimeTableFragment clean up 2020-06-05 20:56:37 +02:00
Jannik 72e9efb9e7
fix warings in TCoRAPIController 2020-06-05 19:17:49 +02:00
Jannik bea1b47396
complete & move AddLessonDialog to separate class 2020-06-05 16:12:53 +02:00
Jannik 34a68ff75d
add courses to addLesson Dialog, read lessonSubjects from tcor 2020-05-29 16:56:15 +02:00
Jannik 1ba3f1fa87
update gradle tool to 4.0.0 2020-05-28 23:27:16 +02:00
Jannik 48544aef2f
add a complete version of the addLesson Dialog 2020-05-15 18:58:57 +02:00
Jannik 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
Jannik 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
Jannik 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
Jannik 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
Jannik e46c234b6c
fix OnboardingActivity is not closed on start of MainActivity 2020-02-27 12:13:46 +01:00
Jannik faa07966da
fix crash on first start, if tcor is not reachable 2020-02-27 12:09:24 +01:00
Jannik 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
Jannik e15bf95b85
update to gradle 6.2.1 and gradle android plugin 3.6.0 2020-02-25 11:12:22 +01:00
Jannik 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
Jannik 6e9c63d3d4 Merge branch 'develop' of Seil0/ProjectLaogai into master [Hotfix] 2020-02-18 15:24:08 +01:00
Jannik c343735b57
fix crash on first startup 2020-02-18 15:18:36 +01:00
Jannik 9c5274dc06 Merge branch 'develop' of Seil0/ProjectLaogai into master
release version 0.5.1
2020-02-05 14:15:04 +01:00
Jannik b186a2e96e
release version 0.5.1 2020-02-05 14:10:45 +01:00
Jannik 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
Jannik 2d497d1a96
update gradle wrapper to version 6.0.1 2020-01-15 15:28:07 +01:00
Jannik 9d2de3fcb3
don't use deprecated Gson methods 2020-01-15 15:08:05 +01:00
Jannik bed3f5d978
added Timetable and Moodle shortcut, cleaned up activity init 2020-01-15 15:00:05 +01:00
Jannik 8f5a4dd1b3
reworked preference saving 2019-12-13 14:54:51 +01:00
Jannik 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
Jannik 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
Jannik 5ec2b53bce
added "Get it on Google Play" badge 2019-10-24 22:22:17 +02:00
Jannik 701a351e6e
update .drone.yml
continuous-integration/drone/push Build encountered an error Details
2019-10-17 19:21:15 +02:00
Jannik 5cad924b26
removed anko dependency from PreferenceController
continuous-integration/drone/push Build encountered an error Details
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: steps:
- name: assembleRelease - name: assembleRelease
image: gradle:jdk8 image: nextcloudci/android10:android-56
commands: 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) ![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) [![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" # Project Laogai
ProjectLaogai is a app to access the timetable and the mensa menu of Hochschule Offenburg. 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://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 ## Features
* check out the mensa menu of this and next week * have your grades displayed directly in the app
* access your timetable * 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 * check the current balance of your mensa card
* open moodle * open moodle directly in the app
* probably some funny bugs
Please report bugs and issues to support@mosad.xyz
## Screenshots ## 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) [<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_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) [<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: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
android { android {
@ -12,8 +10,8 @@ android {
applicationId "org.mosad.seil0.projectlaogai" applicationId "org.mosad.seil0.projectlaogai"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 29 targetSdkVersion 29
versionCode 14 versionCode 6000 // 0006000
versionName "0.5.0" versionName "0.6.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "build_time", buildTime() resValue "string", "build_time", buildTime()
setProperty("archivesBaseName", "projectlaogai-$versionName") setProperty("archivesBaseName", "projectlaogai-$versionName")
@ -21,8 +19,8 @@ android {
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled true
shrinkResources false shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
@ -49,23 +47,27 @@ android {
dependencies { dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.anko:anko-commons:0.10.8' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'
implementation 'androidx.appcompat:appcompat:1.1.0' 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.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2' implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.google.android.material:material:1.0.0' implementation 'androidx.security:security-crypto:1.1.0-alpha02'
implementation 'com.google.code.gson:gson:2.8.5' 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:aesthetic:1.0.0-beta05'
implementation 'com.afollestad.material-dialogs:core:3.1.1' implementation 'com.afollestad.material-dialogs:core:3.3.0'
implementation 'com.afollestad.material-dialogs:color:3.1.1' 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 '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' testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.ext:junit:1.1.1'
} }
static def buildTime() { static def buildTime() {

View File

@ -15,7 +15,16 @@
# Uncomment this to preserve the line number information for # Uncomment this to preserve the line number information for
# debugging stack traces. # debugging stack traces.
#-keepattributes SourceFile,LineNumberTable #-keepattributes SourceFile,LineNumberTable
-dontobfuscate
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-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 package org.mosad.seil0.projectlaogai
import androidx.test.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.runner.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -18,7 +18,7 @@ class ExampleInstrumentedTest {
@Test @Test
fun useAppContext() { fun useAppContext() {
// Context of the app under test. // Context of the app under test.
val appContext = InstrumentationRegistry.getTargetContext() val appContext = InstrumentationRegistry.getInstrumentation().context
assertEquals("org.mosad.seil0.projectlaogai", appContext.packageName) assertEquals("org.mosad.seil0.projectlaogai", appContext.packageName)
} }
} }

View File

@ -23,6 +23,17 @@
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </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>
<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 * ProjectLaogai
* *
* Copyright 2019 <seil0@mosad.xyz> * Copyright 2019-2020 <seil0@mosad.xyz>
* *
* This program is free software; you can redistribute it and/or modify * 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 * 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.NfcManager
import android.nfc.tech.NfcA import android.nfc.tech.NfcA
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.util.TypedValue
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.ActionBarDrawerToggle
@ -42,18 +44,22 @@ import com.afollestad.aesthetic.NavigationViewMode
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.app_bar_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.NFCMensaCard
import org.mosad.seil0.projectlaogai.controller.PreferencesController import org.mosad.seil0.projectlaogai.controller.cache.CacheController
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorAccent import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorPrimary 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 org.mosad.seil0.projectlaogai.fragments.*
import kotlin.system.measureTimeMillis 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 { class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
private var activeFragment: Fragment = HomeFragment() // the currently active fragment, home at the start 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 adapter: NfcAdapter
private lateinit var pendingIntent: PendingIntent private lateinit var pendingIntent: PendingIntent
@ -62,6 +68,8 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
private var useNFC = false private var useNFC = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
Aesthetic.attach(this) Aesthetic.attach(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
@ -72,11 +80,6 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
initAesthetic() initAesthetic()
initForegroundDispatch() initForegroundDispatch()
//init home fragment
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragment_container, activeFragment)
fragmentTransaction.commit()
val toggle = ActionBarDrawerToggle( val toggle = ActionBarDrawerToggle(
this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close 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) nav_view.setNavigationItemSelectedListener(this)
// if we get an NFC read intent while the app is closed call readBalance // based on the intent we get, call readBalance or open a Fragment
if (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action) when (intent.action) {
NFCMensaCard.readBalance(intent, this) 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) { override fun onNewIntent(intent: Intent) {
@ -145,6 +156,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
R.id.nav_mensa -> MensaFragment() R.id.nav_mensa -> MensaFragment()
R.id.nav_timetable -> TimeTableFragment() R.id.nav_timetable -> TimeTableFragment()
R.id.nav_moodle -> MoodleFragment() R.id.nav_moodle -> MoodleFragment()
R.id.nav_grades -> GradesFragment()
R.id.nav_settings -> SettingsFragment() R.id.nav_settings -> SettingsFragment()
else -> HomeFragment() else -> HomeFragment()
} }
@ -163,22 +175,25 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
*/ */
private fun load() { private fun load() {
val startupTime = measureTimeMillis { 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 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() { private fun initAesthetic() {
// If we haven't set any defaults, do that now // If we haven't set any defaults, do that now
if (Aesthetic.isFirstTime) { 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 { Aesthetic.config {
activityTheme(R.style.AppTheme_Light) activityTheme(R.style.AppTheme_Light)
apply() apply()
} }
SettingsFragment().selectCourse(this) // FIXME this is not working // show the onboarding activity
startActivity(Intent(this, OnboardingActivity::class.java))
finish()
} }
Aesthetic.config { Aesthetic.config {
@ -189,6 +204,13 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
apply() 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() { 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 * ProjectLaogai
* *
* Copyright 2019 <seil0@mosad.xyz> * Copyright 2019-2020 <seil0@mosad.xyz>
* *
* This program is free software; you can redistribute it and/or modify * 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 * 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 com.codebutler.farebot.card.desfire.DesfireProtocol
import kotlinx.android.synthetic.main.dialog_mensa_credit.* import kotlinx.android.synthetic.main.dialog_mensa_credit.*
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import java.lang.Exception import java.lang.Exception
class NFCMensaCard { class NFCMensaCard {
@ -66,7 +67,7 @@ class NFCMensaCard {
lookAtMe(context, data, settings.value).show() lookAtMe(context, data, settings.value).show()
} }
} catch (ex: Exception) { } 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) val dialog = MaterialDialog(context)
.customView(R.layout.dialog_mensa_credit) .customView(R.layout.dialog_mensa_credit)
val current = if (!PreferencesController.oGiants) { val current = if (!Preferences.oGiants) {
String.format("%.2f €", (currentRaw.toFloat() / 1000)) String.format("%.2f €", (currentRaw.toFloat() / 1000))
} else { } else {
String.format("%.4f shm", (currentRaw.toFloat() * 0.0000075)) String.format("%.4f shm", (currentRaw.toFloat() * 0.0000075))
} }
val last = if (!PreferencesController.oGiants) { val last = if (!Preferences.oGiants) {
String.format("%.2f €", (lastRaw.toFloat() / 1000)) String.format("%.2f €", (lastRaw.toFloat() / 1000))
} else { } else {
String.format("%.4f shm", (lastRaw.toFloat() * 0.0000075)) 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)
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
}
}

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2019 <seil0@mosad.xyz> * Copyright 2019-2020 <seil0@mosad.xyz>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -22,99 +22,95 @@
package org.mosad.seil0.projectlaogai.controller package org.mosad.seil0.projectlaogai.controller
import android.content.Context import com.google.gson.Gson
import android.util.Log import com.google.gson.JsonParser
import org.jetbrains.anko.doAsync import com.google.gson.reflect.TypeToken
import org.json.JSONObject import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.coursesCacheTime import org.mosad.seil0.projectlaogai.util.*
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 java.net.URL import java.net.URL
import kotlin.Exception
/**
* This Controller calls the tcor api,
* all functions return tcor api objects
*/
class TCoRAPIController { class TCoRAPIController {
companion object { companion object {
private const val className = "TCoRAPIController"
private const val tcorBaseURL = "https://tcor.mosad.xyz" 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) = doAsync {
try {
val url = URL("$tcorBaseURL/courseList")
val file = File(context.filesDir, "courses.json")
// read data from the API fun getCourseListNEW(): CoursesList {
val coursesObject = JSONObject(url.readText()) //JSONObject(inReader.readLine()) val url = URL("$tcorBaseURL/courseList")
// write the json object to a file
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)
}
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) = doAsync { fun getMensaMenu(): MensaMenu {
try { val url = URL("$tcorBaseURL/mensamenu")
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
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)
}
return Gson().fromJson(url.readText(), MensaMenu().javaClass)
} }
/** /**
* get the json object from tcor api and write it as file (cache) * Get the timetable for "courseName" at week "week"
* Read the json object from tcor api
* @param courseName the course name (e.g AI1)
* @param week the week to update (0 for the current and so on)
*/ */
fun getTimetable(courseName: String, week: Int, context: Context) = doAsync { fun getTimetable(courseName: String, week: Int): TimetableWeek {
try { val url = URL("$tcorBaseURL/timetable?course=$courseName&week=$week")
val url = URL("$tcorBaseURL/timetable?courseName=$courseName&week=$week") val timetableCW = Gson().fromJson(url.readText(), TimetableCourseWeek().javaClass)
val file = File(context.filesDir, "timetable-$courseName-$week.json")
// read data from the API return TimetableWeek(
val mensaObject = JSONObject(url.readText()) //JSONObject(inReader.readLine()) timetableCW.meta.weekIndex,
timetableCW.meta.weekNumberYear,
timetableCW.timetable.days
)
}
// write the json object to a file /**
val writer = BufferedWriter(FileWriter(file)) * Get all lessons for a course at one week (async)
writer.write(mensaObject.toString()) * @param courseName the course name
writer.close() * @param week the week to look up
*/
fun getSubjectListAsync(courseName: String, week: Int): Deferred<ArrayList<String>> {
val url = URL("$tcorBaseURL/subjectList?course=$courseName&week=$week")
// update cache time return GlobalScope.async {
timetableCacheTime = System.currentTimeMillis() / 1000 Gson().fromJson(url.readText(), ArrayList<String>()::class.java)
PreferencesController.save(context) }
} catch (ex: Exception) { }
Log.e(className, "failed to get /timetable", ex)
/**
* Get all occurrences of a lesson for a course at one week
* @param courseName the course name
* @param subject the subject to search for
* @param week the week to look up
*/
fun getLessons(courseName: String, subject: String, week: Int): ArrayList<Lesson> {
val url = URL("$tcorBaseURL/lessons?course=$courseName&subject=$subject&week=$week")
var array: ArrayList<Lesson>
runBlocking {
withContext(Dispatchers.Default) {
array = Gson().fromJson(
JsonParser.parseString(url.readText()).asJsonArray,
object : TypeToken<ArrayList<Lesson>>() {}.type
)
}
} }
return array
} }
} }
} }

View File

@ -0,0 +1,375 @@
/**
* 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.cache
import android.content.Context
import android.util.Log
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonParser
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.coursesCacheTime
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.mensaCacheTime
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.timetableCacheTime
import org.mosad.seil0.projectlaogai.util.*
import java.io.*
import java.util.*
import kotlin.collections.ArrayList
/**
* The cacheController reads and updates the cache files.
* It contains the courseList and mensaMenu object, all timetable objects
* are located in TimetableController.
*/
class CacheController(cont: Context) {
private val className = "CacheController"
private val context = cont
init {
val cal = Calendar.getInstance()
val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
val currentTime = System.currentTimeMillis() / 1000
// check if we need to update the mensa data before displaying it
cal.time = Date(mensaCacheTime * 1000)
// if a) it's monday and the last cache update was on sunday or b) the cache is older than 24hr, update blocking
if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - mensaMenu.meta.updateTime) > 86400) {
Log.i(className, "update mensa blocking")
GlobalScope.launch(Dispatchers.Default) { updateMensaMenu(context).join() }
}
// check if we need to update the timetable before displaying it
cal.time = Date(timetableCacheTime * 1000)
// if a) it`s monday and the last cache update was not on a sunday or b) the cache is older than 24hr, update blocking
if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - timetableCacheTime) > 86400) {
Log.i(className, "updating timetable after sunday!")
GlobalScope.launch(Dispatchers.Default) {
val threads = listOf(
updateTimetable(cCourse.courseName, 0, context),
updateTimetable(cCourse.courseName, 1, context)
)
threads.joinAll()
}
}
updateCourseList(context)
readStartCache(cCourse.courseName) // initially read values from cache
// check if an update is necessary, not blocking
if (currentTime - coursesCacheTime > 86400)
updateCourseList(context)
if (currentTime - mensaCacheTime > 10800)
updateMensaMenu(context)
if (currentTime - timetableCacheTime > 10800) {
TimetableController.update(context)
}
}
companion object {
private const val className = "CacheController"
var coursesList = ArrayList<Course>()
var mensaMenu = MensaMenu()
/**
* update the course list, async
*/
fun updateCourseList(context: Context): Job {
val file = File(context.filesDir, "courses.json")
var courseListUp = CoursesList()
return GlobalScope.launch(Dispatchers.IO) {
try {
courseListUp = TCoRAPIController.getCourseListNEW()
} catch (ex: Exception) {
Log.e(className, "could not load course list from tcor", ex)
}
// update coursesList array list
coursesList = courseListUp.courses
// save cache file and update time
save(file, Gson().toJson(courseListUp))
coursesCacheTime = System.currentTimeMillis() / 1000
Preferences.save(context)
}
}
/**
* update the mensa menu, async
*/
fun updateMensaMenu(context: Context): Job {
val file = File(context.filesDir, "mensa.json")
return GlobalScope.launch(Dispatchers.IO) {
try {
mensaMenu = TCoRAPIController.getMensaMenu()
} catch (ex: Exception) {
Log.e(className, "could not load mensa menu from tcor", ex)
}
// save cache file and update time
save(file, Gson().toJson(mensaMenu))
mensaCacheTime = System.currentTimeMillis() / 1000
Preferences.save(context)
}
}
/**
* update the timetable for a week, async
* @param courseName the course name (e.g AI1)
* @param week the week to update (0 for the current and so on)
*/
fun updateTimetable(courseName: String, week: Int, context: Context): Job {
val file = File(context.filesDir, "timetable-$courseName-$week.json")
var timetable = TimetableWeek()
// try to update timetable from tcor, async
return GlobalScope.launch(Dispatchers.IO) {
try {
timetable = TCoRAPIController.getTimetable(courseName, week)
} catch (ex: Exception) {
Log.e(className, "could not load timetable $courseName[$week] from tcor", ex)
}
// update timetable in TTC
if (TimetableController.timetable.size > week) {
TimetableController.timetable[week] = timetable
} else {
TimetableController.timetable.add(timetable)
}
// save cache file and update time
save(file, Gson().toJson(timetable))
timetableCacheTime = System.currentTimeMillis() / 1000
Preferences.save(context)
}
}
/**
* update all additional subject lessons, async
*/
fun updateAdditionalLessons(context: Context): Job {
val fileLessons = File(context.filesDir, "additional_lessons.json")
return GlobalScope.launch(Dispatchers.IO) {
TimetableController.subjectMap.forEach { (courseName, subjects) ->
// update all subjects for a course
subjects.forEach {subject ->
try {
TCoRAPIController.getLessons(courseName, subject, 0).forEach { lesson ->
TimetableController.addLesson(courseName, subject, lesson)
}
} catch (ex: Exception) {
Log.e(className, "could not load $courseName: $subject", ex)
}
}
}
save(fileLessons, Gson().toJson(TimetableController.lessonMap))
}
}
/**
* save changes in lessonMap and subjectMap,
* called on addSubject or removeSubject
*/
fun saveAdditionalSubjects(context: Context) {
val fileLessons = File(context.filesDir, "additional_lessons.json")
val fileSubjects = File(context.filesDir, "additional_subjects.json")
save(fileLessons, Gson().toJson(TimetableController.lessonMap))
save(fileSubjects, Gson().toJson(TimetableController.subjectMap))
}
private fun save(file: File, text: String) {
try {
val writer = BufferedWriter(FileWriter(file))
writer.write(text)
writer.close()
} catch (ex: Exception) {
Log.e(className, "failed to write file \"${file.absoluteFile}\"", ex)
}
}
}
/**
* read coursesList, mensa (current and next week), timetable (current and next week)
* @param courseName the course name (e.g AI1)
*/
private fun readStartCache(courseName: String) {
try {
readCoursesList()
} catch (ex : Exception) {
Log.e(className, "Error while reading the course list", ex)
}
try {
readMensa()
} catch (ex : Exception) {
Log.e(className, "Error while reading the mensa menu", ex)
}
try {
readTimetable(courseName, 0)
} catch (ex : Exception) {
Log.e(className, "Error while reading timetable week 0", ex)
}
try {
readTimetable(courseName, 1)
} catch (ex : Exception) {
Log.e(className, "Error while reading timetable week 1", ex)
}
try {
readAdditionalSubjects()
} catch (ex : Exception) {
Log.e(className, "Error while reading additional subjects", ex)
}
}
/**
* read the courses list from the cached file
* add them to the coursesList object
*/
private fun readCoursesList() {
val file = File(context.filesDir, "courses.json")
// make sure the file exists
if (!file.exists()) {
runBlocking { updateCourseList(context).join() }
return
}
coursesList = FileReader(file).use {
GsonBuilder().create().fromJson(BufferedReader(it).readLine(), CoursesList().javaClass).courses
}
}
/**
* get the MensaMenu object from the cached json,
* if cache is empty create the cache file
*/
private fun readMensa() {
val file = File(context.filesDir, "mensa.json")
// make sure the file exists
if (!file.exists()) {
runBlocking { updateMensaMenu(context).join() }
return
}
mensaMenu = FileReader(file).use {
GsonBuilder().create().fromJson(BufferedReader(it).readLine(), MensaMenu().javaClass)
}
}
/**
* read the weeks timetable from the cached file
* @param courseName the course name (e.g AI1)
* @param week the week to read (0 for the current and so on)
*/
private fun readTimetable(courseName: String, week: Int) {
val file = File(context.filesDir, "timetable-$courseName-$week.json")
// if the file does not exist, call updateTimetable blocking and return
if (!file.exists()) {
runBlocking { updateTimetable(courseName, week, context).join() }
return
}
val timetableObject = FileReader(file).use {
JsonParser.parseString(BufferedReader(it).readLine()).asJsonObject
}
// if its a TimetableCourseWeek object migrate to TimetableWeek TODO remove converting at version 0.8
val timetable = if(timetableObject.has("meta")) {
Log.i(Companion.className, "trying to migrate TimetableCourseWeek to TimetableWeek")
val timetableWC = Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass)
save(file, Gson().toJson(TimetableWeek(
timetableWC.meta.weekIndex,
timetableWC.meta.weekNumberYear,
timetableWC.timetable.days
)))
TimetableWeek(timetableWC.meta.weekIndex, timetableWC.meta.weekNumberYear, timetableWC.timetable.days)
} else {
Gson().fromJson(timetableObject, TimetableWeek().javaClass)
}
// update timetable in TTC
if (TimetableController.timetable.size > week) {
TimetableController.timetable[week] = timetable
} else {
TimetableController.timetable.add(timetable)
}
}
private fun readAdditionalSubjects() {
val fileLessons = File(context.filesDir, "additional_lessons.json")
val fileSubjects = File(context.filesDir, "additional_subjects.json")
// make sure the file exists
if (!fileLessons.exists() || !fileSubjects.exists()) {
return
}
// clear the maps before loading, just to be save
TimetableController.lessonMap.clear()
TimetableController.subjectMap.clear()
// read subjects and lessons from cache file
FileReader(fileLessons).use {
TimetableController.lessonMap.putAll(
GsonBuilder().create()
.fromJson(BufferedReader(it).readLine(), object : TypeToken<HashMap<String, Lesson>>() {}.type)
)
}
FileReader(fileSubjects).use {
TimetableController.subjectMap.putAll(
GsonBuilder().create()
.fromJson(BufferedReader(it).readLine(), HashMap<String, ArrayList<String>>().javaClass)
)
}
// add lessons to timetable
TimetableController.lessonMap.forEach { (_, lesson) ->
TimetableController.addLessonToTimetable(lesson)
}
}
}

View File

@ -0,0 +1,131 @@
/**
* 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.cache
import android.content.Context
import kotlinx.coroutines.Job
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
import org.mosad.seil0.projectlaogai.util.Lesson
import org.mosad.seil0.projectlaogai.util.TimetableWeek
/**
* The TimetableController contains the timetable, subjectMap
* and lessonMap objects. It also contains the additional subjects logic.
* All functions ro read or update cache files are located in the CacheController.
*
* TODO
* * add second week
* * add configurable week to addSubject() and removeSubject(), updateAdditionalLessons()
*/
class TimetableController {
companion object {
val timetable = ArrayList<TimetableWeek>()
val lessonMap = HashMap<String, Lesson>() // the key is courseName-subject-lessonID
val subjectMap = HashMap<String, ArrayList<String>>() // the key is courseName
/**
* update the main timetable and all additional subjects, async
*/
fun update(context: Context): List<Job> {
return listOf(
CacheController.updateTimetable(cCourse.courseName, 0, context),
CacheController.updateTimetable(cCourse.courseName, 1, context),
CacheController.updateAdditionalLessons(context)
)
}
/**
* add a subject to the subjectMap and all it's lessons
* to the lessonMap
* @param courseName course to which the subject belongs
* @param subject the subjects name
*/
fun addSubject(courseName: String, subject: String, context: Context) {
// add subject
if (subjectMap.containsKey(courseName)) {
subjectMap[courseName]?.add(subject)
} else {
subjectMap[courseName] = arrayListOf(subject)
}
// add concrete lessons
TCoRAPIController.getLessons(courseName, subject, 0).forEach { lesson ->
addLesson(courseName, subject, lesson)
}
CacheController.saveAdditionalSubjects(context)
}
/**
* remove a subject from the subjectMap and all it's lessons
* from the lessonMap
* @param courseName course to which the subject belongs
* @param subject the subjects name
*/
fun removeSubject(courseName: String, subject: String, context: Context) {
// remove subject
subjectMap[courseName]?.remove(subject)
// remove concrete lessons
val iterator = lessonMap.iterator()
while (iterator.hasNext()) {
val it = iterator.next()
if(it.key.contains("$courseName-$subject")) {
// remove the lesson from the lessons list
iterator.remove() // use iterator to remove, otherwise ConcurrentModificationException
// remove the lesson from the timetable
val id = it.value.lessonID.split(".")
if(id.size == 3)
timetable[0].days[id[0].toInt()].timeslots[id[1].toInt()].remove(it.value)
}
}
CacheController.saveAdditionalSubjects(context)
}
/**
* add a lesson to the lessonMap, also add it to the timetable
*/
fun addLesson(courseName: String, subject: String, lesson: Lesson) {
//the courseName, subject and lessonID, separator: -
val key = "$courseName-$subject-${lesson.lessonID}"
lessonMap[key] = lesson
addLessonToTimetable(lesson)
}
/**
* add a lesson to the timetable
*/
fun addLessonToTimetable(lesson: Lesson) {
val id = lesson.lessonID.split(".")
if(id.size == 3)
timetable[0].days[id[0].toInt()].timeslots[id[1].toInt()].add(lesson)
}
}
}

View File

@ -0,0 +1,110 @@
/**
* 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.preferences
import android.content.Context
import android.content.SharedPreferences
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Log
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import org.mosad.seil0.projectlaogai.R
object EncryptedPreferences {
var email = ""
internal set
/**
* save user email and password to encrypted preference
*/
fun saveCredentials(email: String, password: String, context: Context) {
this.email = email
with (getEncryptedPreferences(context)?.edit()) {
this?.putString(context.getString(R.string.save_key_user_email), email)
this?.putString(context.getString(R.string.save_key_user_password), password)
this?.apply()
}
}
/**
* read user email and password from encrypted preference
* @return Pair(email, password)
*/
fun readCredentials(context: Context): Pair<String, String> {
return with (getEncryptedPreferences(context)) {
email = this?.getString(context.getString(R.string.save_key_user_email), "").toString()
Pair(
this?.getString(context.getString(R.string.save_key_user_email), "").toString(),
this?.getString(context.getString(R.string.save_key_user_password), "").toString()
)
}
}
/**
* initially load the stored values
*/
fun load(context: Context) {
with(getEncryptedPreferences(context)) {
email = this?.getString(
context.getString(R.string.save_key_user_email), ""
).toString()
}
}
/**
* create a encrypted shared preference
*/
private fun getEncryptedPreferences(context: Context): SharedPreferences? {
return try {
val spec = KeyGenParameterSpec.Builder(MasterKey.DEFAULT_MASTER_KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(MasterKey.DEFAULT_AES_GCM_MASTER_KEY_SIZE)
.build()
val masterKey = MasterKey.Builder(context)
.setKeyGenParameterSpec(spec)
.build()
EncryptedSharedPreferences.create(
context,
context.getString(R.string.encrypted_preference_file_key),
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
} catch (ex: Exception) {
Log.e(javaClass.name, "Could not create encrypted shared preference.", ex)
null
}
}
}

View File

@ -0,0 +1,192 @@
/**
* 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.preferences
import android.content.Context
import android.graphics.Color
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.util.Course
/**
* The PreferencesController class
* contains all preferences and global variables that exist in this app
*/
object Preferences {
var coursesCacheTime: Long = 0
var mensaCacheTime: Long = 0
var timetableCacheTime: Long = 0
var cColorPrimary: Int = Color.parseColor("#009688")
var cColorAccent: Int = Color.parseColor("#0096ff")
var cCourse = Course(
"https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0",
"AI3"
)
var cShowBuffet = true
var oGiants = false
// TODO move!
var themePrimary = 0
var themeSecondary = 0
// the save function
fun save(context: Context) {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
// save the update times (cache)
with (sharedPref.edit()) {
putLong(context.getString(R.string.save_key_coursesCacheTime),
coursesCacheTime
)
putLong(context.getString(R.string.save_key_mensaCacheTime),
mensaCacheTime
)
putLong(context.getString(R.string.save_key_timetableCacheTime),
timetableCacheTime
)
apply()
}
}
/**
* save the course locally
*/
fun saveCourse(context: Context, course: Course) {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
with (sharedPref.edit()) {
putString(context.getString(R.string.save_key_course), course.courseName)
putString(context.getString(R.string.save_key_courseTTLink), course.courseLink)
apply()
}
cCourse = course
}
/**
* save the primary color
*/
fun saveColorPrimary(context: Context, colorPrimary: Int) {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
with (sharedPref.edit()) {
putInt(context.getString(R.string.save_key_colorPrimary),
colorPrimary
)
apply()
}
cColorPrimary = colorPrimary
}
/**
* save the accent color
*/
fun saveColorAccent(context: Context, colorAccent: Int) {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
with (sharedPref.edit()) {
putInt(context.getString(R.string.save_key_colorAccent),
colorAccent
)
apply()
}
cColorAccent = colorAccent
}
/**
* save showBuffet
*/
fun saveShowBuffet(context: Context, showBuffet: Boolean) {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
with (sharedPref.edit()) {
putBoolean(context.getString(R.string.save_key_showBuffet),
showBuffet
)
apply()
}
cShowBuffet = showBuffet
}
/**
* initially load the stored values
*/
fun load(context: Context) {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
// load the update times (cache)
coursesCacheTime = sharedPref.getLong(context.getString(
R.string.save_key_coursesCacheTime
), 0)
mensaCacheTime = sharedPref.getLong(context.getString(
R.string.save_key_mensaCacheTime
), 0)
timetableCacheTime = sharedPref.getLong(context.getString(
R.string.save_key_timetableCacheTime
), 0)
// load saved course
cCourse = Course(
sharedPref.getString(context.getString(R.string.save_key_courseTTLink),
"https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0"
)!!,
sharedPref.getString(context.getString(R.string.save_key_course), "AI3")!!
)
// load saved colors
cColorPrimary = sharedPref.getInt(context.getString(
R.string.save_key_colorPrimary
), cColorPrimary)
cColorAccent = sharedPref.getInt(context.getString(
R.string.save_key_colorAccent
), cColorAccent)
// load showBuffet
cShowBuffet = sharedPref.getBoolean(context.getString(
R.string.save_key_showBuffet
), true)
}
}

View File

@ -0,0 +1,200 @@
/**
* 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.fragments
import android.graphics.Rect
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.Fragment
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import kotlinx.android.synthetic.main.fragment_grades.*
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.QISPOSParser
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.GradeLinearLayout
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
/**
* The grades fragment class
* contains all needed parts to display and the grades screen
*/
class GradesFragment : Fragment() {
private lateinit var refreshLayoutGrades: SwipeRefreshLayout
private lateinit var parser: QISPOSParser
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_grades, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
refreshLayoutGrades = view.findViewById(R.id.refreshLayout_Grades)
refreshLayoutGrades.isEnabled = false // disable swipe
refreshLayoutGrades.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
parser = QISPOSParser(context!!)// init the parser
if (checkCredentials() && checkQisposStatus()) {
GlobalScope.launch(Dispatchers.Default) {
addGrades()
}
}
}
/**
* check if the credentials are set, if not show a login dialog
*/
private fun checkCredentials(): Boolean {
val credentials = EncryptedPreferences.readCredentials(context!!)
var credentialsPresent = false
// if there is no password set, show the login dialog
if (credentials.first.isEmpty() || credentials.second.isEmpty()) {
LoginDialog(this.context!!)
.positiveButton {
EncryptedPreferences.saveCredentials(email, password, context)
addGrades()
}
.negativeButton {
txtView_Loading.text = resources.getString(R.string.credentials_missing)
}
.show {
email = EncryptedPreferences.email
password = ""
}
} else {
credentialsPresent = true
}
return credentialsPresent
}
/**
* check if qispos is available, if not show an error
*/
private fun checkQisposStatus(): Boolean {
val statusCode = parser.checkQISPOSStatus()
// show error if the status code is not 200
if (statusCode != 200) {
val infoText = resources.getString(when(statusCode) {
503 -> R.string.qispos_unavailable
else -> R.string.qispos_generic_error
})
val img = ResourcesCompat.getDrawable(resources, R.drawable.ic_error_outline_black_24dp, null)?.apply {
bounds = Rect(0, 0, 75, 75)
}
txtView_Loading?.apply {
text = infoText
setCompoundDrawables(null, null, null, img)
}
}
return statusCode == 200
}
// add the grades to the layout, async
private fun addGrades() = GlobalScope.launch(Dispatchers.Default) {
withContext(Dispatchers.Main) {
refreshLayout_Grades.isRefreshing = true
}
val grades = parser.parseGrades()
withContext(Dispatchers.Main) {
linLayout_Grades.removeAllViews() // clear layout
}
// for each semester add a new card
grades.forEach { semester ->
val usedSubjects = ArrayList<String>()
val semesterCard = DayCardView(context!!)
semesterCard.setDayHeading(semester.key)
// for each subject add a new linLayout
semester.value.forEachIndexed { index, subject ->
if (usedSubjects.contains(subject.name)) {
return@forEachIndexed
}
// get the first sub subjects
val subSubject = semester.value.firstOrNull {
it.name.contains(subject.name) && it.name != subject.name
}
// if sub subject is not null, add it to used subjects
subSubject?.let {
usedSubjects.add(it.name)
}
val subjectLayout = GradeLinearLayout(context).set {
subjectName = subject.name
grade = subject.grade
subSubjectName = subSubject?.name.toString()
subGrade = subSubject?.grade.toString()
}
// disable sub-subject if not set
if (subSubject == null)
subjectLayout.disableSubSubject()
// disable divider if last element
if (index == semester.value.lastIndex || semester.value.indexOf(subSubject) == semester.value.lastIndex)
subjectLayout.disableDivider()
semesterCard.getLinLayoutDay().addView(subjectLayout)
}
// without context we can't access the view
withContext(Dispatchers.Main) {
linLayout_Grades.addView(semesterCard)
}
}
val txtViewLegal = TextView(context).apply {
text = resources.getString(R.string.without_guarantee)
textAlignment = View.TEXT_ALIGNMENT_CENTER
}
// stop refreshing and show legal warning
withContext(Dispatchers.Main) {
linLayout_Grades.addView(txtViewLegal)
refreshLayout_Grades.isRefreshing = false
}
}
}

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2019 <seil0@mosad.xyz> * Copyright 2019-2020 <seil0@mosad.xyz>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -31,16 +31,15 @@ import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_home.* import kotlinx.android.synthetic.main.fragment_home.*
import org.jetbrains.anko.doAsync import kotlinx.coroutines.*
import org.jetbrains.anko.uiThread
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaMenu import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.mensaMenu
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.timetables import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
import org.mosad.seil0.projectlaogai.hsoparser.Meal import org.mosad.seil0.projectlaogai.util.Meal
import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar import org.mosad.seil0.projectlaogai.util.TimetableDay
import org.mosad.seil0.projectlaogai.hsoparser.TimetableDay
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -54,26 +53,26 @@ class HomeFragment : Fragment() {
private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault()) private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault())
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
val view: View = inflater.inflate(R.layout.fragment_home, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
addMensaMenu().get() addMensaMenu()
addTimeTable().get() addTimeTable()
// Inflate the layout for this fragment
return view
} }
/** /**
* add the current mensa meal to the home screens * add the current mensa meal to the home screens
*/ */
private fun addMensaMenu() = doAsync { private fun addMensaMenu() = GlobalScope.launch(Dispatchers.Default) {
var dayMeals: ArrayList<Meal> var dayMeals: ArrayList<Meal>
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
val mensaCardView = DayCardView(context!!) val mensaCardView = DayCardView(context!!)
uiThread { withContext(Dispatchers.Main) {
if (isAdded) { if (isAdded) {
if (cal.get(Calendar.HOUR_OF_DAY) < 15) { if (cal.get(Calendar.HOUR_OF_DAY) < 15) {
@ -117,11 +116,11 @@ class HomeFragment : Fragment() {
/** /**
* add the current timetable to the home screen * add the current timetable to the home screen
*/ */
private fun addTimeTable() = doAsync { private fun addTimeTable() = GlobalScope.launch(Dispatchers.Default) {
uiThread { withContext(Dispatchers.Main) {
if (isAdded && timetables.isNotEmpty()) { if (isAdded && TimetableController.timetable.isNotEmpty()) {
try { try {
val dayCardView = findNextDay(NotRetardedCalendar.getDayOfWeekIndex()) val dayCardView = findNextDay(NotRetardedCalendar.getDayOfWeekIndex())
linLayout_Home.addView(dayCardView) linLayout_Home.addView(dayCardView)
@ -140,18 +139,19 @@ class HomeFragment : Fragment() {
* @param startDayIndex the day index you want to start searching * @param startDayIndex the day index you want to start searching
* @return a DayCardView with all lessons added * @return a DayCardView with all lessons added
*/ */
private fun findNextDay(startDayIndex: Int) : DayCardView { private fun findNextDay(startDayIndex: Int): DayCardView {
val dayCardView = DayCardView(context!!) val dayCardView = DayCardView(context!!)
var dayTimetable: TimetableDay? = null var dayTimetable: TimetableDay? = null
var dayIndexSearch = startDayIndex var dayIndexSearch = startDayIndex
var weekIndexSearch = 0 var weekIndexSearch = 0
while (dayTimetable == null && weekIndexSearch < timetables.size) { while (dayTimetable == null && weekIndexSearch < TimetableController.timetable.size) {
for (dayIndex in dayIndexSearch..5) { for (dayIndex in dayIndexSearch..5) {
dayTimetable = timetables[weekIndexSearch].timetable.days[dayIndex] dayTimetable = TimetableController.timetable[weekIndexSearch].days[dayIndex]
// some wired calendar magic, calculate the correct date to be shown ((timetable week - current week * 7) + (dayIndex - dayIndex of current week) // some wired calendar magic, calculate the correct date to be shown ((timetable week - current week * 7) + (dayIndex - dayIndex of current week)
val daysToAdd =(timetables[weekIndexSearch].meta.weekNumberYear - NotRetardedCalendar.getWeekOfYear()) * 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex()) val daysToAdd = ((TimetableController.timetable[weekIndexSearch].weekNumberYear - NotRetardedCalendar.getWeekOfYear())
* 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex()))
dayCardView.addTimetableDay(dayTimetable, daysToAdd) dayCardView.addTimetableDay(dayTimetable, daysToAdd)
// if there are no lessons don't show the dayCardView // if there are no lessons don't show the dayCardView
@ -179,7 +179,7 @@ class HomeFragment : Fragment() {
noLesson.textSize = 18.0F noLesson.textSize = 18.0F
noLesson.setTypeface(null, Typeface.BOLD) noLesson.setTypeface(null, Typeface.BOLD)
noLesson.textAlignment = View.TEXT_ALIGNMENT_CENTER noLesson.textAlignment = View.TEXT_ALIGNMENT_CENTER
noLesson.setPadding(7,7,7,7) noLesson.setPadding(7, 7, 7, 7)
return noLesson return noLesson
} }

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2019 <seil0@mosad.xyz> * Copyright 2019-2020 <seil0@mosad.xyz>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -28,17 +28,20 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_mensa.* import kotlinx.android.synthetic.main.fragment_mensa.*
import org.jetbrains.anko.doAsync import kotlinx.coroutines.Dispatchers
import org.jetbrains.anko.uiThread import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.CacheController import org.mosad.seil0.projectlaogai.controller.cache.CacheController
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaMenu import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.mensaMenu
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cShowBuffet import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cShowBuffet
import org.mosad.seil0.projectlaogai.hsoparser.MensaWeek
import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
import org.mosad.seil0.projectlaogai.uicomponents.TextViewInfo
import org.mosad.seil0.projectlaogai.util.MensaWeek
import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
/** /**
* The mensa controller class * The mensa controller class
@ -47,22 +50,31 @@ import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
class MensaFragment : Fragment() { class MensaFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_mensa, container, false)
}
val view: View = inflater.inflate(R.layout.fragment_mensa, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// init actions refreshLayout_Mensa.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
refreshAction()
// add the current week (week starts on sunday) refreshAction() // init actions
val dayCurrent = if(NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
addWeek(mensaMenu.currentWeek, dayCurrent).get()
// add the next week GlobalScope.launch(Dispatchers.Default) {
addWeek(mensaMenu.nextWeek, 0) val dayCurrent = if(NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
addWeek(mensaMenu.currentWeek, dayCurrent).join()
// TODO should we show a info if there is no more food this & next week? // add the next week
addWeek(mensaMenu.nextWeek, 0)
}
return view // show a info if there are no more menus
if (linLayout_Mensa.childCount == 0) {
val txtViewInfo = TextViewInfo(context!!).set {
txt = resources.getString(R.string.no_more_meals)
}
linLayout_Mensa.addView(txtViewInfo)
}
} }
/** /**
@ -70,10 +82,9 @@ class MensaFragment : Fragment() {
* @param menusWeek menu of type MensaWeek you want to add * @param menusWeek menu of type MensaWeek you want to add
* @param dayStart the first day of the week to add * @param dayStart the first day of the week to add
*/ */
private fun addWeek(menusWeek: MensaWeek, dayStart: Int) = doAsync { private fun addWeek(menusWeek: MensaWeek, dayStart: Int) = GlobalScope.launch(Dispatchers.Default) {
uiThread {
withContext(Dispatchers.Main) {
// only add the days dayStart to Fri since the mensa is closed on Sat/Sun // only add the days dayStart to Fri since the mensa is closed on Sat/Sun
for (dayIndex in dayStart..4) { for (dayIndex in dayStart..4) {
var helpMeal = MealLinearLayout(context) var helpMeal = MealLinearLayout(context)
@ -94,10 +105,9 @@ class MensaFragment : Fragment() {
helpMeal.disableDivider() helpMeal.disableDivider()
if(dayCardView.getLinLayoutDay().childCount > 1) if(dayCardView.getLinLayoutDay().childCount > 2)
linLayout_Mensa.addView(dayCardView) linLayout_Mensa.addView(dayCardView)
} }
} }
} }
@ -105,8 +115,8 @@ class MensaFragment : Fragment() {
/** /**
* initialize the refresh action * initialize the refresh action
*/ */
private fun refreshAction() = doAsync { private fun refreshAction() = GlobalScope.launch(Dispatchers.Default) {
uiThread { withContext(Dispatchers.Main) {
// set the refresh listener // set the refresh listener
refreshLayout_Mensa.setOnRefreshListener { refreshLayout_Mensa.setOnRefreshListener {
updateMensaScreen() updateMensaScreen()
@ -118,23 +128,29 @@ class MensaFragment : Fragment() {
/** /**
* refresh the mensa cache and update the mensa screen * refresh the mensa cache and update the mensa screen
*/ */
private fun updateMensaScreen() = doAsync { private fun updateMensaScreen() = GlobalScope.launch(Dispatchers.Default) {
// update the cache CacheController.updateMensaMenu(context!!).join() // blocking since we want the new data
TCoRAPIController.getMensa(context!!).get() // blocking since we want the new data
CacheController.readMensa(context!!)
uiThread { withContext(Dispatchers.Main) {
// remove all menus from the layout // remove all menus from the layout
linLayout_Mensa.removeAllViews() linLayout_Mensa.removeAllViews()
// add the refreshed menus // add the refreshed menus
val dayCurrent = if (NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex() val dayCurrent = if (NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
addWeek(mensaMenu.currentWeek, dayCurrent).get() addWeek(mensaMenu.currentWeek, dayCurrent).join()
// add the next week // add the next week
addWeek(mensaMenu.nextWeek, 0) addWeek(mensaMenu.nextWeek, 0)
refreshLayout_Mensa.isRefreshing = false refreshLayout_Mensa.isRefreshing = false
// show a info if there are no more menus
if (linLayout_Mensa.childCount == 0) {
val txtViewInfo = TextViewInfo(context!!).set {
txt = resources.getString(R.string.no_more_meals)
}
linLayout_Mensa.addView(txtViewInfo)
}
} }
} }

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2019 <seil0@mosad.xyz> * Copyright 2019-2020 <seil0@mosad.xyz>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -42,8 +42,11 @@ class MoodleFragment : Fragment() {
private lateinit var webSettings: WebSettings private lateinit var webSettings: WebSettings
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_moodle, container, false)
}
val view: View = inflater.inflate(R.layout.fragment_moodle, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
webView = view.findViewById(R.id.webView) webView = view.findViewById(R.id.webView)
webView.loadUrl("https://elearning.hs-offenburg.de/moodle/") webView.loadUrl("https://elearning.hs-offenburg.de/moodle/")
@ -52,7 +55,5 @@ class MoodleFragment : Fragment() {
//webSettings.setJavaScriptEnabled(true) // Enable Javascript //webSettings.setJavaScriptEnabled(true) // Enable Javascript
webView.webViewClient = WebViewClient() // Force links and redirects to open in the WebView instead of in a browser webView.webViewClient = WebViewClient() // Force links and redirects to open in the WebView instead of in a browser
return view
} }
} }

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2019 <seil0@mosad.xyz> * Copyright 2019-2020 <seil0@mosad.xyz>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -29,32 +29,37 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Switch import android.widget.TextView
import androidx.appcompat.widget.SwitchCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.afollestad.aesthetic.Aesthetic import com.afollestad.aesthetic.Aesthetic
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.WhichButton
import com.afollestad.materialdialogs.actions.getActionButton
import com.afollestad.materialdialogs.callbacks.onDismiss
import com.afollestad.materialdialogs.color.colorChooser import com.afollestad.materialdialogs.color.colorChooser
import com.afollestad.materialdialogs.customview.customView import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.list.listItems import com.afollestad.materialdialogs.list.listItems
import com.afollestad.materialdialogs.list.listItemsMultiChoice
import com.afollestad.materialdialogs.list.listItemsSingleChoice import com.afollestad.materialdialogs.list.listItemsSingleChoice
import de.psdev.licensesdialog.LicensesDialog import de.psdev.licensesdialog.LicensesDialog
import kotlinx.android.synthetic.main.fragment_settings.* import kotlinx.android.synthetic.main.fragment_settings.*
import org.jetbrains.anko.doAsync import kotlinx.coroutines.*
import org.jetbrains.anko.uiThread
import org.mosad.seil0.projectlaogai.BuildConfig import org.mosad.seil0.projectlaogai.BuildConfig
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.CacheController import org.mosad.seil0.projectlaogai.controller.cache.CacheController
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.coursesList import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.coursesList
import org.mosad.seil0.projectlaogai.controller.PreferencesController import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorAccent import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorPrimary import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorAccent
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cShowBuffet import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorPrimary
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
import org.mosad.seil0.projectlaogai.hsoparser.DataTypes import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cShowBuffet
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
import org.mosad.seil0.projectlaogai.util.DataTypes
import java.util.* import java.util.*
/** /**
* The settings controller class * The settings controller class
* contains all needed parts to display and the settings screen * contains all needed parts to display and the settings screen
@ -63,19 +68,30 @@ class SettingsFragment : Fragment() {
private lateinit var linLayoutUser: LinearLayout private lateinit var linLayoutUser: LinearLayout
private lateinit var linLayoutCourse: LinearLayout private lateinit var linLayoutCourse: LinearLayout
private lateinit var linLayoutManageLessons: LinearLayout
private lateinit var linLayoutAbout: LinearLayout private lateinit var linLayoutAbout: LinearLayout
private lateinit var linLayoutLicence: LinearLayout private lateinit var linLayoutLicence: LinearLayout
private lateinit var linLayoutTheme: LinearLayout private lateinit var linLayoutTheme: LinearLayout
private lateinit var linLayoutPrimaryColor: LinearLayout private lateinit var linLayoutPrimaryColor: LinearLayout
private lateinit var linLayoutAccentColor: LinearLayout private lateinit var linLayoutAccentColor: LinearLayout
private lateinit var switchBuffet: Switch private lateinit var switchBuffet: SwitchCompat
private lateinit var txtViewCourse: TextView
private var selectedTheme = DataTypes.Theme.Light
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_settings, container, false)
}
val view: View = inflater.inflate(R.layout.fragment_settings, container, false) /**
* initialize the settings gui
*/
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
linLayoutUser = view.findViewById(R.id.linLayout_User) linLayoutUser = view.findViewById(R.id.linLayout_User)
linLayoutCourse = view.findViewById(R.id.linLayout_Course) linLayoutCourse = view.findViewById(R.id.linLayout_Course)
linLayoutManageLessons = view.findViewById(R.id.linLayout_ManageLessons)
linLayoutAbout = view.findViewById(R.id.linLayout_About) linLayoutAbout = view.findViewById(R.id.linLayout_About)
linLayoutLicence = view.findViewById(R.id.linLayout_Licence) linLayoutLicence = view.findViewById(R.id.linLayout_Licence)
linLayoutTheme = view.findViewById(R.id.linLayout_Theme) linLayoutTheme = view.findViewById(R.id.linLayout_Theme)
@ -83,16 +99,13 @@ class SettingsFragment : Fragment() {
linLayoutAccentColor = view.findViewById(R.id.linLayout_AccentColor) linLayoutAccentColor = view.findViewById(R.id.linLayout_AccentColor)
switchBuffet = view.findViewById(R.id.switch_buffet) switchBuffet = view.findViewById(R.id.switch_buffet)
// if we call txtView_Course via KAE view binding it'll result in a NPE in the onDismissed call
txtViewCourse = view.findViewById(R.id.txtView_Course)
initActions() initActions()
// Inflate the layout for this fragment
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { txtView_User.text = EncryptedPreferences.email.ifEmpty { resources.getString(R.string.sample_user) }
super.onViewCreated(view, savedInstanceState)
// initialize the settings gui
txtView_Course.text = cCourse.courseName txtView_Course.text = cCourse.courseName
txtView_AboutDesc.text = resources.getString(R.string.about_version, BuildConfig.VERSION_NAME, getString(R.string.build_time)) txtView_AboutDesc.text = resources.getString(R.string.about_version, BuildConfig.VERSION_NAME, getString(R.string.build_time))
switch_buffet.isChecked = cShowBuffet // init switch switch_buffet.isChecked = cShowBuffet // init switch
@ -102,18 +115,21 @@ class SettingsFragment : Fragment() {
when(outValue.string) { when(outValue.string) {
"light" -> { "light" -> {
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryLight, activity!!.theme)) switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryLight, activity!!.theme))
txtView_SelectedTheme.text = resources.getString(R.string.themeLight) selectedTheme = DataTypes.Theme.Light
selectedTheme.string = resources.getString(R.string.themeLight)
} }
"dark" -> { "dark" -> {
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryDark, activity!!.theme)) switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryDark, activity!!.theme))
txtView_SelectedTheme.text = resources.getString(R.string.themeDark) selectedTheme = DataTypes.Theme.Dark
selectedTheme.string = resources.getString(R.string.themeDark)
} }
"black" -> { "black" -> {
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryDark, activity!!.theme)) switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryDark, activity!!.theme))
txtView_SelectedTheme.text = resources.getString(R.string.themeBlack) selectedTheme = DataTypes.Theme.Black
selectedTheme.string = resources.getString(R.string.themeBlack)
} }
} }
txtView_SelectedTheme.text = selectedTheme.string
} }
/** /**
@ -122,15 +138,51 @@ class SettingsFragment : Fragment() {
private fun initActions() { private fun initActions() {
linLayoutUser.setOnClickListener { linLayoutUser.setOnClickListener {
// open a new dialog // open a new dialog
LoginDialog(context!!)
.positiveButton {
EncryptedPreferences.saveCredentials(email, password, context)
}
.show {
email = EncryptedPreferences.email
password = ""
}
} }
linLayoutUser.setOnLongClickListener { linLayoutUser.setOnLongClickListener {
PreferencesController.oGiants = true // enable easter egg Preferences.oGiants = true // enable easter egg
return@setOnLongClickListener true return@setOnLongClickListener true
} }
linLayoutCourse.setOnClickListener { linLayoutCourse.setOnClickListener {
selectCourse(context!!) selectCourse(context!!).show {
onDismiss {
txtViewCourse.text = cCourse.courseName // update txtView after the dialog is dismissed
}
}
}
linLayoutManageLessons.setOnClickListener {
val lessons = ArrayList<String>()
TimetableController.subjectMap.forEach { pair ->
pair.value.forEach {
lessons.add("${pair.key} - $it")
}
}
MaterialDialog(context!!).show {
title(R.string.manage_lessons)
positiveButton(R.string.delete)
negativeButton(R.string.cancel)
getActionButton(WhichButton.POSITIVE).updateTextColor(cColorAccent)
getActionButton(WhichButton.NEGATIVE).updateTextColor(cColorAccent)
listItemsMultiChoice(items = lessons) { _, _, items ->
items.forEach {
val list = it.split(" - ")
TimetableController.removeSubject(list[0], list[1], context)
}
}
}
} }
linLayoutAbout.setOnClickListener { linLayoutAbout.setOnClickListener {
@ -173,8 +225,10 @@ class SettingsFragment : Fragment() {
resources.getString(R.string.themeDark), resources.getString(R.string.themeDark),
resources.getString(R.string.themeBlack) resources.getString(R.string.themeBlack)
) )
MaterialDialog(context!!).show { MaterialDialog(context!!).show {
listItemsSingleChoice(items = themes) { _, index, _ -> title(R.string.theme)
listItemsSingleChoice(items = themes, initialSelection = selectedTheme.ordinal) { _, index, _ ->
Aesthetic.config { Aesthetic.config {
when (index) { when (index) {
0 -> activityTheme(R.style.AppTheme_Light) 0 -> activityTheme(R.style.AppTheme_Light)
@ -182,6 +236,7 @@ class SettingsFragment : Fragment() {
2 -> activityTheme(R.style.AppTheme_Black) 2 -> activityTheme(R.style.AppTheme_Black)
else -> activityTheme(R.style.AppTheme_Light) else -> activityTheme(R.style.AppTheme_Light)
} }
apply()
} }
} }
} }
@ -190,7 +245,6 @@ class SettingsFragment : Fragment() {
linLayoutPrimaryColor.setOnClickListener { linLayoutPrimaryColor.setOnClickListener {
// open a new color chooser dialog // open a new color chooser dialog
MaterialDialog(context!!) MaterialDialog(context!!)
.title(R.string.primary_color)
.colorChooser(DataTypes().primaryColors, allowCustomArgb = true, initialSelection = cColorPrimary) { _, color -> .colorChooser(DataTypes().primaryColors, allowCustomArgb = true, initialSelection = cColorPrimary) { _, color ->
view_PrimaryColor.setBackgroundColor(color) view_PrimaryColor.setBackgroundColor(color)
Aesthetic.config { Aesthetic.config {
@ -198,18 +252,19 @@ class SettingsFragment : Fragment() {
colorPrimaryDark(color) colorPrimaryDark(color)
apply() apply()
} }
Preferences.saveColorPrimary(context!!, color)
cColorPrimary = color
PreferencesController.save(context!!)
} }
.positiveButton(R.string.select) .show {
.show() title(R.string.primary_color)
positiveButton(R.string.select)
getActionButton(WhichButton.POSITIVE).updateTextColor(cColorAccent)
}
} }
linLayoutAccentColor.setOnClickListener { linLayoutAccentColor.setOnClickListener {
// open a new color chooser dialog // open a new color chooser dialog
MaterialDialog(context!!) MaterialDialog(context!!)
.title(R.string.accent_color)
.colorChooser(DataTypes().accentColors, allowCustomArgb = true, initialSelection = cColorAccent) { _, color -> .colorChooser(DataTypes().accentColors, allowCustomArgb = true, initialSelection = cColorAccent) { _, color ->
view_AccentColor.setBackgroundColor(color) view_AccentColor.setBackgroundColor(color)
Aesthetic.config { Aesthetic.config {
@ -217,56 +272,53 @@ class SettingsFragment : Fragment() {
apply() apply()
} }
cColorAccent = color Preferences.saveColorAccent(context!!, color)
PreferencesController.save(context!!) }
.show{
title(R.string.accent_color)
positiveButton(R.string.select)
getActionButton(WhichButton.POSITIVE).updateTextColor(cColorAccent)
} }
.positiveButton(R.string.select)
.show()
} }
switchBuffet.setOnClickListener { switchBuffet.setOnClickListener {
cShowBuffet = switchBuffet.isChecked Preferences.saveShowBuffet(context!!, switchBuffet.isChecked)
PreferencesController.save(context!!)
println(switchBuffet.isChecked)
} }
} }
fun selectCourse(context: Context) { fun selectCourse(context: Context) : MaterialDialog {
val courseNameList = ArrayList<String>() val courseNameList = ArrayList<String>()
coursesList.forEach { (_, courseName) -> coursesList.forEach { (_, courseName) ->
courseNameList.add(courseName) courseNameList.add(courseName)
} }
// open a new dialog // return a new course selection dialog
MaterialDialog(context) return MaterialDialog(context)
.title(R.string.select_course) .title(R.string.select_course)
.listItems(items = courseNameList) { _, index, _ -> .listItems(items = courseNameList) { _, index, _ ->
val dialog = MaterialDialog(context).cancelable(false) val loadingDialog = MaterialDialog(context).cancelable(false)
.cancelOnTouchOutside(false) .cancelOnTouchOutside(false)
.customView(R.layout.dialog_loading) .customView(R.layout.dialog_loading)
dialog.show() loadingDialog.show()
doAsync { GlobalScope.launch(Dispatchers.Default) {
cCourse = coursesList[index] // set the course Preferences.saveCourse(context, coursesList[index]) // save the course
PreferencesController.save(context)
// update current & next weeks timetable // update current & next weeks timetable
TCoRAPIController.getTimetable(cCourse.courseName, 0, context).get() // blocking since we want the new data val threads = listOf(
TCoRAPIController.getTimetable(cCourse.courseName, 1, context).get() // blocking since we want the new data CacheController.updateTimetable(cCourse.courseName, 0, context),
CacheController.updateTimetable(cCourse.courseName, 1, context)
)
threads.joinAll() // blocking since we want the new data
CacheController.readTimetable(cCourse.courseName, 0, context) withContext(Dispatchers.Main) {
CacheController.readTimetable(cCourse.courseName, 1, context) loadingDialog.dismiss()
uiThread {
dialog.dismiss()
txtView_Course.text = cCourse.courseName // update txtView
} }
} }
} }
.show()
} }
} }

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2019 <seil0@mosad.xyz> * Copyright 2019-2020 <seil0@mosad.xyz>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -28,18 +28,17 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ScrollView import android.widget.ScrollView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.android.synthetic.main.fragment_timetable.* import kotlinx.android.synthetic.main.fragment_timetable.*
import org.jetbrains.anko.doAsync import kotlinx.coroutines.*
import org.jetbrains.anko.uiThread
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.CacheController import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.timetables import org.mosad.seil0.projectlaogai.controller.cache.TimetableController.Companion.timetable
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController import org.mosad.seil0.projectlaogai.uicomponents.dialogs.AddSubjectDialog
import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.TextViewInfo
import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
/** /**
* The timetable controller class * The timetable controller class
@ -48,55 +47,57 @@ import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
class TimeTableFragment : Fragment() { class TimeTableFragment : Fragment() {
private lateinit var scrollViewTimetable: ScrollView private lateinit var scrollViewTimetable: ScrollView
private lateinit var faBtnAddLesson: FloatingActionButton private lateinit var faBtnAddSubject: FloatingActionButton
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_timetable, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val view: View = inflater.inflate(R.layout.fragment_timetable, container, false)
scrollViewTimetable = view.findViewById(R.id.scrollView_Timetable) scrollViewTimetable = view.findViewById(R.id.scrollView_Timetable)
faBtnAddLesson = view.findViewById(R.id.faBtnAddLesson) faBtnAddSubject = view.findViewById(R.id.faBtnAddSubject)
refreshLayout_Timetable.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
// init actions initActions() // init actions
initActions()
if (timetables[0].timetable.days.isNotEmpty() && timetables[1].timetable.days.isNotEmpty()) { if (timetable.size > 1 && timetable[0].days.isNotEmpty() && timetable[1].days.isNotEmpty()) {
addInitWeeks() initTimetable()
} else { } else {
MaterialDialog(context!!) val txtViewInfo = TextViewInfo(context!!).set {
.title(R.string.error) txt = resources.getString(R.string.timetable_generic_error)
.message(R.string.timetable_error) }.showImage()
.show() linLayout_Timetable.addView(txtViewInfo)
} }
return view
} }
/** /**
* initialize the actions * initialize the actions
*/ */
private fun initActions() = doAsync { private fun initActions() = GlobalScope.launch(Dispatchers.Main) {
uiThread { refreshLayout_Timetable.setOnRefreshListener {
refreshLayout_Timetable.setOnRefreshListener { runBlocking { TimetableController.update(context!!).joinAll() }
updateTimetableScreen() reloadTimetableUI()
}
// show the AddLessonDialog if the ftaBtn is clicked
faBtnAddSubject.setOnClickListener {
AddSubjectDialog(context!!)
.positiveButton {
TimetableController.addSubject(selectedCourse, selectedSubject, context)
runBlocking { reloadTimetableUI() }
}.show()
}
// hide the btnCardValue if the user is scrolling down
scrollViewTimetable.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if (scrollY > oldScrollY) {
faBtnAddSubject.hide()
} else {
faBtnAddSubject.show()
} }
faBtnAddLesson.setOnClickListener {
MaterialDialog(context!!)
.title(text = "Vorlesung hinzufügen")
.message(text = "wähle einen Studiengang aus:\n\nWähle eine Vorlesung aus: \n\n Diese Funktion ist noch nicht verfügbar")
.show()
}
// hide the btnCardValue if the user is scrolling down
scrollViewTimetable.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if (scrollY > oldScrollY) {
faBtnAddLesson.hide()
} else {
faBtnAddLesson.show()
}
}
} }
} }
@ -104,54 +105,43 @@ class TimeTableFragment : Fragment() {
/** /**
* add the current and next weeks lessons * add the current and next weeks lessons
*/ */
private fun addInitWeeks() = doAsync { private fun initTimetable() = GlobalScope.launch(Dispatchers.Default) {
val currentDayIndex = NotRetardedCalendar.getDayOfWeekIndex() val currentDayIndex = NotRetardedCalendar.getDayOfWeekIndex()
addWeek(currentDayIndex, 5, 0).get() // add current week addTimetableWeek(currentDayIndex, 5, 0).join() // add current week
addWeek(0, currentDayIndex - 1, 1) // add next week addTimetableWeek(0, currentDayIndex - 1, 1) // add next week
} }
private fun addWeek(startDayIndex: Int, dayEndIndex: Int, weekIndex: Int) = doAsync { private fun addTimetableWeek(dayBegin: Int, dayEnd: Int, week: Int) = GlobalScope.launch(Dispatchers.Main) {
val timetable = timetables[weekIndex].timetable for (dayIndex in dayBegin..dayEnd) {
val timetableMeta = timetables[weekIndex].meta val dayCardView = DayCardView(context!!)
uiThread { // some wired calendar magic, calculate the correct date to be shown
// ((timetable week - current week * 7) + (dayIndex - dayIndex of current week)
val daysToAdd = ((timetable[week].weekNumberYear - NotRetardedCalendar.getWeekOfYear())
* 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex()))
dayCardView.addTimetableDay(timetable[week].days[dayIndex], daysToAdd)
for (dayIndex in startDayIndex..dayEndIndex) { // if there are no lessons don't show the dayCardView
val dayCardView = DayCardView(context!!) if (dayCardView.getLinLayoutDay().childCount > 1)
linLayout_Timetable.addView(dayCardView)
// some wired calendar magic, calculate the correct date to be shown ((timetable week - current week * 7) + (dayIndex - dayIndex of current week)
val daysToAdd =(timetableMeta.weekNumberYear - NotRetardedCalendar.getWeekOfYear()) * 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex())
dayCardView.addTimetableDay(timetable.days[dayIndex], daysToAdd)
// if there are no lessons don't show the dayCardView
if (dayCardView.getLinLayoutDay().childCount > 1)
linLayout_Timetable.addView(dayCardView)
}
} }
} }
private fun updateTimetableScreen() = doAsync { /**
// update the cache * clear linLayout_Timetable, add the updated timetable
TCoRAPIController.getTimetable(cCourse.courseName, 0, context!!).get() // blocking since we want the new data */
TCoRAPIController.getTimetable(cCourse.courseName, 1, context!!).get() // blocking since we want the new data private fun reloadTimetableUI() = GlobalScope.launch(Dispatchers.Default) {
withContext(Dispatchers.Main) {
CacheController.readTimetable(cCourse.courseName, 0, context!!) // remove all lessons from the layout
CacheController.readTimetable(cCourse.courseName, 1, context!!)
uiThread {
// remove all menus from the layout
linLayout_Timetable.removeAllViews() linLayout_Timetable.removeAllViews()
// add the refreshed timetables // add the refreshed timetables
val dayIndex = NotRetardedCalendar.getDayOfWeekIndex() val dayIndex = NotRetardedCalendar.getDayOfWeekIndex()
// add current week addTimetableWeek(dayIndex, 5, 0).join() // add current week
addWeek(dayIndex, 5, 0).get() addTimetableWeek(0, dayIndex - 1, 1) // add next week
// add next week
addWeek(0, dayIndex - 1, 1)
refreshLayout_Timetable.isRefreshing = false refreshLayout_Timetable.isRefreshing = false
} }

View File

@ -0,0 +1,59 @@
/**
* 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.onboarding
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.viewpager.widget.PagerAdapter
import org.mosad.seil0.projectlaogai.OnboardingActivity.Companion.layouts
/**
* a PagerAdapter
*/
class ViewPagerAdapter(cont: Context) : PagerAdapter() {
private val context = cont
override fun isViewFromObject(view: View, `object`: Any): Boolean {
return view === `object`
}
override fun getCount(): Int {
return layouts.size
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val view = LayoutInflater.from(context).inflate(layouts[position], container, false)
container.addView(view)
return view
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
val view = `object` as View
container.removeView(view)
}
}

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2019 <seil0@mosad.xyz> * Copyright 2019-2020 <seil0@mosad.xyz>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -28,8 +28,8 @@ import android.widget.LinearLayout
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import kotlinx.android.synthetic.main.cardview_day.view.* import kotlinx.android.synthetic.main.cardview_day.view.*
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.hsoparser.DataTypes import org.mosad.seil0.projectlaogai.util.DataTypes
import org.mosad.seil0.projectlaogai.hsoparser.TimetableDay import org.mosad.seil0.projectlaogai.util.TimetableDay
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -65,13 +65,12 @@ class DayCardView(context: Context) : CardView(context) {
txtView_DayHeading.text = formatter.format(cal.time) txtView_DayHeading.text = formatter.format(cal.time)
// for every timeslot of that timetable // for every timeslot of that timetable
for ((tsIndex, timeslot) in timetable.timeslots.withIndex()) { timetable.timeslots.forEachIndexed { tsIndex, timeslot ->
timeslot.forEach { lesson ->
for (lesson in timeslot) {
if (lesson.lessonSubject.isNotEmpty()) { if (lesson.lessonSubject.isNotEmpty()) {
val lessonLayout = LessonLinearLayout(context) val lessonLayout = LessonLinearLayout(context)
lessonLayout.setLesson(lesson, DataTypes().getTime()[tsIndex]) lessonLayout.setLesson(lesson, DataTypes().times[tsIndex])
linLayout_Day.addView(lessonLayout) linLayout_Day.addView(lessonLayout)
if (lesson != timeslot.last()) { if (lesson != timeslot.last()) {
@ -81,6 +80,7 @@ class DayCardView(context: Context) : CardView(context) {
lastLesson = lessonLayout lastLesson = lessonLayout
} }
} }
} }
lastLesson.disableDivider() // disable the divider for the last lesson of the day lastLesson.disableDivider() // disable the divider for the last lesson of the day

View File

@ -0,0 +1,58 @@
/**
* 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.uicomponents
import android.content.Context
import android.view.View
import android.widget.LinearLayout
import kotlinx.android.synthetic.main.linearlayout_grade.view.*
import org.mosad.seil0.projectlaogai.R
class GradeLinearLayout(context: Context?): LinearLayout(context) {
var subjectName = ""
var grade = ""
var subSubjectName = ""
var subGrade = ""
init {
inflate(context, R.layout.linearlayout_grade, this)
}
fun set(func: GradeLinearLayout.() -> Unit): GradeLinearLayout = apply {
func()
txtView_subject.text = subjectName
txtView_grade.text = grade
txtView_sub_subject.text = subSubjectName
txtView_sub_grade.text = subGrade
}
fun disableDivider() {
divider_grade.visibility = View.GONE
}
fun disableSubSubject() {
linLayout_sub_subject.visibility = View.GONE
}
}

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2019 <seil0@mosad.xyz> * Copyright 2019-2020 <seil0@mosad.xyz>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -25,15 +25,14 @@ package org.mosad.seil0.projectlaogai.uicomponents
import android.content.Context import android.content.Context
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.cardview.widget.CardView
import kotlinx.android.synthetic.main.linearlayout_lesson.view.* import kotlinx.android.synthetic.main.linearlayout_lesson.view.*
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.hsoparser.Lesson import org.mosad.seil0.projectlaogai.util.Lesson
class LessonLinearLayout(context: Context?) : LinearLayout(context) { class LessonLinearLayout(context: Context?) : LinearLayout(context) {
init { init {
CardView.inflate(context, R.layout.linearlayout_lesson, this) inflate(context, R.layout.linearlayout_lesson, this)
} }
fun setLesson(lesson: Lesson, time: String) { fun setLesson(lesson: Lesson, time: String) {

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2019 <seil0@mosad.xyz> * Copyright 2019-2020 <seil0@mosad.xyz>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -25,15 +25,14 @@ package org.mosad.seil0.projectlaogai.uicomponents
import android.content.Context import android.content.Context
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.cardview.widget.CardView
import kotlinx.android.synthetic.main.linearlayout_meal.view.* import kotlinx.android.synthetic.main.linearlayout_meal.view.*
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.hsoparser.Meal import org.mosad.seil0.projectlaogai.util.Meal
class MealLinearLayout(context: Context?): LinearLayout(context) { class MealLinearLayout(context: Context?): LinearLayout(context) {
init { init {
CardView.inflate(context, R.layout.linearlayout_meal, this) inflate(context, R.layout.linearlayout_meal, this)
} }
fun setMeal(meal: Meal) { fun setMeal(meal: Meal) {

View File

@ -0,0 +1,63 @@
/**
* 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.uicomponents
import android.content.Context
import android.graphics.Rect
import android.view.View
import android.widget.LinearLayout
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.content.res.ResourcesCompat
import org.mosad.seil0.projectlaogai.R
class TextViewInfo(context: Context?): AppCompatTextView(context!!) {
var txt = ""
var txtSize = 15F
var txtAlignment = View.TEXT_ALIGNMENT_CENTER
var drawable = R.drawable.ic_error_outline_black_24dp
var params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
init {
params.setMargins(0,200,0,0)
}
fun set(func: TextViewInfo.() -> Unit): TextViewInfo = apply {
func()
text = txt
layoutParams = params
textSize = txtSize
textAlignment = txtAlignment
}
fun showImage(): TextViewInfo = apply {
val img = ResourcesCompat.getDrawable(resources, drawable, context.theme)?.apply {
bounds = Rect(0, 0, 75, 75)
}
setCompoundDrawables(null, null, null, img)
}
}

View File

@ -0,0 +1,164 @@
/**
* 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.uicomponents.dialogs
import android.content.Context
import android.os.Build
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Spinner
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.WhichButton
import com.afollestad.materialdialogs.actions.getActionButton
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
import com.afollestad.materialdialogs.bottomsheets.setPeekHeight
import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.customview.getCustomView
import kotlinx.coroutines.runBlocking
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.util.Course
import java.util.stream.Collectors
/**
* This class creates a new AddLessonDialog.
*/
class AddSubjectDialog(val context: Context) {
private val dialog = MaterialDialog(context, BottomSheet())
private val spinnerCourses: Spinner
private val spinnerSubjects: Spinner
private val subjectsList = ArrayList<String>()
private val courseNamesList = getCourseNames()
var selectedCourse = ""
var selectedSubject = ""
init {
dialog.title(R.string.add_lesson)
.message(R.string.add_lesson_desc)
.customView(R.layout.dialog_add_lesson)
.positiveButton(R.string.add)
.negativeButton(R.string.cancel)
.setPeekHeight(900)
spinnerCourses = dialog.getCustomView().findViewById(R.id.spinner_Courses)
spinnerSubjects = dialog.getCustomView().findViewById(R.id.spinner_Lessons)
// fix not working accent color
dialog.getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.cColorAccent)
dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.cColorAccent)
initSpinners()
}
fun positiveButton(func: AddSubjectDialog.() -> Unit): AddSubjectDialog = apply {
dialog.positiveButton {
func()
}
}
fun show() {
dialog.show()
}
fun show(func: AddSubjectDialog.() -> Unit): AddSubjectDialog = apply {
func()
this.show()
}
private fun initSpinners() {
setArrayAdapter(spinnerCourses, courseNamesList)
val lessonsAdapter = setArrayAdapter(spinnerSubjects, subjectsList)
// don't call onItemSelected() on spinnerCourses.onItemSelectedListener
spinnerCourses.setSelection(0,false)
spinnerCourses.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, pos: Int, id: Long) {
selectedCourse = parent.getItemAtPosition(pos).toString()
// TODO show loading dialog while loading
val lessonSubjects = runBlocking {
TCoRAPIController.getSubjectListAsync(parent.getItemAtPosition(pos).toString(), 0).await()
}
lessonsAdapter.clear()
lessonsAdapter.addAll(lessonSubjects)
lessonsAdapter.notifyDataSetChanged()
}
override fun onNothingSelected(parent: AdapterView<*>) {
// Another interface callback
}
}
// don't call onItemSelected() on spinnerCourses.onItemSelectedListener
spinnerSubjects.setSelection(0,false)
spinnerSubjects.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, pos: Int, id: Long) {
selectedSubject = parent.getItemAtPosition(pos).toString()
}
override fun onNothingSelected(parent: AdapterView<*>) {
// Another interface callback
}
}
}
/**
* set a new ArrayAdapter for a spinner with a list
* @param spinner the spinner you wish to set the adapter for
* @param list the list to set the adapter to
*/
private fun setArrayAdapter(spinner: Spinner, list: List<String>): ArrayAdapter<String> {
return ArrayAdapter(context, android.R.layout.simple_spinner_item, list)
.also { adapter ->
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = adapter
}
}
/**
* get all course names from coursesList
* @return a list, containing all course names
*/
private fun getCourseNames(): List<String> {
val coursesNameList: List<String>
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
coursesNameList = CacheController.coursesList.stream().map(Course::courseName).collect(
Collectors.toList())
} else {
coursesNameList = ArrayList()
CacheController.coursesList.forEach { course ->
coursesNameList.add(course.courseName)
}
}
return coursesNameList
}
}

View File

@ -0,0 +1,87 @@
/**
* 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.uicomponents.dialogs
import android.content.Context
import android.widget.EditText
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.WhichButton
import com.afollestad.materialdialogs.actions.getActionButton
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
import com.afollestad.materialdialogs.bottomsheets.setPeekHeight
import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.customview.getCustomView
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
class LoginDialog(val context: Context) {
private val dialog = MaterialDialog(context, BottomSheet())
private val editTextEmail: EditText
private val editTextPassword: EditText
var email = ""
var password = ""
init {
dialog.title(R.string.login_heading)
.message(R.string.login_desc_on)
.customView(R.layout.dialog_login)
.positiveButton(R.string.save)
.negativeButton(R.string.cancel)
.setPeekHeight(900)
editTextEmail = dialog.getCustomView().findViewById(R.id.editText_email)
editTextPassword = dialog.getCustomView().findViewById(R.id.editText_password)
// fix not working accent color
dialog.getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.cColorAccent)
dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.cColorAccent)
}
fun positiveButton(func: LoginDialog.() -> Unit): LoginDialog = apply {
dialog.positiveButton {
email = editTextEmail.text.toString()
password = editTextPassword.text.toString()
func()
}
}
fun negativeButton(func: LoginDialog.() -> Unit): LoginDialog = apply {
dialog.negativeButton {
func()
}
}
fun show(func: LoginDialog.() -> Unit): LoginDialog = apply {
func()
editTextEmail.setText(email)
editTextPassword.setText(password)
dialog.show()
}
}

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2019 <seil0@mosad.xyz> * Copyright 2019-2020 <seil0@mosad.xyz>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -20,15 +20,13 @@
* *
*/ */
package org.mosad.seil0.projectlaogai.hsoparser package org.mosad.seil0.projectlaogai.util
import android.graphics.Color import android.graphics.Color
import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class DataTypes { class DataTypes {
val times = val times = arrayOf("8.00 - 9.30", "9.45 - 11.15", "11.35 - 13.05", "14.00 -15.30", "15.45 - 17.15", "17.30 - 19.00")
arrayOf("8.00 - 9.30", "9.45 - 11.15", "11.35 - 13.05", "14.00 -15.30", "15.45 - 17.15", "17.30 - 19.00")
val primaryColors = intArrayOf( val primaryColors = intArrayOf(
Color.parseColor("#E53935"), Color.parseColor("#E53935"),
@ -60,7 +58,7 @@ class DataTypes {
Color.parseColor("#3F51B5"), Color.parseColor("#3F51B5"),
Color.parseColor("#3D5AFE"), Color.parseColor("#3D5AFE"),
Color.parseColor("#2979FF"), Color.parseColor("#2979FF"),
Color.parseColor("#00B0FF"), Color.parseColor("#0096FF"),
Color.parseColor("#00E5FF"), Color.parseColor("#00E5FF"),
Color.parseColor("#1DE9B6"), Color.parseColor("#1DE9B6"),
Color.parseColor("#00E676"), Color.parseColor("#00E676"),
@ -72,58 +70,20 @@ class DataTypes {
Color.parseColor("#000000") Color.parseColor("#000000")
) )
init { enum class Theme(var string: String) {
// do something Light("Light"),
} Dark("Dark"),
Black("Black")
fun getTime(): Array<String> {
return times
}
}
class NotRetardedCalendar {
companion object {
private val calendar = Calendar.getInstance()
fun getDayOfWeekIndex(): Int {
return when (calendar.get(Calendar.DAY_OF_WEEK)) {
Calendar.MONDAY -> 0
Calendar.TUESDAY -> 1
Calendar.WEDNESDAY -> 2
Calendar.THURSDAY -> 3
Calendar.FRIDAY -> 4
Calendar.SATURDAY -> 5
Calendar.SUNDAY -> 6
else -> 7
}
}
fun getTomorrowWeekIndex(): Int {
return when (calendar.get(Calendar.DAY_OF_WEEK)) {
Calendar.MONDAY -> 1
Calendar.TUESDAY -> 2
Calendar.WEDNESDAY -> 3
Calendar.THURSDAY -> 4
Calendar.FRIDAY -> 5
Calendar.SATURDAY -> 6
Calendar.SUNDAY -> 0
else -> 7
}
}
fun getWeekOfYear(): Int {
return when (calendar.get(Calendar.DAY_OF_WEEK)) {
Calendar.SUNDAY -> Calendar.getInstance().get(Calendar.WEEK_OF_YEAR) - 1
else -> Calendar.getInstance().get(Calendar.WEEK_OF_YEAR)
}
}
} }
} }
// data classes for the course part // data classes for the course part
data class Course(val courseLink: String, val courseName: String) data class Course(val courseLink: String, val courseName: String)
data class CoursesMeta(val updateTime: Long = 0, val totalCourses: Int = 0)
data class CoursesList(val meta: CoursesMeta = CoursesMeta(), val courses: ArrayList<Course> = ArrayList())
// data classes for the Mensa part // data classes for the Mensa part
data class Meal(val day: String, val heading: String, val parts: ArrayList<String>, val additives: String) data class Meal(val day: String, val heading: String, val parts: ArrayList<String>, val additives: String)
@ -137,16 +97,23 @@ data class MensaMenu(val meta: MensaMeta = MensaMeta(), val currentWeek: MensaWe
// data classes for the timetable part // data classes for the timetable part
data class Lesson( data class Lesson(
val lessonID: String,
val lessonSubject: String, val lessonSubject: String,
val lessonTeacher: String, val lessonTeacher: String,
val lessonRoom: String, val lessonRoom: String,
val lessonRemark: String val lessonRemark: String
) )
data class TimetableDay(val timeslots: Array<ArrayList<Lesson>> = Array(6) { ArrayList<Lesson>() }) data class TimetableDay(val timeslots: Array<ArrayList<Lesson>> = Array(6) { ArrayList() })
data class TimetableWeek(val days: Array<TimetableDay> = Array(6) { TimetableDay() }) data class TimetableWeek(val weekIndex: Int = 0, val weekNumberYear: Int = 0, val days: Array<TimetableDay> = Array(6) { TimetableDay() })
// data classes for the qispos part
data class GradeSubject(val id: String = "", val name: String = "", val semester: String = "", val grade: String = "", val credits: String = "")
// TCoR
data class TimetableWeekTCoR(val days: Array<TimetableDay> = Array(6) { TimetableDay() })
data class TimetableCourseMeta(val updateTime: Long = 0, val courseName: String = "", val weekIndex: Int = 0, val weekNumberYear: Int = 0, val link: String = "") data class TimetableCourseMeta(val updateTime: Long = 0, val courseName: String = "", val weekIndex: Int = 0, val weekNumberYear: Int = 0, val link: String = "")
data class TimetableCourseWeek(val meta: TimetableCourseMeta = TimetableCourseMeta(), var timetable: TimetableWeek = TimetableWeek()) data class TimetableCourseWeek(val meta: TimetableCourseMeta = TimetableCourseMeta(), var timetable: TimetableWeekTCoR = TimetableWeekTCoR())

View File

@ -0,0 +1,64 @@
/**
* 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.util
import java.util.*
class NotRetardedCalendar {
companion object {
private val calendar = Calendar.getInstance()
fun getDayOfWeekIndex(): Int {
return when (calendar.get(Calendar.DAY_OF_WEEK)) {
Calendar.MONDAY -> 0
Calendar.TUESDAY -> 1
Calendar.WEDNESDAY -> 2
Calendar.THURSDAY -> 3
Calendar.FRIDAY -> 4
Calendar.SATURDAY -> 5
Calendar.SUNDAY -> 6
else -> 7
}
}
fun getTomorrowWeekIndex(): Int {
return when (calendar.get(Calendar.DAY_OF_WEEK)) {
Calendar.MONDAY -> 1
Calendar.TUESDAY -> 2
Calendar.WEDNESDAY -> 3
Calendar.THURSDAY -> 4
Calendar.FRIDAY -> 5
Calendar.SATURDAY -> 6
Calendar.SUNDAY -> 0
else -> 7
}
}
fun getWeekOfYear(): Int {
return when (calendar.get(Calendar.DAY_OF_WEEK)) {
Calendar.SUNDAY -> Calendar.getInstance().get(Calendar.WEEK_OF_YEAR) - 1
else -> Calendar.getInstance().get(Calendar.WEEK_OF_YEAR)
}
}
}
}

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/colorPrimary"/> <item android:drawable="@android:color/black"/>
<item android:gravity="center" android:width="144dp" android:height="144dp"> <item android:gravity="center" android:width="144dp" android:height="144dp">
<bitmap <bitmap
android:gravity="fill_horizontal|fill_vertical" android:gravity="fill_horizontal|fill_vertical"
android:src="@mipmap/ic_laogai_icon_splash"/> android:src="@drawable/ic_splash_logo"/>
</item> </item>
</layer-list> </layer-list>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M4,7h16v2H4V7zM4,13h16v-2H4V13zM4,17h7v-2H4V17zM4,21h7v-2H4V21zM15.41,18.17L14,16.75l-1.41,1.41L15.41,21L20,16.42L18.58,15L15.41,18.17zM4,3v2h16V3H4z"
android:fillColor="#000000"/>
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
</vector>

View File

@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
android:height="24dp"
android:width="24dp" android:width="24dp"
android:viewportHeight="24.0" android:height="24dp"
android:viewportWidth="24.0"> android:autoMirrored="true"
<path android:fillColor="#FF000000" android:viewportWidth="24.0"
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/> android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z" />
</vector> </vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -1,9 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="135"
android:centerColor="?colorPrimary"
android:endColor="?colorPrimaryDark"
android:startColor="?colorPrimary"
android:type="linear"/>
</shape>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</androidx.viewpager.widget.ViewPager>
<LinearLayout
android:id="@+id/linLayout_Dots"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
android:gravity="center"
android:orientation="horizontal" />
<View
android:id="@+id/view"
android:layout_width="wrap_content"
android:layout_height="1dp"
android:layout_above="@id/linLayout_Dots"
android:alpha="0.5"
android:background="@android:color/white" />
<Button
android:id="@+id/btn_Next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:background="@null"
android:onClick="btnNextClick"
android:text="@string/next"
android:visibility="gone" />
<Button
android:id="@+id/btn_Skip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="true"
android:background="@null"
android:onClick="btnSkipClick"
android:text="@string/skip"
tools:visibility="gone" />
</RelativeLayout>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linLayout_login"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingStart="24dp"
android:paddingEnd="24dp">
<EditText
android:id="@+id/editText_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:ems="10"
android:hint="@string/email"
android:importantForAutofill="no"
android:inputType="textEmailAddress" />
<EditText
android:id="@+id/editText_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:ems="10"
android:hint="@string/password"
android:importantForAutofill="no"
android:inputType="textPassword" />
</LinearLayout>

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linLayout_grade"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="7dp"
android:paddingTop="2dp"
android:paddingRight="7dp"
android:paddingBottom="3dp">
<LinearLayout
android:id="@+id/linLayout_subject"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/txtView_subject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/sample_subject"
android:textSize="15sp" />
<TextView
android:id="@+id/txtView_grade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:text="@string/sample_grade"
android:textAlignment="textEnd"
android:textSize="15sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/linLayout_sub_subject"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:paddingStart="7dp"
android:paddingTop="3dp"
android:paddingEnd="0dp">
<TextView
android:id="@+id/txtView_sub_subject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/sample_sub_subject" />
<TextView
android:id="@+id/txtView_sub_grade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:text="@string/sample_grade_state"
android:textAlignment="textEnd" />
</LinearLayout>
<View
android:id="@+id/divider_grade"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="3dp"
android:background="?dividerColor" />
</LinearLayout>

View File

@ -28,7 +28,7 @@
android:layout_weight="1" android:layout_weight="1"
android:gravity="end" android:gravity="end"
android:text="@string/a_time" android:text="@string/a_time"
android:textColor="@color/textSecondary" /> android:textColor="@color/textSecondaryLight" />
</LinearLayout> </LinearLayout>
<TextView <TextView

View File

@ -1,38 +1,40 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_header_main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/nav_header_height" android:layout_height="@dimen/nav_header_height"
android:background="@color/colorPrimary" android:background="@android:color/black"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:theme="@style/ThemeOverlay.AppCompat.Dark"
android:orientation="vertical"
android:gravity="bottom" android:gravity="bottom"
android:id="@+id/nav_header_main"> android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<ImageView <ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="@dimen/nav_header_vertical_spacing"
app:srcCompat="@mipmap/ic_laogai_icon"
android:contentDescription="@string/app_name" android:contentDescription="@string/app_name"
android:id="@+id/imageView"/> android:paddingTop="@dimen/nav_header_vertical_spacing"
app:srcCompat="@mipmap/ic_laogai_icon" />
<TextView <TextView
android:id="@+id/txtView_nav_header_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="@dimen/nav_header_vertical_spacing" android:paddingTop="@dimen/nav_header_vertical_spacing"
android:text="@string/nav_header_title" android:text="@string/nav_header_title"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:id="@+id/txtView_nav_header_title"/> android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textColor="#FFFFFF" />
<TextView <TextView
android:id="@+id/textView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/nav_header_subtitle" android:text="@string/nav_header_subtitle"
android:id="@+id/textView"/> android:textColor="#ffffff" />
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,43 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:shortcutId="mensa"
android:enabled="true"
android:icon="@drawable/ic_local_dining_black_24dp"
android:shortcutShortLabel="@string/shortcut_mensa_short"
android:shortcutLongLabel="@string/shortcut_mensa_long"
android:shortcutDisabledMessage="@string/shortcut_mensa_disabled">
<intent
android:action="org.mosad.seil0.projectlaogai.fragments.MensaFragment"
android:targetPackage="org.mosad.seil0.projectlaogai"
android:targetClass="org.mosad.seil0.projectlaogai.MainActivity" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
<shortcut
android:shortcutId="timetable"
android:enabled="true"
android:icon="@drawable/ic_baseline_calendar_today_24dp"
android:shortcutShortLabel="@string/shortcut_timetable_short"
android:shortcutLongLabel="@string/shortcut_timetable_long"
android:shortcutDisabledMessage="@string/shortcut_timetable_disabled">
<intent
android:action="org.mosad.seil0.projectlaogai.fragments.TimeTableFragment"
android:targetPackage="org.mosad.seil0.projectlaogai"
android:targetClass="org.mosad.seil0.projectlaogai.MainActivity" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
<shortcut
android:shortcutId="moodle"
android:enabled="true"
android:icon="@drawable/ic_school_black_24dp"
android:shortcutShortLabel="@string/shortcut_moodle_short"
android:shortcutLongLabel="@string/shortcut_moodle_long"
android:shortcutDisabledMessage="@string/shortcut_moodle_disabled">
<intent
android:action="org.mosad.seil0.projectlaogai.fragments.MoodleFragment"
android:targetPackage="org.mosad.seil0.projectlaogai"
android:targetClass="org.mosad.seil0.projectlaogai.MainActivity" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
</shortcuts>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linLayout_addlesson"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingStart="24dp"
android:paddingEnd="24dp">
<TextView
android:id="@+id/txtView_Course"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="7dp"
android:text="@string/courses"
android:textSize="16sp" />
<Spinner
android:id="@+id/spinner_Courses"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/courses"
android:paddingBottom="10dp"
android:popupBackground="?themeSecondary"
android:spinnerMode="dropdown" />
<TextView
android:id="@+id/txtView_Lesson"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="7dp"
android:text="@string/lessons"
android:textSize="16sp" />
<Spinner
android:id="@+id/spinner_Lessons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/lessons"
android:paddingBottom="10dp"
android:popupBackground="?themeSecondary" />
</LinearLayout>

View File

@ -20,7 +20,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="7dp" android:layout_marginTop="7dp"
android:layout_marginBottom="7dp" android:layout_marginBottom="7dp"
android:text="16,95 €" android:text="@string/mensa_current"
android:textAlignment="center" android:textAlignment="center"
android:textSize="20sp" /> android:textSize="20sp" />

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.GradesFragment">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/refreshLayout_Grades"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?themePrimary">
<ScrollView
android:id="@+id/scrollView_Grades"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/linLayout_Grades"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/txtView_Loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="100dp"
android:padding="7dp"
android:text="@string/loading_from_hs"
android:textAlignment="center"
android:textSize="15sp" />
</LinearLayout>
</ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</FrameLayout>

View File

@ -1,26 +1,27 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".fragments.MensaFragment" tools:context=".fragments.MensaFragment"
android:background="?themePrimary"> android:background="?themePrimary">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:layout_width="match_parent" android:id="@+id/refreshLayout_Mensa"
android:layout_height="match_parent" android:layout_width="match_parent"
android:id="@+id/refreshLayout_Mensa"> android:layout_height="match_parent">
<ScrollView <ScrollView
android:layout_width="match_parent" android:id="@+id/scrollView_Mensa"
android:layout_height="match_parent" android:layout_width="match_parent"
android:id="@+id/scrollView_Mensa"> android:layout_height="match_parent">
<LinearLayout <LinearLayout
android:orientation="vertical" android:id="@+id/linLayout_Mensa"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:animateLayoutChanges="false" android:animateLayoutChanges="false"
android:id="@+id/linLayout_Mensa"/> android:orientation="vertical" />
</ScrollView> </ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imgCourse"
android:layout_width="256dp"
android:layout_height="256dp"
android:contentDescription="@string/timetable"
android:scaleType="fitCenter"
app:layout_constraintBottom_toTopOf="@+id/linearLayout2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_baseline_calendar_today_24dp" />
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="60dp"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/txtView_Course"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/course_heading"
android:textAlignment="center"
android:textSize="26sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtView_CourseDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:text="@string/course_desc_on"
android:textAlignment="center"
android:textSize="18sp" />
<Button
android:id="@+id/btnSelectCourse"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:onClick="btnSelectCourseClick"
android:text="@string/select_course"
android:textColor="#FFFFFFFF"
android:textStyle="bold" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imgGrades"
android:layout_width="256dp"
android:layout_height="256dp"
android:layout_marginBottom="8dp"
android:contentDescription="@string/app_name"
android:scaleType="fitCenter"
app:layout_constraintBottom_toTopOf="@+id/linearLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_school_black_24dp" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="60dp"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/txtView_Grades"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/login_heading"
android:textAlignment="center"
android:textSize="26sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtView_GradesDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:text="@string/login_desc_on"
android:textAlignment="center"
android:textSize="18sp" />
<EditText
android:id="@+id/editText_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:ems="10"
android:hint="@string/email"
android:importantForAutofill="no"
android:inputType="textEmailAddress" />
<EditText
android:id="@+id/editText_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:ems="10"
android:hint="@string/password"
android:importantForAutofill="no"
android:inputType="textPassword" />
<Button
android:id="@+id/btnLogin"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:onClick="btnLoginClick"
android:text="@string/login"
android:textColor="#FFFFFFFF"
android:textStyle="bold" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/logo"
android:layout_width="256dp"
android:layout_height="256dp"
android:contentDescription="@string/app_name"
android:scaleType="fitCenter"
app:layout_constraintBottom_toTopOf="@+id/linearLayout3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@mipmap/ic_laogai_icon_foreground" />
<LinearLayout
android:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="60dp"
android:orientation="vertical"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/txtAppName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textAlignment="center"
android:textSize="26sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtWelcome"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:text="@string/welcome"
android:textAlignment="center"
android:textSize="18sp" />
<Button
android:id="@+id/btnGetStarted"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:onClick="btnNextClick"
android:text="@string/get_started"
android:textColor="#FFFFFFFF"
android:textStyle="bold" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -1,280 +1,345 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".fragments.SettingsFragment" android:background="?themePrimary"
android:background="?themePrimary"> tools:context=".fragments.SettingsFragment">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout <LinearLayout
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:id="@+id/cardView_Info"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
> android:layout_marginTop="9dp"
<androidx.cardview.widget.CardView app:cardBackgroundColor="?themeSecondary"
app:cardElevation="5dp"
app:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="9dp" android:orientation="vertical">
android:id="@+id/cardView_Info"
app:cardElevation="5dp"
app:cardUseCompatPadding="true"
app:cardBackgroundColor="?themeSecondary">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView <TextView
android:text="@string/info" android:id="@+id/txtView_Info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:fontFamily="sans-serif"
android:text="@string/info"
android:textColor="?colorAccent"
android:textSize="18sp"
android:textStyle="bold"
android:typeface="sans" />
<LinearLayout
android:id="@+id/linLayout_User"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="7dp">
<TextView
android:id="@+id/txtView_User"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/txtView_Info" android:text="@string/sample_user"
android:typeface="sans" android:textSize="16sp"
android:textSize="18sp" android:textStyle="bold" />
android:textStyle="bold"
android:fontFamily="sans-serif" <TextView
android:layout_margin="7dp" android:id="@+id/txtView_UserDesc"
android:textColor="?colorAccent"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="7dp" android:text="@string/user_desc" />
android:id="@+id/linLayout_User">
<TextView
android:text="@string/sample_user"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtView_User"
android:textSize="16sp"
android:textStyle="bold"
/>
<TextView
android:text="@string/user"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtView_UserDesc"/>
</LinearLayout> </LinearLayout>
<View <View
android:id="@+id/divider2" android:id="@+id/divider2"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:background="?dividerColor" android:background="?dividerColor" />
/>
<LinearLayout <LinearLayout
android:orientation="vertical" android:id="@+id/linLayout_Course"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:orientation="vertical">
<TextView
android:id="@+id/txtView_Course"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="7dp" android:text="@string/sample_course"
android:id="@+id/linLayout_Course"> android:textSize="16sp"
android:textStyle="bold" />
<TextView <TextView
android:text="@string/sample_course" android:id="@+id/txtView_CourseDesc"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/txtView_Course" android:text="@string/course_desc" />
android:textSize="16sp"
android:textStyle="bold"/>
<TextView
android:text="@string/course_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtView_CourseDesc"/>
</LinearLayout> </LinearLayout>
<View <View
android:id="@+id/divider" android:id="@+id/divider"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:background="?dividerColor" android:background="?dividerColor" />
/>
<LinearLayout <LinearLayout
android:orientation="vertical" android:id="@+id/linLayout_ManageLessons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:orientation="vertical">
<TextView
android:id="@+id/txtView_ManageLessons"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_margin="7dp" android:text="@string/manage_lessons"
android:id="@+id/linLayout_About"> android:textSize="16sp"
android:textStyle="bold" />
<TextView <TextView
android:text="@string/about_txtView" android:id="@+id/txtView_ManageLessonsDesc"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/txtView_About" android:text="@string/manage_lessons_desc" />
android:textStyle="bold"
android:textSize="16sp"
/>
<TextView
android:text="@string/about_version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtView_AboutDesc"/>
</LinearLayout> </LinearLayout>
<View <View
android:id="@+id/divider6" android:id="@+id/divider7"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:background="?dividerColor" android:background="?dividerColor" />
/>
<LinearLayout <LinearLayout
android:orientation="vertical" android:id="@+id/linLayout_About"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:id="@+id/linLayout_Licence" android:layout_height="match_parent"
android:layout_margin="7dp"> android:layout_margin="7dp"
android:orientation="vertical">
<TextView <TextView
android:text="@string/licenses" android:id="@+id/txtView_About"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/textView3" android:layout_height="wrap_content"
android:textStyle="bold" android:textSize="16sp"/> android:text="@string/about_txtView"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtView_AboutDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/about_version" />
</LinearLayout>
<View
android:id="@+id/divider6"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?dividerColor" />
<LinearLayout
android:id="@+id/linLayout_Licence"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="7dp"
android:orientation="vertical">
<TextView
android:id="@+id/textView3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/licenses"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/cardView_Settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="?themeSecondary"
app:cardElevation="5dp"
app:cardUseCompatPadding="true">
<LinearLayout
android:id="@+id/linLayout_Settings"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/cardView_Settings" android:orientation="vertical">
app:cardElevation="5dp"
app:cardUseCompatPadding="true" <TextView
app:cardBackgroundColor="?themeSecondary"> android:id="@+id/txtView_Settings"
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/linLayout_Settings" android:layout_margin="7dp"
> android:paddingTop="3dp"
<TextView android:text="@string/settings"
android:text="@string/settings" android:textColor="?colorAccent"
android:layout_width="match_parent" android:textSize="18sp"
android:layout_height="wrap_content" android:textStyle="bold"
android:id="@+id/txtView_Settings" android:typeface="sans" />
android:typeface="sans"
android:textSize="18sp"
android:textColor="?colorAccent"
android:textStyle="bold"
android:paddingTop="3dp" android:layout_margin="7dp"/>
<LinearLayout <LinearLayout
android:orientation="vertical" android:id="@+id/linLayout_Theme"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:id="@+id/linLayout_Theme" android:layout_height="match_parent"
android:layout_margin="7dp"> android:layout_margin="7dp"
android:orientation="vertical">
<TextView <TextView
android:text="@string/theme" android:id="@+id/txtView_Theme"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_Theme"
android:textSize="16sp" android:textStyle="bold"/>
<TextView
android:text="@string/themeLight"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_SelectedTheme"/>
</LinearLayout>
<View
android:id="@+id/divider3"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?dividerColor"
/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical|end" android:text="@string/theme"
android:clickable="true"
android:id="@+id/linLayout_PrimaryColor"
android:focusable="true" android:layout_margin="7dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:text="@string/primary_color"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtView_PrimaryColor"
android:textSize="16sp"
android:textStyle="bold"
/>
<TextView
android:text="@string/primary_color_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtView_PrimaryColorDesc"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:gravity="end">
<View
android:layout_width="40dp"
android:layout_height="40dp"
android:id="@+id/view_PrimaryColor"
android:background="?colorPrimary"/>
</LinearLayout>
</LinearLayout>
<View
android:id="@+id/divider4"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?dividerColor"
/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|end"
android:clickable="true"
android:id="@+id/linLayout_AccentColor"
android:focusable="true" android:layout_margin="7dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:text="@string/accent_color"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtView_AccentColor"
android:textSize="16sp"
android:textStyle="bold"
/>
<TextView
android:text="@string/accent_color_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtView_AccentColorDesc"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:gravity="end">
<View
android:layout_width="40dp"
android:layout_height="40dp"
android:id="@+id/view_AccentColor"
android:background="?colorAccent"/>
</LinearLayout>
</LinearLayout>
<View
android:id="@+id/divider5"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
/>
<Switch
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/switch_buffet"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" android:textStyle="bold" />
android:text="@string/show_buffet"
android:layout_margin="7dp"/> <TextView
android:id="@+id/txtView_SelectedTheme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/themeLight" />
</LinearLayout>
<View
android:id="@+id/divider3"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?dividerColor" />
<LinearLayout
android:id="@+id/linLayout_PrimaryColor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:clickable="true"
android:focusable="true"
android:gravity="center_vertical|end"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/txtView_PrimaryColor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/primary_color"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtView_PrimaryColorDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/primary_color_desc" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:gravity="end"
android:orientation="vertical">
<View
android:id="@+id/view_PrimaryColor"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?colorPrimary" />
</LinearLayout>
</LinearLayout>
<View
android:id="@+id/divider4"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?dividerColor" />
<LinearLayout
android:id="@+id/linLayout_AccentColor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:clickable="true"
android:focusable="true"
android:gravity="center_vertical|end"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/txtView_AccentColor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/accent_color"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtView_AccentColorDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/accent_color_desc" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:gravity="end"
android:orientation="vertical">
<View
android:id="@+id/view_AccentColor"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?colorAccent" />
</LinearLayout>
</LinearLayout>
<View
android:id="@+id/divider5"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_buffet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:text="@string/show_buffet"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
</LinearLayout> </LinearLayout>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".fragments.TimeTableFragment" tools:context=".fragments.TimeTableFragment"
@ -22,11 +23,19 @@
android:id="@+id/linLayout_Timetable"/> android:id="@+id/linLayout_Timetable"/>
</ScrollView> </ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:src="@drawable/icon_custom_black" android:id="@+id/faBtnAddSubject"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clickable="true" android:id="@+id/faBtnAddLesson" android:elevation="7dp" android:layout_gravity="bottom|center|end"
android:visibility="gone" android:layout_gravity="bottom|center|end" android:layout_marginEnd="11dp" android:layout_marginEnd="11dp"
android:layout_marginBottom="11dp" android:focusable="true"/> android:layout_marginBottom="11dp"
android:clickable="true"
android:elevation="7dp"
android:focusable="true"
android:src="@drawable/ic_add_black_24dp"
android:visibility="visible"
app:backgroundTint="?colorAccent"
app:fabSize="auto" />
</FrameLayout> </FrameLayout>

View File

@ -20,6 +20,10 @@
android:id="@+id/nav_moodle" android:id="@+id/nav_moodle"
android:icon="@drawable/ic_school_black_24dp" android:icon="@drawable/ic_school_black_24dp"
android:title="@string/moodle"/> android:title="@string/moodle"/>
<item
android:id="@+id/nav_grades"
android:icon="@drawable/ic_grading_black_24dp"
android:title="@string/grades" />
<item <item
android:id="@+id/nav_settings" android:id="@+id/nav_settings"
android:icon="@drawable/ic_settings_black_24dp" android:icon="@drawable/ic_settings_black_24dp"

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_laogai_icon_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_laogai_icon_foreground"/>
</adaptive-icon> </adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -3,7 +3,7 @@
<notice> <notice>
<name>Material Dialogs</name> <name>Material Dialogs</name>
<url>https://github.com/afollestad/material-dialogs</url> <url>https://github.com/afollestad/material-dialogs</url>
<copyright>Copyright (C) Aidan Follestad</copyright> <copyright>Copyright Aidan Follestad</copyright>
<license>Apache Software License 2.0</license> <license>Apache Software License 2.0</license>
</notice> </notice>
<notice> <notice>
@ -13,27 +13,39 @@
<license>Apache Software License 2.0</license> <license>Apache Software License 2.0</license>
</notice> </notice>
<notice> <notice>
<name>gson</name> <name>Gson</name>
<url>https://github.com/google/gson</url> <url>https://github.com/google/gson</url>
<copyright>Copyright 2008 Google Inc.</copyright> <copyright>Copyright 2008 Google Inc.</copyright>
<license>Apache Software License 2.0</license> <license>Apache Software License 2.0</license>
</notice> </notice>
<notice> <notice>
<name>anko</name> <name>Jsoup</name>
<url>https://github.com/Kotlin/anko</url> <url>https://jsoup.org/</url>
<copyright>Copyright 2009 - 2020 Jonathan Hedley</copyright>
<license>MIT License</license>
</notice>
<notice>
<name>kotlinx.coroutines</name>
<url>https://github.com/Kotlin/kotlinx.coroutines</url>
<copyright>Copyright JetBrains</copyright> <copyright>Copyright JetBrains</copyright>
<license>Apache Software License 2.0</license> <license>Apache Software License 2.0</license>
</notice> </notice>
<notice> <notice>
<name>farebot part for desfire cards</name> <name>FareBot part for desfire cards</name>
<url>https://github.com/codebutler/farebot</url> <url>https://github.com/codebutler/farebot</url>
<copyright>Copyright 2011-2012, 2014, 2016 Eric Butler</copyright> <copyright>Copyright 2011-2012, 2014, 2016 Eric Butler</copyright>
<license>GNU General Public License 3.0</license> <license>GNU General Public License 3.0</license>
</notice> </notice>
<notice> <notice>
<name>Android Support Libraries</name> <name>AndroidX</name>
<url>http://developer.android.com/tools/support-library/index.html</url> <url>https://developer.android.com/jetpack/androidx</url>
<copyright>Copyright (C) 2016 The Android Open Source Project</copyright> <copyright>Copyright 2018 The Android Open Source Project</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>Material design icons</name>
<url>https://github.com/google/material-design-icons</url>
<copyright>Copyright Google Inc.</copyright>
<license>Apache Software License 2.0</license> <license>Apache Software License 2.0</license>
</notice> </notice>
</notices> </notices>

View File

@ -8,8 +8,23 @@
<string name="mensa">Mensa</string> <string name="mensa">Mensa</string>
<string name="timetable">Stundenplan</string> <string name="timetable">Stundenplan</string>
<string name="moodle">Moodle</string> <string name="moodle">Moodle</string>
<string name="grades">Noten</string>
<string name="settings">Einstellungen</string> <string name="settings">Einstellungen</string>
<!-- Onboarding -->
<string name="skip">überspringen</string>
<string name="next">weiter</string>
<string name="start">fertig</string>
<string name="welcome">Willkommen bei Project Laogai.\nBevor wir loslegen können, richte bitte die App ein.</string>
<string name="get_started">los geht\'s</string>
<string name="course_heading">Studiengang</string>
<string name="course_desc_on">Wähle deinen aktuellen Studiengang.\nZusätzliche Vorlesungen können später hinzugefügt werden.</string>
<string name="login_heading">Login</string>
<string name="login_desc_on">Project Laogai kann auf die Notenverwaltung zugreifen. Deine Login-Daten werden verschlüsselt auf deinem Gerät gespeichert. Diese Funktion wird "as is" und ohne Garantie bereitgestellt.</string>
<string name="email">E-Mail</string>
<string name="password">Passwort</string>
<string name="login">login</string>
<!-- fragment_home --> <!-- fragment_home -->
<string name="meal">Essen</string> <string name="meal">Essen</string>
<string name="today_date">Heute, %1$s</string> <string name="today_date">Heute, %1$s</string>
@ -18,33 +33,68 @@
<string name="no_more_food">Diese Woche keine weitere Essensausgabe</string> <string name="no_more_food">Diese Woche keine weitere Essensausgabe</string>
<string name="no_lesson_today">heute keine Vorlesung!</string> <string name="no_lesson_today">heute keine Vorlesung!</string>
<!-- fragment_mensa -->
<string name="no_more_meals">Für diese und nächste Woche ist der Speiseplan leer.</string>
<!-- fragment_timetable -->
<string name="add_lesson">Eine Vorlesung hinzufügen</string>
<string name="add_lesson_desc">Füge eine Vorlesung eines anderen Studiengangs zu deinem Stundenplan hinzu.</string>
<string name="courses">Studiengänge:</string>
<string name="lessons">Vorlesungen:</string>
<string name="timetable_generic_error">Beim laden des Stundenplans ist ein Fehler aufgetreten.\n</string>
<!-- fragment_grades -->
<string name="loading_from_hs">Lade Daten von den Hochschul Servern.\nDas kann eine Weile dauern.</string>
<string name="credentials_missing">Diese Funktion benötigt deine Login-Daten. Bitte logge dich über die Einstellungen ein.</string>
<string name="qispos_unavailable">Die Notenverwaltung ist zur Zeit nicht ereichbar.\nVersuche es später noch einmal.\n</string>
<string name="qispos_generic_error">Error.\nVersuche es später noch einmal.\n</string>
<string name="without_guarantee">Alle Angaben ohne Gewähr.</string>
<!-- fragment_settings --> <!-- fragment_settings -->
<string name="info">Info</string> <string name="info">Info</string>
<string name="user">Benutzer</string> <string name="user_desc">Zum bearbeiten tippen</string>
<string name="course_desc">Tippen, um den Kurs zu ändern</string> <string name="course_desc">Tippe um den Kurs zu ändern</string>
<string name="manage_lessons">Bearbeite Zusätzliche Vorlesungen</string>
<string name="manage_lessons_desc">Tippe um zusätzliche Vorlesungen zu bearbeiten</string>
<string name="about_dialog_heading">Über</string> <string name="about_dialog_heading">Über</string>
<string name="licenses">Lizenzen</string> <string name="licenses">Lizenzen</string>
<string name="theme">Design</string> <string name="theme">Design</string>
<string name="themeLight">Hell</string> <string name="themeLight">Hell</string>
<string name="themeDark">Dunkel</string> <string name="themeDark">Dunkel</string>
<string name="themeBlack">Schwarz</string> <string name="themeBlack">Schwarz</string>
<string name="primary_color">Hauptfarbe</string> <string name="primary_color">Primärfarbe</string>
<string name="primary_color_desc">Die Primärfarbe, Standard ist Schwarz.</string> <string name="primary_color_desc">Zum Ändern tippen, Standard ist Blaugrün.</string>
<string name="accent_color">Akzentfarbe</string> <string name="accent_color">Akzentfarbe</string>
<string name="accent_color_desc">Die Akzentfarbe, Standard ist indigo</string> <string name="accent_color_desc">Zum Ändern tippen, Standard ist Hellblau.</string>
<string name="show_buffet">Buffet immer anzeigen</string> <string name="show_buffet">Buffet immer anzeigen</string>
<!-- dialogs --> <!-- dialogs -->
<string name="select_course">Wähle deinen Studiengang</string> <string name="select_course">Wähle deinen Studiengang</string>
<string name="loading_timetable">lade Stundenplan …</string> <string name="loading_timetable">lade Stundenplan …</string>
<string name="add">hinzufügen</string>
<string name="delete">löschen</string>
<string name="cancel">abbrechen</string>
<string name="select">auswählen</string> <string name="select">auswählen</string>
<string name="close">schließen</string> <string name="save">speichern</string>
<string name="mensa_credit">Mensa-Guthaben</string> <string name="mensa_credit">Mensa-Guthaben</string>
<string name="mensa_current">aktuell: %1$s\n</string> <string name="mensa_current">aktuell: %1$s\n</string>
<string name="mensa_last">letzte Abbuchung: %1$s</string> <string name="mensa_last">letzte Abbuchung: %1$s</string>
<!-- errors --> <!-- errors -->
<string name="error">Fehler</string> <string name="error">Fehler</string>
<string name="timetable_error">Stundenplan konnte nicht geladen werden!</string> <string name="timetable_error">Der Stundenplan konnte nicht geladen werden.</string>
<!-- shortcuts -->
<string name="shortcut_mensa_short">Mensa</string>
<string name="shortcut_mensa_long">Mensa</string>
<string name="shortcut_mensa_disabled">Mensa deaktiviert</string>
<string name="shortcut_timetable_short">Stundenplan</string>
<string name="shortcut_timetable_long">Stundenplan</string>
<string name="shortcut_timetable_disabled">Stundenplan deaktiviert</string>
<string name="shortcut_moodle_short">Moodle</string>
<string name="shortcut_moodle_long">Moodle</string>
<string name="shortcut_moodle_disabled">Moodle deaktiviert</string>
</resources> </resources>

View File

@ -1,24 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="colorPrimary">#000000</color> <color name="colorPrimary">#009688</color>
<color name="colorPrimaryDark">#000000</color> <color name="colorPrimaryDark">#009688</color>
<color name="colorAccent">#3f51b5</color> <color name="colorAccent">#0096ff</color>
<color name="ic_launcher_background">#ffffff</color> <color name="ic_laogai_icon_background">#ffffff</color>
<color name="themePrimary">#ffffff</color>
<color name="themeSecondary">#ffffff</color>
<color name="textPrimary">#000000</color>
<color name="textSecondary">#818181</color>
<!--theme color section, not the working colors--> <!--theme color section, not the working colors-->
<color name="themePrimaryDark">#000000</color> <color name="themePrimaryDark">#000000</color>
<color name="themeSecondaryDark">#303030</color> <color name="themeSecondaryDark">#303030</color>
<color name="themeTertiaryDark">#424242</color>
<color name="textPrimaryDark">#ffffff</color> <color name="textPrimaryDark">#ffffff</color>
<color name="textSecondaryDark">#e0e0e0</color> <color name="textSecondaryDark">#c0c0c0</color>
<color name="dividerDark">#232323</color> <color name="dividerDark">#232323</color>
<color name="themePrimaryLight">#ffffff</color> <color name="themePrimaryLight">#f5f5f5</color>
<color name="themeSecondaryLight">#ffffff</color> <color name="themeSecondaryLight">#ffffff</color>
<color name="textPrimaryLight">#000000</color> <color name="textPrimaryLight">#000000</color>
<color name="textSecondaryLight">#818181</color> <color name="textSecondaryLight">#818181</color>

View File

@ -10,8 +10,23 @@
<string name="mensa">Mensa</string> <string name="mensa">Mensa</string>
<string name="timetable">Timetable</string> <string name="timetable">Timetable</string>
<string name="moodle">Moodle</string> <string name="moodle">Moodle</string>
<string name="grades">Grades</string>
<string name="settings">Settings</string> <string name="settings">Settings</string>
<!-- Onboarding -->
<string name="skip">skip</string>
<string name="next">next</string>
<string name="start">start</string>
<string name="welcome">Welcome to Project Laogai.\nBefore we can start, setup the App.</string>
<string name="get_started">get started</string>
<string name="course_heading">Course</string>
<string name="course_desc_on">Select your current course.\nAdditional lessons can be added later.</string>
<string name="login_heading">Login</string>
<string name="login_desc_on">Project Laogai can connect to the Qispos Online-Portal. Your Login-Data will be stored encrypted on your device. This feature is provided "as is", without any guarantee.</string>
<string name="email">E-Mail</string>
<string name="password">Password</string>
<string name="login">login</string>
<!-- fragment_home --> <!-- fragment_home -->
<string name="meal">Meal</string> <string name="meal">Meal</string>
<string name="today_date">Today, %1$s</string> <string name="today_date">Today, %1$s</string>
@ -20,46 +35,86 @@
<string name="no_more_food">No more Food this week</string> <string name="no_more_food">No more Food this week</string>
<string name="no_lesson_today">"No lecture today!"</string> <string name="no_lesson_today">"No lecture today!"</string>
<!-- fragment_mensa -->
<string name="no_more_meals">The menu is empty for this and the next week.</string>
<!-- fragment_timetable -->
<string name="add_lesson">Add a lesson</string>
<string name="add_lesson_desc">Add a lesson from another course to your timetable.</string>
<string name="courses">Courses:</string>
<string name="lessons">Lessons:</string>
<string name="timetable_generic_error">An error occurred while loading the timetable.\n</string>
<!-- fragment_grades -->
<string name="loading_from_hs">Loading data from the university servers.\nThis may take a while.</string>
<string name="credentials_missing">This feature needs your Login-Data to work. Please login via the settings.</string>
<string name="qispos_unavailable">The Qispos server is currently unavailable.\nPlease try again later.\n</string>
<string name="qispos_generic_error">Error.\nPlease try again later.\n</string>
<string name="without_guarantee">All information is supplied without guarantee.</string>
<!-- fragment_settings --> <!-- fragment_settings -->
<string name="info">Info</string> <string name="info">Info</string>
<string name="user">User</string> <string name="user_desc">Tap to edit</string>
<string name="course_desc">Tap to change course</string> <string name="course_desc">Tap to change course</string>
<string name="about_dialog_heading">About</string> <string name="manage_lessons">Manage Additional Lessons</string>
<string name="about_dialog_text" translatable="false">"This software is made by @Seil0 and is published under the terms and conditions of GPL 3. For further information visit \ngit.mosad.xyz/Seil0/ProjectLaogai \n\n© 2018-2019 seil0@mosad.xyz "</string> <string name="manage_lessons_desc">Tap to manage additional lessons</string>
<string name="about_txtView" translatable="false">hso App by @Seil0</string> <string name="about_txtView" translatable="false">hso App by @Seil0</string>
<string name="about_version" translatable="false">Version %1$s (%2$s)</string> <string name="about_version" translatable="false">Version %1$s (%2$s)</string>
<string name="about_dialog_heading">About</string>
<string name="about_dialog_text" translatable="false">"This software is made by @Seil0 and is published under the terms and conditions of GPL 3. For further information visit \ngit.mosad.xyz/Seil0/ProjectLaogai \n\n© 2018-2020 seil0@mosad.xyz "</string>
<string name="licenses">Licenses</string> <string name="licenses">Licenses</string>
<string name="theme">Theme</string> <string name="theme">Theme</string>
<string name="themeLight">Light</string> <string name="themeLight">Light</string>
<string name="themeDark">Dark</string> <string name="themeDark">Dark</string>
<string name="themeBlack">Black</string> <string name="themeBlack">Black</string>
<string name="primary_color">Primary color</string> <string name="primary_color">Primary color</string>
<string name="primary_color_desc">The primary color, default is black.</string> <string name="primary_color_desc">Tap to change, default is teal blue.</string>
<string name="accent_color">Accent color</string> <string name="accent_color">Accent color</string>
<string name="accent_color_desc">The accent color, default is indigo.</string> <string name="accent_color_desc">Tap to change, default is light blue.</string>
<string name="show_buffet">Always show buffet</string> <string name="show_buffet">Always show buffet</string>
<!-- dialogs --> <!-- dialogs -->
<string name="select_course">Select your course</string> <string name="select_course">Select your course</string>
<string name="loading_timetable">loading timetable …</string> <string name="loading_timetable">loading timetable …</string>
<string name="add">add</string>
<string name="delete">delete</string>
<string name="cancel">@android:string/cancel</string>
<string name="select">select</string> <string name="select">select</string>
<string name="close">close</string> <string name="save">save</string>
<string name="mensa_credit">Mensa credit</string> <string name="mensa_credit">Mensa credit</string>
<string name="mensa_current">current: %1$s\n</string> <string name="mensa_current">current: %1$s\n</string>
<string name="mensa_last">last: %1$s</string> <string name="mensa_last">last: %1$s</string>
<!-- sample strings --> <!-- sample strings -->
<string name="sample_user" translatable="false">spinefield@stud.hs-offenburg.de</string> <string name="sample_user" translatable="false">spinefield@stud.hs-offenburg.de</string>
<string name="sample_course" translatable="false">SampleCourse 3</string> <string name="sample_course" translatable="false">Everything</string>
<string name="sample_date" translatable="false">Montag, 30.02</string> <string name="sample_subject" translatable="false">Earthbending</string>
<string name="sample_sub_subject" translatable="false">Applied Earthbending</string>
<string name="sample_grade" translatable="false">1,0</string>
<string name="sample_grade_state" translatable="false">passed</string>
<string name="sample_date" translatable="false">Monday, 30.02</string>
<string name="a_time" translatable="false">0.00 23.59</string> <string name="a_time" translatable="false">0.00 23.59</string>
<!-- errors --> <!-- errors -->
<string name="error">Error</string> <string name="error">Error</string>
<string name="timetable_error">Could not load timetable!"</string> <string name="timetable_error">Could not load the timetable!"</string>
<!-- shortcuts -->
<string name="shortcut_mensa_short">Mensa</string>
<string name="shortcut_mensa_long">Mensa</string>
<string name="shortcut_mensa_disabled">Mensa disabled</string>
<string name="shortcut_timetable_short">Timetable</string>
<string name="shortcut_timetable_long">Timetable</string>
<string name="shortcut_timetable_disabled">Timetable disabled</string>
<string name="shortcut_moodle_short">Moodle</string>
<string name="shortcut_moodle_long">Moodle</string>
<string name="shortcut_moodle_disabled">Moodle disabled</string>
<!-- save keys --> <!-- save keys -->
<string name="preference_file_key" translatable="false">org.mosad.seil0.projectlaogai_preferences</string>
<string name="encrypted_preference_file_key" translatable="false">org.mosad.seil0.projectlaogai_encrypted_preferences</string>
<string name="save_key_course" translatable="false">org.mosad.seil0.projectlaogai.course</string> <string name="save_key_course" translatable="false">org.mosad.seil0.projectlaogai.course</string>
<string name="save_key_courseTTLink" translatable="false">org.mosad.seil0.projectlaogai.courseTTLink</string> <string name="save_key_courseTTLink" translatable="false">org.mosad.seil0.projectlaogai.courseTTLink</string>
<string name="save_key_colorPrimary" translatable="false">org.mosad.seil0.projectlaogai.colorPrimary</string> <string name="save_key_colorPrimary" translatable="false">org.mosad.seil0.projectlaogai.colorPrimary</string>
@ -68,5 +123,24 @@
<string name="save_key_coursesCacheTime" translatable="false">org.mosad.seil0.projectlaogai.coursesCacheTime</string> <string name="save_key_coursesCacheTime" translatable="false">org.mosad.seil0.projectlaogai.coursesCacheTime</string>
<string name="save_key_mensaCacheTime" translatable="false">org.mosad.seil0.projectlaogai.mensaCacheTime</string> <string name="save_key_mensaCacheTime" translatable="false">org.mosad.seil0.projectlaogai.mensaCacheTime</string>
<string name="save_key_timetableCacheTime" translatable="false">org.mosad.seil0.projectlaogai.timetableCacheTime</string> <string name="save_key_timetableCacheTime" translatable="false">org.mosad.seil0.projectlaogai.timetableCacheTime</string>
<string name="save_key_user_email" translatable="false">org.mosad.seil0.projectlaogai.user_email</string>
<string name="save_key_user_password" translatable="false">org.mosad.seil0.projectlaogai.user_password</string>
<string-array name="courses">
<item>AI-1</item>
<item>AI-2</item>
<item>AI-3</item>
<item>AI-4</item>
<item>AI-5</item>
<item>AI-6</item>
</string-array>
<string-array name="lessons">
<item>Mathematik</item>
<item>Verteilte Systemr</item>
<item>EA 1</item>
<item>IT Security</item>
</string-array>
</resources> </resources>

View File

@ -14,7 +14,9 @@
<item name="themeSecondary">@color/themeSecondaryLight</item> <item name="themeSecondary">@color/themeSecondaryLight</item>
<item name="android:textColor">@color/textPrimaryLight</item> <item name="android:textColor">@color/textPrimaryLight</item>
<item name="android:textColorPrimary">@color/textPrimaryLight</item> <item name="android:textColorPrimary">@color/textPrimaryLight</item>
<item name="android:textColorHint">@color/textSecondaryLight</item>
<item name="textPrimary">@color/textPrimaryLight</item> <item name="textPrimary">@color/textPrimaryLight</item>
<item name="textSecondary">@color/textSecondaryLight</item>
<item name="dividerColor">@color/dividerLight</item> <item name="dividerColor">@color/dividerLight</item>
<item name="md_background_color">@color/themeSecondaryLight</item> <item name="md_background_color">@color/themeSecondaryLight</item>
<item name="md_color_title">@color/textPrimaryLight</item> <item name="md_color_title">@color/textPrimaryLight</item>
@ -27,7 +29,9 @@
<item name="themeSecondary">@color/themeSecondaryDark</item> <item name="themeSecondary">@color/themeSecondaryDark</item>
<item name="android:textColor">@color/textPrimaryDark</item> <item name="android:textColor">@color/textPrimaryDark</item>
<item name="android:textColorPrimary">@color/textPrimaryDark</item> <item name="android:textColorPrimary">@color/textPrimaryDark</item>
<item name="android:textColorHint">@color/textSecondaryDark</item>
<item name="textPrimary">@color/textPrimaryDark</item> <item name="textPrimary">@color/textPrimaryDark</item>
<item name="textSecondary">@color/textSecondaryDark</item>
<item name="dividerColor">@color/dividerDark</item> <item name="dividerColor">@color/dividerDark</item>
<item name="md_background_color">@color/themeSecondaryDark</item> <item name="md_background_color">@color/themeSecondaryDark</item>
<item name="md_color_title">@color/textPrimaryDark</item> <item name="md_color_title">@color/textPrimaryDark</item>
@ -40,17 +44,15 @@
<item name="themeSecondary">@color/themePrimaryDark</item> <item name="themeSecondary">@color/themePrimaryDark</item>
<item name="android:textColor">@color/textPrimaryDark</item> <item name="android:textColor">@color/textPrimaryDark</item>
<item name="android:textColorPrimary">@color/textPrimaryDark</item> <item name="android:textColorPrimary">@color/textPrimaryDark</item>
<item name="android:textColorHint">@color/textSecondaryDark</item>
<item name="textPrimary">@color/textPrimaryDark</item> <item name="textPrimary">@color/textPrimaryDark</item>
<item name="textSecondary">@color/textSecondaryDark</item>
<item name="dividerColor">@color/dividerDark</item> <item name="dividerColor">@color/dividerDark</item>
<item name="md_background_color">@color/themeSecondaryDark</item> <item name="md_background_color">@color/themeSecondaryDark</item>
<item name="md_color_title">@color/textPrimaryDark</item> <item name="md_color_title">@color/textPrimaryDark</item>
<item name="md_color_content">@color/textPrimaryDark</item> <item name="md_color_content">@color/textPrimaryDark</item>
</style> </style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/> <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/> <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>

View File

@ -1,13 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.3.50' ext.kotlin_version = '1.4.0'
repositories { repositories {
google() google()
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.5.0' classpath 'com.android.tools.build:gradle:4.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.0"
width="121"
height="219"
id="svg92"
viewBox="0 0 121.0 219"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs6">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipEmfPath1">
<path
d="M -0.33333333,0 H 1087.3333 V 232 H -0.33333333 Z"
id="path2" />
</clipPath>
<pattern
id="EMFhbasepattern"
patternUnits="userSpaceOnUse"
width="6"
height="6"
x="0"
y="0" />
</defs>
<g
id="g60"
transform="matrix(1.0027625,0,0,1.0015244,-19.720996,-9.0137196)">
<path
style="opacity:0.993;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;image-rendering:auto"
clip-path="url(#clipEmfPath1)"
d="m 103.66667,16 v 0 0 40.333333 c 12,6.666667 21.66666,19 21.66666,46.999997 0,28 -14.33333,45 -21.66666,50.33334 v 36 l 36.66666,-12.33334 V 9 Z"
id="path8" />
<path
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
clip-path="url(#clipEmfPath1)"
d="M 64.666667,56.666667 V 15.333333 l -45,8 v 0 0 204.333337 l 45,-15.66667 V 169 C 52,162.66667 38.333333,147.33333 38.333333,116.33333 38.333333,85.333333 53.666667,65 64.666667,56.666667 Z"
id="path10" />
<path
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
clip-path="url(#clipEmfPath1)"
d="M 75.333333,68.666667 V 158 c 11.333334,-4.33333 25.333337,-20 25.333337,-49 0,-32.909003 -12.564687,-42.347858 -25.328763,-44.875741 0.01241,1.916829 -0.005,1.98343 -0.0046,4.542408 z"
id="path90" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,8 +1,10 @@
ProjectLaogai ist eine App um den Vorlesungsplan und den Mensa-Speiseplan der Hochschule Offenburg anzuzeigen. ProjectLaogai ist eine App um den Vorlesungsplan, die Notenverwaltung und den Mensa-Speiseplan der Hochschule Offenburg anzuzeigen.
Features: Features:
* schaue was es diese und nächste Woche in der Mensa zu Essen gibt * lass dir deine Noten direkt in der App anzeigen
* schaue dir deinen Vorlesungsplan an * zeige den Stundenplan deines Studiengangs an
* lass dir das aktuelle Guthaben der Mensakarte anzeigen * schau dir den Mensa Speiseplan der aktuellen und der nächsten Woche an
* schaue dir das aktuelle Guthaben deiner Mensakarte an
* öffne moodle direkt in der App * öffne moodle direkt in der App
* viele lustige Bugs
Bitte melde Fehler und Probleme an support@mosad.xyz

View File

@ -0,0 +1,5 @@
This release 0.5.1 is called "artistical Apollon".
* new: it's now possible to use App shortcuts for the timetable, mensa and moodle screen
* change: updated some libs, updated kotlin to 1.3.61
* change: the app was updated to use kotlin coroutines instead of anko for asynchronous workloads

View File

@ -0,0 +1,9 @@
Here comes version 0.6.0 "anthropomorphised Athena"
* new Onboarding Process
* it's now possible to access the grades from qispos
* you can now add additional lessons which are not part of your course timetable
* new primary and accent colors to match the mosad.xyz design
* updated some libraries
* better compatibility with tcor version 1.2.2 and higher
* fixed a crash on first start, if tcor is not reachable

View File

@ -1,8 +1,10 @@
ProjectLaogai is an app to access the timetable and the mensa menu of Hochschule Offenburg. ProjectLaogai is an app to access the timetable, grades (qispos) and the canteen menu of Hochschule Offenburg.
Features: Features:
* check out the mensa menu of this and next week * have your grades displayed directly in the app
* access your timetable * 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 * check the current balance of your mensa card
* open moodle * open moodle directly in the app
* probably some funny bugs
Please report bugs and issues to support@mosad.xyz

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

35
gradlew vendored
View File

@ -82,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@ -125,10 +126,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi fi
# For Cygwin, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if $cygwin ; then if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"` APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"` JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath # We build the pattern for arguments to be converted via cygpath
@ -154,19 +156,19 @@ if $cygwin ; then
else else
eval `echo args$i`="\"$arg\"" eval `echo args$i`="\"$arg\""
fi fi
i=$((i+1)) i=`expr $i + 1`
done done
case $i in case $i in
(0) set -- ;; 0) set -- ;;
(1) set -- "$args0" ;; 1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;; 2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;; 3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
@ -175,14 +177,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " " echo " "
} }
APP_ARGS=$(save "$@") APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules # Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"

4
gradlew.bat vendored
View File

@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@ -81,6 +84,7 @@ set CMD_LINE_ARGS=%*
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%