111 Commits

Author SHA1 Message Date
6e9c63d3d4 Merge branch 'develop' of Seil0/ProjectLaogai into master [Hotfix] 2020-02-18 15:24:08 +01:00
c343735b57 fix crash on first startup 2020-02-18 15:18:36 +01:00
9c5274dc06 Merge branch 'develop' of Seil0/ProjectLaogai into master
release version 0.5.1
2020-02-05 14:15:04 +01:00
b186a2e96e release version 0.5.1 2020-02-05 14:10:45 +01:00
2cb4b72369 anko code removed, coroutines are now used for asynchronous functions
* constraintlayout 2.0.0-beta3 -> 2.0.0-beta4
* material-components-android 1.0.0 -> 1.1.0
* this is the first release candidate for version 0.5.1
2020-02-04 19:58:07 +01:00
2d497d1a96 update gradle wrapper to version 6.0.1 2020-01-15 15:28:07 +01:00
9d2de3fcb3 don't use deprecated Gson methods 2020-01-15 15:08:05 +01:00
bed3f5d978 added Timetable and Moodle shortcut, cleaned up activity init 2020-01-15 15:00:05 +01:00
8f5a4dd1b3 reworked preference saving 2019-12-13 14:54:51 +01:00
9907d083e9 added a Mensa shortcut
* added a Mensa shortcut, to directly display the Mensa-Screen
* kotlin 1.3.60 -> 1.3.61
2019-11-28 16:38:25 +01:00
d4860b2a32 updated some libs
* kotlin 1.3.50 -> 1.3.60
* androidx.constraintlayout 2.0.0-beta2 -> 2.0.0-beta3
2019-11-14 23:11:49 +01:00
5ec2b53bce added "Get it on Google Play" badge 2019-10-24 22:22:17 +02:00
701a351e6e update .drone.yml
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2019-10-17 19:21:15 +02:00
5cad924b26 removed anko dependency from PreferenceController
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2019-10-17 19:19:47 +02:00
605bf6248d release 0.5.0
Merge branch 'develop' of Seil0/ProjectLaogai into master
2019-09-30 21:28:54 +02:00
d5adc4df51 release 0.5.0 2019-09-30 21:25:38 +02:00
c23454f081 removed versionNameSuffix, f-droid does not like that 2019-09-28 15:01:19 +02:00
b240beacc9 turns out there is a current week too
* don't debug timetable cod on Sundays, since there are no lessons on Sunday
2019-09-25 22:46:58 +02:00
aa69d2242f fixed course name not being updated on change
* minor GUI improvements
2019-09-23 22:44:03 +02:00
6e6c9f71a0 reworked the way the timetable date is calculated
* cleaned up some timetable gui code
* close #33
2019-09-22 20:51:36 +02:00
889c673c5d Update 'README.md' 2019-09-08 20:35:06 +02:00
522f31e077 added new screenshots 2019-09-08 20:30:03 +02:00
b9ca18044c drone use gradle test instead of assembleRelease
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2019-09-07 21:10:51 +02:00
bd49e482e2 updated some libs
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
* appcompat 1.0.2 -> 1.1.0
* material-dialogs 3.1.0 -> 3.1.1
2019-09-07 21:06:41 +02:00
2f5b2a6579 added drone ci
* updated gradle wrapper to gradle 5.6.2
2019-09-07 21:01:17 +02:00
4ce37b3dcf updated desfire legal notes 2019-08-30 14:17:07 +02:00
7550fcfd22 Update 'README.md' 2019-08-30 14:12:17 +02:00
a23f7c9212 update to release 0.5.0 2019-08-30 14:09:59 +02:00
75168c6688 some more logging and version 0.4.99 (RC1) 2019-08-25 22:45:23 +02:00
be916a74ab the license dialog will now follow the theme too 2019-08-25 22:19:36 +02:00
3e909ab68f color the selected element in the NavigationView too 2019-08-23 20:25:20 +02:00
5f2b3aa496 clean up the layout & polished Mensa credit dialog
* don't crash if the nfc tag has been removed between triggering intent and connecting to the tag
* closes #21
2019-08-23 19:19:35 +02:00
6cc1671a52 don't crash on devices without nfc 2019-08-21 20:43:08 +02:00
b4ed1ca927 migrated all farebot code to kotlin 2019-08-19 12:42:56 +02:00
e74c307566 cleand up the nfc part, added foreground-dispatch
* cleaned up the strings.xml files
* fixed license dialog
2019-08-18 22:09:33 +02:00
733b675ffa first working implementation of the mensa card reader
* based on farebot's desfire protocol implementation
* see #21
2019-08-17 18:59:28 +02:00
3a0a31f781 first bits of the nfc reader 2019-08-17 01:51:15 +02:00
f52151fbf1 the no lesson card now has the correct text color
* updated material-dialogs to version 3.1.0
2019-07-18 00:32:31 +02:00
e8cae7e807 update to gradle 5.5.1, update gradle plugin to 3.4.2 2019-07-18 00:15:57 +02:00
c6ac19bfae fixed the Sunday-fix 2019-07-01 20:31:06 +02:00
a7abd48726 moved all cache checking code tho the CacheController
* added new sunday bug fix for the timetable, same as for the mensa
2019-06-26 15:00:43 +02:00
9a22d9b737 use a ScrollView for the settings fragment 2019-06-26 14:11:32 +02:00
2cc2ecf952 theme the dialogs and navView too, added a License dialog 2019-06-25 17:10:57 +02:00
ea70aedbd0 added the fastlane changelog for version 14 2019-06-25 13:09:17 +02:00
98b3adbf3b update to kotlin 1.3.40, added theme choose option
* added missing dividers in settings
* added a black theme
* closes #15
2019-06-25 12:07:18 +02:00
a055f59cc8 added a dark theme, see #15
* the app now needs sdkversion 23 now an min
2019-06-24 22:41:45 +02:00
01db6bb451 don't use the faBtn in MensaFragment, use it in TimetableFragment 2019-06-08 23:14:55 +02:00
4838c9406c fastlane description spelling fixes 2019-06-06 23:36:55 +02:00
23195f94e0 don't us deprecated tcor api requests 2019-06-03 12:05:33 +02:00
5e766ec126 hide the mensa card value button 2019-06-03 10:59:32 +02:00
9c1f95ca25 some lib updates & new sunday-bug fix 2019-06-02 19:09:36 +02:00
e99127a63a Update 'README.md' 2019-05-03 22:54:43 +02:00
f7fa96b1ae use gradle 5.4.1
* fixes build issues if instant run is enabled
2019-05-03 22:45:53 +02:00
ac37bce145 Merge branch 'develop' of Seil0/ProjectLaogai into master 2019-05-02 14:43:20 +02:00
6fd82254e0 updated description for f-droid 2019-05-02 14:42:28 +02:00
0dd8ba9475 release 0.4.1 2019-05-01 01:04:09 +02:00
770e9a255d Update 'README.md' 2019-05-01 00:46:47 +02:00
4589badbfc updated versionName to 0.4.1
* updated material-dialogs 2.6.0 -> 2.8.1
2019-05-01 00:36:33 +02:00
74f75bfbde update to kotlin 1.3.31, gradle 5.1.1 & gradle plugin 3.4.0 2019-04-25 18:27:57 +01:00
a00c651bfd disable layout change animation 2019-04-08 18:39:43 +02:00
77757ad9ca fixed mensa screen crash if day has no meals 2019-04-08 14:04:26 +02:00
fe111ac56b added refresh to timetable screen 2019-04-06 14:13:01 +02:00
4971d0b1b8 added fastlane metadata 2019-04-05 15:05:29 +02:00
2bd86ff6bb added fastlane metadata 2019-04-05 15:03:53 +02:00
dbaf496a79 minor clean up and refactoring 2019-04-03 20:37:17 +02:00
77326a8ed6 added ability to refresh the mensa menus
* some minor clean ups
2019-04-03 19:21:51 +02:00
9fc897e194 fixed initial accent color 2019-04-02 13:23:37 +02:00
f9ac219ec6 Merge remote-tracking branch 'origin/master' into develop 2019-04-02 13:22:32 +02:00
a87e57c80e removed versionNameSuffix 2019-04-01 18:03:26 +02:00
ff1d353cae removed versionNameSuffix 2019-04-01 18:03:03 +02:00
6e5cf29eaa removed maven repo 2019-04-01 17:55:37 +02:00
ee388bf5a5 removed maven repo 2019-04-01 17:55:14 +02:00
8aaf8e3647 release 0.4.0
* use Locale.getDefault() for the timetable screen
2019-04-01 10:14:00 +02:00
d645d75bbf fixed showing wrong meal of tomorrow on Wednesday 2019-03-27 19:29:54 +01:00
f97491addd added mensa sunday workaround
* update mensa blocking un sundays between 1500 and 2359
* updated material-dialogs 2.2.0 -> 2.6.0
2019-03-27 13:29:36 +01:00
e08790aaa4 show the next day with a lecture on Sundays too 2019-03-24 17:23:10 +01:00
750a808fbe the homescreen now shows the next day with a lecture
* fixed getTomorrowWeekIndex() since it would return 7 which is out of the arrays bounds
* minor clean up
2019-03-23 21:07:33 +01:00
e51a80b78d home screen redesign 2019-03-22 21:59:32 +01:00
a4eaea2918 timetable and mensa gui redesign
* the timetable and mensa screen are now the same design as the settings, much cleaner and ready for themes
2019-03-20 21:33:55 +01:00
cd3136715f spelling 2019-03-20 17:55:30 +01:00
15f1386b6e some minor gui fixes
* why was minifyEnabled = false ??????????
2019-03-19 19:51:58 +01:00
3bace6c155 Update 'README.md' 2019-03-19 19:18:34 +01:00
61716f8eb4 Update 'README.md' 2019-03-19 19:13:47 +01:00
a1410f7b80 sort of worked around the Sunday problem 2019-03-18 19:09:03 +01:00
953185425b fixed menu formatting 2019-03-17 20:52:34 +01:00
a6d14044c2 added initial course selection
* some minor gui fixes
2019-03-17 18:38:31 +01:00
f3b7ff066d added the tcor api
* drastically improved loading times, closes #14
2019-03-17 18:12:36 +01:00
040eb26dcf update app version to 0.3.90 2019-03-14 22:57:41 +01:00
1a5d2b6561 fix homescreen 2019-03-14 08:04:09 +01:00
92b60d660c updated gradle build tool to 3.3.2 2019-03-14 08:03:39 +01:00
a4eceeddc9 added 2nd week menus & option to disable buffet in the mensa tab
* the GUI now shows a second week for Mensa menus
* you can disable the buffet in the Mensa tab now
2019-03-10 13:37:13 +01:00
3e3a80442e next weeks menu & accent color
* the MensaParser can now get the next weeks menus
* it's now possible to select a accent color
2019-03-09 22:15:53 +01:00
5a1a07cd42 minor gui updates 2019-03-03 20:46:51 +01:00
dbfdaffe99 the app can now display multiple lessons per time slot
* display multiple lessons per slot if needed
* material dialogs 2.0.0 -> 2.0.3
2019-03-01 18:59:17 +01:00
58eb217ab7 added a new timetable parser implementation
* added supports for multiple lessons per slot
* added support for lessons which take 2 slots
2019-02-28 14:52:40 +01:00
24f920c05f Added PreferenceController
* all preferences and global variables are now inside the PreferenceController, this makes it much easier to extend the apps functionality
* CourseTTLink is now Course with courseLink and courseName as values
2019-02-17 15:05:03 +01:00
6301308d76 Update 'README.md' 2019-02-16 17:38:24 +01:00
8e205fa889 updated the gradle plugin to 3.3.1 2019-02-14 16:27:29 +01:00
e9bdcee443 some clean up
* removed unnecessary MainActivity dependencies
2019-02-14 16:25:23 +01:00
b214cfccb2 „README.md“ ändern 2019-02-13 21:16:17 +01:00
404ddd58b8 „README.md“ ändern 2019-02-13 21:14:34 +01:00
137ff7df0c „README.md“ ändern 2019-02-13 21:10:38 +01:00
b4071d7456 updated some libs
* material-dialogs 2.0.0-rc5 -> 2.0.0
2019-02-09 21:58:35 +01:00
5e6e6cfde6 updated gradle plugin to 3.3.0 2019-01-15 18:55:06 +01:00
ffeb09a37f fixed a possible issue with the mensa parser 2019-01-04 23:58:54 +01:00
75a457312d major bug fixes & version 0.3.2
* fixed a issue that prevented the app from showing the second weeks schedule, until now the app showed always the current week as next week
* minor color chooser improvements
* minor code clean up
2019-01-03 13:46:09 +01:00
87bf614d28 added color saving
** WARNING this contains issues, many issues **
2019-01-03 01:45:28 +01:00
e69354af96 clean up 2018-12-18 11:49:46 +01:00
ec74a8e4f8 basic color selection 2018-12-14 15:02:19 +01:00
b49d16b1a1 updated some libs, meal tomorrow shows correct string if there's no meal 2018-12-02 00:05:23 +01:00
70059b4b0c added moodle webview 2018-11-30 13:54:39 +01:00
79 changed files with 3453 additions and 1182 deletions

9
.drone.yml Normal file
View File

@ -0,0 +1,9 @@
kind: pipeline
name: default
steps:
- name: assembleRelease
image: nextcloudci/android:android-51
commands:
- gradle assembleRelease

View File

@ -1,12 +1,23 @@
# ProjectLaogai "hso App" ![fdroid version](https://img.shields.io/f-droid/v/org.mosad.seil0.projectlaogai.svg?style=flat-square)
Some info about the app ... [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg?style=flat-square)](https://www.gnu.org/licenses/gpl-3.0)
# ProjectLaogai "hso App"
ProjectLaogai is a app to access the timetable and the mensa menu of Hochschule Offenburg.
[<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
* look up what you can eat in the mensa * check out the mensa menu of this and next week
* check your timetable * access your timetable
* probably many many bugs * check the current balance of your mensa card
* open moodle
* probably some funny bugs
## 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_Mensa.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.png)
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.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)
ProjectLaogai © 2019-2020 [@Seil0](https://git.mosad.xyz/Seil0), a [mosad](http://www.mosad.xyz) Project
ProjectLaogai © 2018 mosad www.mosad.xyz, Project by [@Seil0](https://git.mosad.xyz/Seil0)

View File

@ -1,43 +1,71 @@
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 {
signingConfigs { signingConfigs {
} }
compileSdkVersion 28 compileSdkVersion 29
defaultConfig { defaultConfig {
applicationId "org.mosad.seil0.projectlaogai" applicationId "org.mosad.seil0.projectlaogai"
minSdkVersion 21 minSdkVersion 23
targetSdkVersion 28 targetSdkVersion 29
versionCode 9 versionCode 15
versionName "0.3.1" versionName "0.5.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "build_time", buildTime()
setProperty("archivesBaseName", "projectlaogai-$versionName")
} }
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
sourceSets {
main {
res.srcDirs =
[
'src/main/res/layouts/activities',
'src/main/res/layouts/dialogs',
'src/main/res/layouts/fragments',
'src/main/res/layouts',
'src/main/res'
]
}
} }
} }
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 'androidx.appcompat:appcompat:1.0.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
implementation 'org.jsoup:jsoup:1.11.3' implementation 'com.google.android.material:material:1.1.0'
implementation 'org.jetbrains.anko:anko-commons:0.10.8' implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.afollestad.material-dialogs:core:2.0.0-rc1' implementation 'com.afollestad:aesthetic:1.0.0-beta05'
implementation 'com.afollestad.material-dialogs:color:2.0.0-rc1' implementation 'com.afollestad.material-dialogs:core:3.1.1'
implementation 'com.afollestad.material-dialogs:color:3.1.1'
implementation 'de.psdev.licensesdialog:licensesdialog:2.1.0'
implementation 'org.apache.commons:commons-lang3:3.9'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.0' androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
} }
static def buildTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}

View File

@ -2,31 +2,47 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mosad.seil0.projectlaogai"> package="org.mosad.seil0.projectlaogai">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.NFC" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="@xml/backup_descriptor"
android:icon="@mipmap/ic_laogai_icon" android:icon="@mipmap/ic_laogai_icon"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_laogai_icon" android:roundIcon="@mipmap/ic_laogai_icon"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme.Light">
<activity <activity
android:name=".SplashActivity" android:name=".SplashActivity"
android:label="@string/app_name"
android:theme="@style/SplashTheme" android:theme="@style/SplashTheme"
android:screenOrientation="portrait"> android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
<meta-data android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity> </activity>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar" android:theme="@style/AppTheme.Light"
android:screenOrientation="portrait"> android:screenOrientation="portrait"
android:launchMode="singleTop">
<!-- nfc stuff -->
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED"/>
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter" />
</activity> </activity>
</application> </application>

View File

@ -0,0 +1,227 @@
/**
* Utils.kt
*
* Copyright (C) 2011 Eric Butler
* Copyright (C) 2019 <seil0@mosad.xyz>
*
* Authors:
* Eric Butler <eric@codebutler.com>
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package com.codebutler.farebot
import android.app.Activity
import android.app.AlertDialog
import android.util.Log
import android.view.WindowManager
import com.codebutler.farebot.card.desfire.DesfireException
import com.codebutler.farebot.card.desfire.DesfireFileSettings
import com.codebutler.farebot.card.desfire.DesfireProtocol
import org.w3c.dom.Node
import java.io.StringWriter
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import kotlin.experimental.and
class Utils {
companion object {
private val TAG = Utils::class.java.name
@Suppress("unused")
fun showError(activity: Activity, ex: Exception) {
Log.e(activity.javaClass.name, ex.message, ex)
AlertDialog.Builder(activity)
.setMessage(getErrorMessage(ex))
.show()
}
@Suppress("unused")
fun showErrorAndFinish(activity: Activity, ex: Exception) {
try {
Log.e(activity.javaClass.name, getErrorMessage(ex))
ex.printStackTrace()
AlertDialog.Builder(activity)
.setMessage(getErrorMessage(ex))
.setCancelable(false)
.setPositiveButton(android.R.string.ok) { _, _ -> activity.finish() }
.show()
} catch (unused: WindowManager.BadTokenException) {
/* Ignore... happens if the activity was destroyed */
}
}
@Throws(Exception::class)
fun getHexString(b: ByteArray): String {
var result = ""
for (i in b.indices) {
result += ((b[i] and 0xff.toByte()) + 0x100).toString(16).substring(1)
}
return result
}
@Suppress("unused")
fun getHexString(b: ByteArray, defaultResult: String): String {
return try {
getHexString(b)
} catch (ex: Exception) {
defaultResult
}
}
@Suppress("unused")
fun hexStringToByteArray(s: String): ByteArray {
if (s.length % 2 != 0) {
throw IllegalArgumentException("Bad input string: $s")
}
val len = s.length
val data = ByteArray(len / 2)
var i = 0
while (i < len) {
data[i / 2] = ((Character.digit(s[i], 16) shl 4) + Character.digit(s[i + 1], 16)).toByte()
i += 2
}
return data
}
@JvmOverloads
fun byteArrayToInt(b: ByteArray, offset: Int = 0, length: Int = b.size): Int {
return byteArrayToLong(b, offset, length).toInt()
}
fun byteArrayToLong(b: ByteArray, offset: Int, length: Int): Long {
if (b.size < length)
throw IllegalArgumentException("length must be less than or equal to b.length")
var value: Long = 0
for (i in 0 until length) {
val shift = (length - 1 - i) * 8
value += ((b[i + offset].toInt() and 0x000000FF).toLong() shl shift)
}
return value
}
@Suppress("unused")
fun byteArraySlice(b: ByteArray, offset: Int, length: Int): ByteArray {
val ret = ByteArray(length)
for (i in 0 until length)
ret[i] = b[offset + i]
return ret
}
@Suppress("unused")
@Throws(Exception::class)
fun xmlNodeToString(node: Node): String {
// The amount of code required to do simple things in Java is incredible.
val source = DOMSource(node)
val stringWriter = StringWriter()
val result = StreamResult(stringWriter)
val factory = TransformerFactory.newInstance()
val transformer = factory.newTransformer()
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
transformer.uriResolver = null
transformer.transform(source, result)
return stringWriter.buffer.toString()
}
fun getErrorMessage(ex: Throwable): String {
var errorMessage: String? = ex.localizedMessage
if (errorMessage == null)
errorMessage = ex.message
if (errorMessage == null)
errorMessage = ex.toString()
if (ex.cause != null) {
var causeMessage: String? = ex.cause!!.localizedMessage
if (causeMessage == null)
causeMessage = ex.cause!!.message
if (causeMessage == null)
causeMessage = ex.cause.toString()
errorMessage += ": $causeMessage"
}
return errorMessage
}
@Suppress("unused")
fun <T> findInList(list: List<T>, matcher: Matcher<T>): T? {
for (item in list) {
if (matcher.matches(item)) {
return item
}
}
return null
}
interface Matcher<T> {
fun matches(t: T): Boolean
}
fun selectAppFile(tag: DesfireProtocol, appID: Int, fileID: Int): DesfireFileSettings? {
try {
tag.selectApp(appID)
} catch (e: DesfireException) {
Log.w(TAG, "App not found")
return null
}
return try {
tag.getFileSettings(fileID)
} catch (e: DesfireException) {
Log.w(TAG, "File not found")
null
}
}
fun arrayContains(arr: IntArray, item: Int): Boolean {
for (i in arr)
if (i == item)
return true
return false
}
@Suppress("unused")
fun containsAppFile(tag: DesfireProtocol, appID: Int, fileID: Int): Boolean {
try {
tag.selectApp(appID)
} catch (e: DesfireException) {
Log.w(TAG, "App not found")
Log.w(TAG, e)
return false
}
return try {
arrayContains(tag.fileList, fileID)
} catch (e: DesfireException) {
Log.w(TAG, "File not found")
Log.w(TAG, e)
false
}
}
}
}

View File

@ -0,0 +1,9 @@
package com.codebutler.farebot.card.desfire
/**
* Created by Jakob Wenzel on 16.11.13.
*/
class DesfireException : Exception {
constructor(message: String) : super(message)
constructor(cause: Throwable) : super(cause)
}

View File

@ -0,0 +1,104 @@
/**
* DesfireFile.kt
*
* Copyright (C) 2011 Eric Butler
* Copyright (C) 2019 <seil0@mosad.xyz>
*
* Authors:
* Eric Butler <eric@codebutler.com>
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package com.codebutler.farebot.card.desfire
import android.os.Parcel
import android.os.Parcelable
import com.codebutler.farebot.card.desfire.DesfireFileSettings.RecordDesfireFileSettings
import org.apache.commons.lang3.ArrayUtils
open class DesfireFile private constructor(val id: Int, private val fileSettings: DesfireFileSettings?, val data: ByteArray) :
Parcelable {
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
if (this is InvalidDesfireFile) {
parcel.writeInt(1)
parcel.writeString(this.errorMessage)
} else {
parcel.writeInt(0)
parcel.writeParcelable(fileSettings, 0)
parcel.writeInt(data.size)
parcel.writeByteArray(data)
}
}
override fun describeContents(): Int {
return 0
}
class RecordDesfireFile(fileId: Int, fileSettings: DesfireFileSettings, fileData: ByteArray) :
DesfireFile(fileId, fileSettings, fileData) {
private val records: Array<DesfireRecord?>
init {
val settings = fileSettings as RecordDesfireFileSettings
val records = arrayOfNulls<DesfireRecord>(settings.curRecords)
for (i in 0 until settings.curRecords) {
val offset = settings.recordSize * i
records[i] = DesfireRecord(ArrayUtils.subarray(data, offset, offset + settings.recordSize))
}
this.records = records
}
}
class InvalidDesfireFile(fileId: Int, val errorMessage: String?) : DesfireFile(fileId, null, ByteArray(0))
companion object {
fun create(fileId: Int, fileSettings: DesfireFileSettings, fileData: ByteArray): DesfireFile {
return (fileSettings as? RecordDesfireFileSettings)?.let { RecordDesfireFile(fileId, it, fileData) }
?: DesfireFile(fileId, fileSettings, fileData)
}
@Suppress("unused")
@JvmField
val CREATOR: Parcelable.Creator<DesfireFile> = object : Parcelable.Creator<DesfireFile> {
override fun createFromParcel(source: Parcel): DesfireFile {
val fileId = source.readInt()
val isError = source.readInt() == 1
return if (!isError) {
val fileSettings =
source.readParcelable<Parcelable>(DesfireFileSettings::class.java.classLoader) as DesfireFileSettings
val dataLength = source.readInt()
val fileData = ByteArray(dataLength)
source.readByteArray(fileData)
create(fileId, fileSettings, fileData)
} else {
InvalidDesfireFile(fileId, source.readString())
}
}
override fun newArray(size: Int): Array<DesfireFile?> {
return arrayOfNulls(size)
}
}
}
}

View File

@ -0,0 +1,248 @@
/**
* DesfireFileSettings.kt
*
* Copyright (C) 2011 Eric Butler
* Copyright (C) 2019 <seil0@mosad.xyz>
*
* Authors:
* Eric Butler <eric@codebutler.com>
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package com.codebutler.farebot.card.desfire
import android.os.Parcel
import android.os.Parcelable
import com.codebutler.farebot.Utils
import org.apache.commons.lang3.ArrayUtils
import java.io.ByteArrayInputStream
abstract class DesfireFileSettings : Parcelable {
private val fileType: Byte
private val commSetting: Byte
private val accessRights: ByteArray
@Suppress("unused")
val fileTypeName: String
get() {
return when (fileType) {
STANDARD_DATA_FILE -> "Standard"
BACKUP_DATA_FILE -> "Backup"
VALUE_FILE -> "Value"
LINEAR_RECORD_FILE -> "Linear Record"
CYCLIC_RECORD_FILE -> "Cyclic Record"
else -> "Unknown"
}
}
private constructor(stream: ByteArrayInputStream) {
fileType = stream.read().toByte()
commSetting = stream.read().toByte()
accessRights = ByteArray(2)
stream.read(accessRights, 0, accessRights.size)
}
private constructor(fileType: Byte, commSetting: Byte, accessRights: ByteArray) {
this.fileType = fileType
this.commSetting = commSetting
this.accessRights = accessRights
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeByte(fileType)
parcel.writeByte(commSetting)
parcel.writeInt(accessRights.size)
parcel.writeByteArray(accessRights)
}
override fun describeContents(): Int {
return 0
}
class StandardDesfireFileSettings : DesfireFileSettings {
private val fileSize: Int
internal constructor(stream: ByteArrayInputStream) : super(stream) {
val buf = ByteArray(3)
stream.read(buf, 0, buf.size)
ArrayUtils.reverse(buf)
fileSize = Utils.byteArrayToInt(buf)
}
internal constructor(
fileType: Byte,
commSetting: Byte,
accessRights: ByteArray,
fileSize: Int
) : super(fileType, commSetting, accessRights) {
this.fileSize = fileSize
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
super.writeToParcel(parcel, flags)
parcel.writeInt(fileSize)
}
}
class RecordDesfireFileSettings : DesfireFileSettings {
private val maxRecords: Int
val recordSize: Int
val curRecords: Int
constructor(stream: ByteArrayInputStream) : super(stream) {
var buf = ByteArray(3)
stream.read(buf, 0, buf.size)
ArrayUtils.reverse(buf)
recordSize = Utils.byteArrayToInt(buf)
buf = ByteArray(3)
stream.read(buf, 0, buf.size)
ArrayUtils.reverse(buf)
maxRecords = Utils.byteArrayToInt(buf)
buf = ByteArray(3)
stream.read(buf, 0, buf.size)
ArrayUtils.reverse(buf)
curRecords = Utils.byteArrayToInt(buf)
}
internal constructor(
fileType: Byte,
commSetting: Byte,
accessRights: ByteArray,
recordSize: Int,
maxRecords: Int,
curRecords: Int
) : super(fileType, commSetting, accessRights) {
this.recordSize = recordSize
this.maxRecords = maxRecords
this.curRecords = curRecords
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
super.writeToParcel(parcel, flags)
parcel.writeInt(recordSize)
parcel.writeInt(maxRecords)
parcel.writeInt(curRecords)
}
}
class ValueDesfireFileSettings(stream: ByteArrayInputStream) : DesfireFileSettings(stream) {
private val lowerLimit: Int
private val upperLimit: Int
val value: Int
private val limitedCreditEnabled: Byte
init {
var buf = ByteArray(4)
stream.read(buf, 0, buf.size)
ArrayUtils.reverse(buf)
lowerLimit = Utils.byteArrayToInt(buf)
buf = ByteArray(4)
stream.read(buf, 0, buf.size)
ArrayUtils.reverse(buf)
upperLimit = Utils.byteArrayToInt(buf)
buf = ByteArray(4)
stream.read(buf, 0, buf.size)
ArrayUtils.reverse(buf)
value = Utils.byteArrayToInt(buf)
buf = ByteArray(1)
stream.read(buf, 0, buf.size)
limitedCreditEnabled = buf[0]
//http://www.skyetek.com/docs/m2/desfire.pdf
//http://neteril.org/files/M075031_desfire.pdf
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
super.writeToParcel(parcel, flags)
parcel.writeInt(lowerLimit)
parcel.writeInt(upperLimit)
parcel.writeInt(value)
parcel.writeByte(limitedCreditEnabled)
}
}
class UnsupportedDesfireFileSettings(fileType: Byte) :
DesfireFileSettings(fileType, java.lang.Byte.MIN_VALUE, ByteArray(0))
companion object {
/* DesfireFile Types */
internal const val STANDARD_DATA_FILE = 0x00.toByte()
internal const val BACKUP_DATA_FILE = 0x01.toByte()
internal const val VALUE_FILE = 0x02.toByte()
internal const val LINEAR_RECORD_FILE = 0x03.toByte()
internal const val CYCLIC_RECORD_FILE = 0x04.toByte()
@Throws(DesfireException::class)
fun create(data: ByteArray): DesfireFileSettings {
val fileType = data[0]
val stream = ByteArrayInputStream(data)
return if (fileType == STANDARD_DATA_FILE || fileType == BACKUP_DATA_FILE)
StandardDesfireFileSettings(stream)
else if (fileType == LINEAR_RECORD_FILE || fileType == CYCLIC_RECORD_FILE)
RecordDesfireFileSettings(stream)
else if (fileType == VALUE_FILE)
ValueDesfireFileSettings(stream)
else
throw DesfireException("Unknown file type: " + Integer.toHexString(fileType.toInt()))
}
@Suppress("unused")
@JvmField
val CREATOR: Parcelable.Creator<DesfireFileSettings> = object : Parcelable.Creator<DesfireFileSettings> {
override fun createFromParcel(source: Parcel): DesfireFileSettings {
val fileType = source.readByte()
val commSetting = source.readByte()
val accessRights = ByteArray(source.readInt())
source.readByteArray(accessRights)
if (fileType == STANDARD_DATA_FILE || fileType == BACKUP_DATA_FILE) {
val fileSize = source.readInt()
return StandardDesfireFileSettings(fileType, commSetting, accessRights, fileSize)
} else if (fileType == LINEAR_RECORD_FILE || fileType == CYCLIC_RECORD_FILE) {
val recordSize = source.readInt()
val maxRecords = source.readInt()
val curRecords = source.readInt()
return RecordDesfireFileSettings(
fileType,
commSetting,
accessRights,
recordSize,
maxRecords,
curRecords
)
} else {
return UnsupportedDesfireFileSettings(fileType)
}
}
override fun newArray(size: Int): Array<DesfireFileSettings?> {
return arrayOfNulls(size)
}
}
}
}

View File

@ -0,0 +1,181 @@
/**
* DesfireManufacturingData.kt
*
* Copyright (C) 2011 Eric Butler
* Copyright (C) 2019 <seil0@mosad.xyz>
*
* Authors:
* Eric Butler <eric@codebutler.com>
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package com.codebutler.farebot.card.desfire
import android.os.Parcel
import android.os.Parcelable
import com.codebutler.farebot.Utils
import org.w3c.dom.Element
import java.io.ByteArrayInputStream
class DesfireManufacturingData : Parcelable {
private val hwVendorID: Int
private val hwType: Int
private val hwSubType: Int
private val hwMajorVersion: Int
private val hwMinorVersion: Int
private val hwStorageSize: Int
private val hwProtocol: Int
private val swVendorID: Int
private val swType: Int
private val swSubType: Int
private val swMajorVersion: Int
private val swMinorVersion: Int
private val swStorageSize: Int
private val swProtocol: Int
private val uid: Int
private val batchNo: Int
private val weekProd: Int
private val yearProd: Int
constructor(data: ByteArray) {
val stream = ByteArrayInputStream(data)
hwVendorID = stream.read()
hwType = stream.read()
hwSubType = stream.read()
hwMajorVersion = stream.read()
hwMinorVersion = stream.read()
hwStorageSize = stream.read()
hwProtocol = stream.read()
swVendorID = stream.read()
swType = stream.read()
swSubType = stream.read()
swMajorVersion = stream.read()
swMinorVersion = stream.read()
swStorageSize = stream.read()
swProtocol = stream.read()
// FIXME: This has fewer digits than what's contained in EXTRA_ID, why?
var buf = ByteArray(7)
stream.read(buf, 0, buf.size)
uid = Utils.byteArrayToInt(buf)
// FIXME: This is returning a negative number. Probably is unsigned.
buf = ByteArray(5)
stream.read(buf, 0, buf.size)
batchNo = Utils.byteArrayToInt(buf)
// FIXME: These numbers aren't making sense.
weekProd = stream.read()
yearProd = stream.read()
}
private constructor(element: Element) {
hwVendorID = Integer.parseInt(element.getElementsByTagName("hw-vendor-id").item(0).textContent)
hwType = Integer.parseInt(element.getElementsByTagName("hw-type").item(0).textContent)
hwSubType = Integer.parseInt(element.getElementsByTagName("hw-sub-type").item(0).textContent)
hwMajorVersion = Integer.parseInt(element.getElementsByTagName("hw-major-version").item(0).textContent)
hwMinorVersion = Integer.parseInt(element.getElementsByTagName("hw-minor-version").item(0).textContent)
hwStorageSize = Integer.parseInt(element.getElementsByTagName("hw-storage-size").item(0).textContent)
hwProtocol = Integer.parseInt(element.getElementsByTagName("hw-protocol").item(0).textContent)
swVendorID = Integer.parseInt(element.getElementsByTagName("sw-vendor-id").item(0).textContent)
swType = Integer.parseInt(element.getElementsByTagName("sw-type").item(0).textContent)
swSubType = Integer.parseInt(element.getElementsByTagName("sw-sub-type").item(0).textContent)
swMajorVersion = Integer.parseInt(element.getElementsByTagName("sw-major-version").item(0).textContent)
swMinorVersion = Integer.parseInt(element.getElementsByTagName("sw-minor-version").item(0).textContent)
swStorageSize = Integer.parseInt(element.getElementsByTagName("sw-storage-size").item(0).textContent)
swProtocol = Integer.parseInt(element.getElementsByTagName("sw-protocol").item(0).textContent)
uid = Integer.parseInt(element.getElementsByTagName("uid").item(0).textContent)
batchNo = Integer.parseInt(element.getElementsByTagName("batch-no").item(0).textContent)
weekProd = Integer.parseInt(element.getElementsByTagName("week-prod").item(0).textContent)
yearProd = Integer.parseInt(element.getElementsByTagName("year-prod").item(0).textContent)
}
private constructor(parcel: Parcel) {
hwVendorID = parcel.readInt()
hwType = parcel.readInt()
hwSubType = parcel.readInt()
hwMajorVersion = parcel.readInt()
hwMinorVersion = parcel.readInt()
hwStorageSize = parcel.readInt()
hwProtocol = parcel.readInt()
swVendorID = parcel.readInt()
swType = parcel.readInt()
swSubType = parcel.readInt()
swMajorVersion = parcel.readInt()
swMinorVersion = parcel.readInt()
swStorageSize = parcel.readInt()
swProtocol = parcel.readInt()
uid = parcel.readInt()
batchNo = parcel.readInt()
weekProd = parcel.readInt()
yearProd = parcel.readInt()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(hwVendorID)
parcel.writeInt(hwType)
parcel.writeInt(hwSubType)
parcel.writeInt(hwMajorVersion)
parcel.writeInt(hwMinorVersion)
parcel.writeInt(hwStorageSize)
parcel.writeInt(hwProtocol)
parcel.writeInt(swVendorID)
parcel.writeInt(swType)
parcel.writeInt(swSubType)
parcel.writeInt(swMajorVersion)
parcel.writeInt(swMinorVersion)
parcel.writeInt(swStorageSize)
parcel.writeInt(swProtocol)
parcel.writeInt(uid)
parcel.writeInt(batchNo)
parcel.writeInt(weekProd)
parcel.writeInt(yearProd)
}
override fun describeContents(): Int {
return 0
}
companion object {
@Suppress("unused")
fun fromXml(element: Element): DesfireManufacturingData {
return DesfireManufacturingData(element)
}
@Suppress("unused")
@JvmField
val CREATOR: Parcelable.Creator<DesfireManufacturingData> =
object : Parcelable.Creator<DesfireManufacturingData> {
override fun createFromParcel(source: Parcel): DesfireManufacturingData {
return DesfireManufacturingData(source)
}
override fun newArray(size: Int): Array<DesfireManufacturingData?> {
return arrayOfNulls(size)
}
}
}
}

View File

@ -0,0 +1,214 @@
/**
* DesfireProtocol.kt
*
* Copyright (C) 2011 Eric Butler
* Copyright (C) 2019 <seil0@mosad.xyz>
*
* Authors:
* Eric Butler <eric@codebutler.com>
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package com.codebutler.farebot.card.desfire
import android.nfc.tech.IsoDep
import com.codebutler.farebot.Utils
import java.io.ByteArrayOutputStream
import java.io.IOException
import org.apache.commons.lang3.ArrayUtils
import kotlin.experimental.and
class DesfireProtocol(private val mTagTech: IsoDep) {
@Suppress("unused")
val manufacturingData: DesfireManufacturingData
@Throws(DesfireException::class)
get() {
val respBuffer = sendRequest(GET_MANUFACTURING_DATA)
if (respBuffer.size != 28)
throw DesfireException("Invalid response")
return DesfireManufacturingData(respBuffer)
}
@Suppress("unused")
val appList: IntArray
@Throws(DesfireException::class)
get() {
val appDirBuf = sendRequest(GET_APPLICATION_DIRECTORY)
val appIds = IntArray(appDirBuf.size / 3)
var app = 0
while (app < appDirBuf.size) {
val appId = ByteArray(3)
System.arraycopy(appDirBuf, app, appId, 0, 3)
appIds[app / 3] = Utils.byteArrayToInt(appId)
app += 3
}
return appIds
}
val fileList: IntArray
@Throws(DesfireException::class)
get() {
val buf = sendRequest(GET_FILES)
val fileIds = IntArray(buf.size)
for (x in buf.indices) {
fileIds[x] = buf[x].toInt()
}
return fileIds
}
@Throws(DesfireException::class)
fun selectApp(appId: Int) {
val appIdBuff = ByteArray(3)
appIdBuff[0] = (appId and 0xFF0000 shr 16).toByte()
appIdBuff[1] = (appId and 0xFF00 shr 8).toByte()
appIdBuff[2] = (appId and 0xFF).toByte()
sendRequest(SELECT_APPLICATION, appIdBuff)
}
@Throws(DesfireException::class)
fun getFileSettings(fileNo: Int): DesfireFileSettings {
val data = sendRequest(GET_FILE_SETTINGS, byteArrayOf(fileNo.toByte()))
return DesfireFileSettings.create(data)
}
@Suppress("unused")
@Throws(DesfireException::class)
fun readFile(fileNo: Int): ByteArray {
return sendRequest(
READ_DATA,
byteArrayOf(
fileNo.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte()
)
)
}
@Suppress("unused")
@Throws(DesfireException::class)
fun readRecord(fileNum: Int): ByteArray {
return sendRequest(
READ_RECORD,
byteArrayOf(
fileNum.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte()
)
)
}
@Throws(DesfireException::class)
fun readValue(fileNum: Int): Int {
val buf = sendRequest(READ_VALUE, byteArrayOf(fileNum.toByte()))
ArrayUtils.reverse(buf)
return Utils.byteArrayToInt(buf)
}
@Throws(DesfireException::class)
private fun sendRequest(command: Byte, parameters: ByteArray? = null): ByteArray {
val output = ByteArrayOutputStream()
var recvBuffer: ByteArray
try {
recvBuffer = mTagTech.transceive(wrapMessage(command, parameters))
} catch (e: IOException) {
throw DesfireException(e)
}
while (true) {
if (recvBuffer[recvBuffer.size - 2] != 0x91.toByte())
throw DesfireException("Invalid response")
output.write(recvBuffer, 0, recvBuffer.size - 2)
val status = recvBuffer[recvBuffer.size - 1]
if (status == OPERATION_OK) {
break
} else if (status == ADDITIONAL_FRAME) {
try {
recvBuffer = mTagTech.transceive(wrapMessage(GET_ADDITIONAL_FRAME, null))
} catch (e: IOException) {
throw DesfireException(e)
}
} else if (status == PERMISSION_DENIED) {
throw DesfireException("Permission denied")
} else {
throw DesfireException("Unknown status code: " + Integer.toHexString((status and 0xFF.toByte()).toInt()))
}
}
return output.toByteArray()
}
@Throws(DesfireException::class)
private fun wrapMessage(command: Byte, parameters: ByteArray?): ByteArray {
val stream = ByteArrayOutputStream()
stream.write(0x90.toByte().toInt())
stream.write(command.toInt())
stream.write(0x00.toByte().toInt())
stream.write(0x00.toByte().toInt())
if (parameters != null) {
stream.write(parameters.size.toByte().toInt())
try {
stream.write(parameters)
} catch (e: IOException) {
throw DesfireException(e)
}
}
stream.write(0x00.toByte().toInt())
return stream.toByteArray()
}
companion object {
/* Commands */
internal const val GET_MANUFACTURING_DATA = 0x60.toByte()
internal const val GET_APPLICATION_DIRECTORY = 0x6A.toByte()
internal const val GET_ADDITIONAL_FRAME = 0xAF.toByte()
internal const val SELECT_APPLICATION = 0x5A.toByte()
internal const val READ_DATA = 0xBD.toByte()
internal const val READ_RECORD = 0xBB.toByte()
internal const val READ_VALUE = 0x6C.toByte()
internal const val GET_FILES = 0x6F.toByte()
internal const val GET_FILE_SETTINGS = 0xF5.toByte()
/* Status codes */
internal const val OPERATION_OK = 0x00.toByte()
internal const val PERMISSION_DENIED = 0x9D.toByte()
internal const val ADDITIONAL_FRAME = 0xAF.toByte()
}
}

View File

@ -0,0 +1,26 @@
/*
* DesfireRecord.kt
*
* Copyright (C) 2011 Eric Butler
* Copyright (C) 2019 <seil0@mosad.xyz>
*
* Authors:
* Eric Butler <eric@codebutler.com>
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package com.codebutler.farebot.card.desfire
class DesfireRecord(val data: ByteArray)

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2018 <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,50 +22,61 @@
package org.mosad.seil0.projectlaogai package org.mosad.seil0.projectlaogai
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.nfc.NfcAdapter
import android.nfc.NfcManager
import android.nfc.tech.NfcA
import android.os.Bundle import android.os.Bundle
import android.util.Log
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
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.FragmentTransaction
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.aesthetic.Aesthetic
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.jetbrains.anko.doAsync import org.mosad.seil0.projectlaogai.controller.CacheController
import org.jetbrains.anko.uiThread import org.mosad.seil0.projectlaogai.controller.NFCMensaCard
import org.mosad.seil0.projectlaogai.controller.PreferencesController
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorAccent
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorPrimary
import org.mosad.seil0.projectlaogai.fragments.* import org.mosad.seil0.projectlaogai.fragments.*
import org.mosad.seil0.projectlaogai.hsoparser.*
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
/**
* TODO save the current fragment to show it when the app is restarted
*/
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
private val mensaParser = MensaParser() private var activeFragment: Fragment = HomeFragment() // the currently active fragment, home at the start
private val timeTableParser = TimeTableParser() private val className = "MainActivity"
private var weekMenus = ArrayList<Meal>() private lateinit var adapter: NfcAdapter
private var courseTTLinkList = ArrayList<CourseTTLink>() private lateinit var pendingIntent: PendingIntent
private var timeTableCurrentWeek = arrayOf<Array<Lesson>>() private lateinit var intentFiltersArray: Array<IntentFilter>
private var timeTableNextWeek = arrayOf<Array<Lesson>>() private lateinit var techListsArray: Array<Array<String>>
private var useNFC = false
private lateinit var course: CourseTTLink
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
Aesthetic.attach(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
// load mensa, timetable and color
// load mensa and timetable
load() load()
initAesthetic()
//init home fragment TODO make a abstract fragment class initForegroundDispatch()
val homeFragment = HomeFragment()
homeFragment.setMainActivity(this)
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragment_container, homeFragment)
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
@ -74,12 +85,48 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
toggle.syncState() toggle.syncState()
nav_view.setNavigationItemSelectedListener(this) nav_view.setNavigationItemSelectedListener(this)
// based on the inent we get, call readBalance or open a Fragment
when (intent.action) {
NfcAdapter.ACTION_TECH_DISCOVERED -> NFCMensaCard.readBalance(intent, this)
"org.mosad.seil0.projectlaogai.fragments.MensaFragment" -> activeFragment = MensaFragment()
"org.mosad.seil0.projectlaogai.fragments.TimeTableFragment" -> activeFragment = TimeTableFragment()
"org.mosad.seil0.projectlaogai.fragments.MoodleFragment" -> activeFragment = MoodleFragment()
}
// open the activeFragment, default is the HomeFragment
fragmentTransaction.replace(R.id.fragment_container, activeFragment)
fragmentTransaction.commit()
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action)
NFCMensaCard.readBalance(intent, this)
}
override fun onResume() {
super.onResume()
Aesthetic.resume(this)
if(useNFC)
adapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray)
}
override fun onPause() {
super.onPause()
Aesthetic.pause(this)
if(useNFC)
adapter.disableForegroundDispatch(this)
} }
override fun onBackPressed() { override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) { if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START) drawer_layout.closeDrawer(GravityCompat.START)
} else { } else {
// TODO only call on double tap
super.onBackPressed() super.onBackPressed()
} }
} }
@ -102,147 +149,71 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
override fun onNavigationItemSelected(item: MenuItem): Boolean { override fun onNavigationItemSelected(item: MenuItem): Boolean {
// Handle navigation view item clicks here. // Handle navigation view item clicks here.
when (item.itemId) { activeFragment = when(item.itemId) {
R.id.nav_home -> { R.id.nav_home -> HomeFragment()
val homeFragment = HomeFragment() R.id.nav_mensa -> MensaFragment()
homeFragment.setMainActivity(this) R.id.nav_timetable -> TimeTableFragment()
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction() R.id.nav_moodle -> MoodleFragment()
fragmentTransaction.replace(R.id.fragment_container, homeFragment) R.id.nav_settings -> SettingsFragment()
fragmentTransaction.commit() else -> HomeFragment()
}
R.id.nav_mensa -> {
val mensaFragment = MensaFragment()
mensaFragment.setMainActivity(this)
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragment_container, mensaFragment)
fragmentTransaction.commit()
}
R.id.nav_timetable -> {
val timeTableFragment = TimeTableFragment()
timeTableFragment.setMainActivity(this)
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragment_container, timeTableFragment)
fragmentTransaction.commit()
}
R.id.nav_moodle -> {
// val moodleFragment = MoodleFragment()
//
// val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
// fragmentTransaction.replace(R.id.fragment_container, moodleFragment)
// fragmentTransaction.commit()
}
R.id.nav_settings -> {
val settingsFragment = SettingsFragment()
settingsFragment.setMainActivity(this)
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragment_container, settingsFragment)
fragmentTransaction.commit()
}
} }
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragment_container, activeFragment)
fragmentTransaction.commit()
drawer_layout.closeDrawer(GravityCompat.START) drawer_layout.closeDrawer(GravityCompat.START)
return true return true
} }
/**
* update the gui with the data of the new selected course
* save selected course and courseTTLink
*/
fun updateCourse(course: CourseTTLink) {
println(course.course)
println(course.courseTTLink)
this.course = course
// save new selected course
val sharedPref = getPreferences(MODE_PRIVATE) ?: return
with (sharedPref.edit()) {
putString(getString(R.string.save_key_course), course.course)
putString(getString(R.string.save_key_courseTTLink), course.courseTTLink.replace("http", "https"))
apply()
}
timeTableCurrentWeek = timeTableParser.getTimeTable(course.courseTTLink.replace("http", "https"))
}
/** /**
* load the mensa menus of the current week * load the mensa menus of the current week
*/ */
private fun load() { private fun load() {
val startupTime = measureTimeMillis {
// load saved course PreferencesController.load(this) // load the settings, must be finished before doing anything else
val sharedPref = getPreferences(MODE_PRIVATE) ?: return CacheController(this) // load the cache
course = CourseTTLink( }
sharedPref.getString(getString(R.string.save_key_courseTTLink), Log.i(className, "startup completed in $startupTime ms")
"https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0")!!, }
sharedPref.getString(getString(R.string.save_key_course), "AI3")!!
) private fun initAesthetic() {
// If we haven't set any defaults, do that now
/** if (Aesthetic.isFirstTime) {
* load mensa, course timetable and courselist from the swfr/hso website // this is executed on the first app start, use this to show tutorial etc.
* TODO make an API see https://git.mosad.xyz/Seil0/TheCitadelofRicks Aesthetic.config {
*/ activityTheme(R.style.AppTheme_Light)
val time = measureTimeMillis { apply()
/* getting the course list should be faster than the timetable, }
* we need have time until the user opens the dialog
*/ SettingsFragment().selectCourse(this) // FIXME this is not working
doAsync { }
courseTTLinkList = timeTableParser.getCourseTTLinks()
} Aesthetic.config {
colorPrimary(cColorPrimary)
doAsync { colorPrimaryDark(cColorPrimary)
try { colorAccent(cColorAccent)
timeTableNextWeek = timeTableParser.getTimeTable(course.courseTTLink.replace("week=0","week=1")) navigationViewMode(NavigationViewMode.SELECTED_ACCENT)
} catch (e: Exception) { apply()
e.stackTrace
}
}
val t1 = doAsync {
weekMenus = mensaParser.getMensaMenu()
}
val t2 = doAsync {
try {
timeTableCurrentWeek = timeTableParser.getTimeTable(course.courseTTLink)
} catch (e: Exception) {
uiThread {
MaterialDialog(this@MainActivity)
.title(R.string.error)
.message(R.string.no_tt_error)
.show()
}
e.stackTrace
}
}
t1.get()
t2.get()
} }
println("Completed in $time ms")
} }
fun getCourseTTLinkList(): ArrayList<CourseTTLink>{ private fun initForegroundDispatch() {
return courseTTLinkList val nfcManager = this.getSystemService(Context.NFC_SERVICE) as NfcManager
} val nfcAdapter = nfcManager.defaultAdapter
fun getTimeTableCurrentWeek(): Array<Array<Lesson>> { if (nfcAdapter != null) {
return timeTableCurrentWeek useNFC = true
} intentFiltersArray = arrayOf(IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED).apply { addDataType("*/*") })
techListsArray = arrayOf(arrayOf(NfcA::class.java.name))
fun getTimeTableNextWeek(): Array<Array<Lesson>> { adapter = NfcAdapter.getDefaultAdapter(this)
return timeTableNextWeek pendingIntent = PendingIntent.getActivity(
} this, 0,
Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0
fun getWeekMenu(): ArrayList<Meal>{ )
return weekMenus }
}
fun getCourse(): CourseTTLink {
return course
} }
} }

View File

@ -0,0 +1,177 @@
/**
* ProjectLaogai
*
* Copyright 2019-2020 <seil0@mosad.xyz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
package org.mosad.seil0.projectlaogai.controller
import android.content.Context
import android.util.Log
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonParser
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
import org.mosad.seil0.projectlaogai.hsoparser.Course
import org.mosad.seil0.projectlaogai.hsoparser.MensaMenu
import org.mosad.seil0.projectlaogai.hsoparser.TimetableCourseWeek
import java.io.BufferedReader
import java.io.File
import java.io.FileReader
import java.util.*
import kotlin.collections.ArrayList
class CacheController(cont: Context) {
private val className = "CacheController"
private val context = cont
init {
val cal = Calendar.getInstance()
val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
val currentTime = System.currentTimeMillis() / 1000
// check if we need to update the mensa data before displaying it
readMensa(context)
cal.time = Date(mensaMenu.meta.updateTime * 1000)
// if a) it's monday and the last cache update was on sunday or b) the cache is older than 24hr, update blocking
if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - mensaMenu.meta.updateTime) > 86400) {
Log.i(className, "update mensa blocking")
GlobalScope.launch(Dispatchers.Default) { TCoRAPIController.getMensa(context).join() }
}
// check if we need to update the timetables before displaying them
readTimetable(cCourse.courseName, 0, context)
cal.time = Date(timetables[0].meta.updateTime * 1000)
// if a) it`s monday and the last cache update was not on a sunday or b) the cache is older than 24hr, update blocking
if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - timetables[0].meta.updateTime) > 86400) {
Log.i(className, "updating timetable after sunday!")
GlobalScope.launch(Dispatchers.Default) {
val threads = listOf(
TCoRAPIController.getTimetable(cCourse.courseName, 0, context),
TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
)
threads.joinAll()
}
TCoRAPIController.getTimetable(cCourse.courseName, 0, context)
TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
}
// check if an update is necessary, not blocking
if (currentTime - PreferencesController.coursesCacheTime > 86400)
TCoRAPIController.getCoursesList(context)
if (currentTime - PreferencesController.mensaCacheTime > 10800)
TCoRAPIController.getMensa(context)
if (currentTime - PreferencesController.timetableCacheTime > 10800) {
TCoRAPIController.getTimetable(cCourse.courseName, 0, context)
TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
}
readStartCache(cCourse.courseName)
}
companion object {
var coursesList = ArrayList<Course>()
var timetables = ArrayList<TimetableCourseWeek>()
var mensaMenu = MensaMenu()
/**
* read the courses list from the cached file
* add them to the coursesList object
*/
private fun readCoursesList(context: Context) {
val file = File(context.filesDir, "courses.json")
// make sure the file exists
if (!file.exists())
runBlocking { TCoRAPIController.getCoursesList(context).join() }
val fileReader = FileReader(file)
val bufferedReader = BufferedReader(fileReader)
val coursesObject = JsonParser.parseString(bufferedReader.readLine()).asJsonObject
coursesList = Gson().fromJson(
coursesObject.getAsJsonArray("courses"),
object : TypeToken<List<Course>>() {}.type
)
}
/**
* get the MensaMenu object from the cached json,
* if cache is empty create the cache file
*/
fun readMensa(context: Context) {
val file = File(context.filesDir, "mensa.json")
// make sure the file exists
if (!file.exists())
runBlocking { TCoRAPIController.getMensa(context).join() }
val fileReader = FileReader(file)
val bufferedReader = BufferedReader(fileReader)
val mensaObject = JsonParser.parseString(bufferedReader.readLine()).asJsonObject
mensaMenu = GsonBuilder().create().fromJson(mensaObject, MensaMenu().javaClass)
}
/**
* read the weeks timetable from the cached file
* @param courseName the course name (e.g AI1)
* @param week the week to read (0 for the current and so on)
*/
fun readTimetable(courseName: String, week: Int, context: Context) {
val file = File(context.filesDir, "timetable-$courseName-$week.json")
// make sure the file exists
if (!file.exists())
runBlocking { TCoRAPIController.getTimetable(courseName, week, context).join() }
val fileReader = FileReader(file)
val bufferedReader = BufferedReader(fileReader)
val timetableObject = JsonParser.parseString(bufferedReader.readLine()).asJsonObject
// make sure you add the single weeks in the exact order!
if (timetables.size > week) {
timetables[week] = (Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass))
} else {
timetables.add(Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass))
}
}
}
/**
* read coursesList, mensa (current and next week), timetable (current and next week)
* @param courseName the course name (e.g AI1)
*/
private fun readStartCache(courseName: String) {
readCoursesList(context)
readMensa(context)
readTimetable(courseName, 0, context)
readTimetable(courseName, 1, context)
}
}

View File

@ -0,0 +1,105 @@
/**
* 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.content.Intent
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.nfc.tech.IsoDep
import android.util.Log
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.codebutler.farebot.Utils
import com.codebutler.farebot.card.desfire.DesfireFileSettings
import com.codebutler.farebot.card.desfire.DesfireProtocol
import kotlinx.android.synthetic.main.dialog_mensa_credit.*
import org.mosad.seil0.projectlaogai.R
import java.lang.Exception
class NFCMensaCard {
companion object {
private const val className = "NFCMensaCard"
private const val appId = 0x5F8415
private const val fileId = 1
/**
* read the current balance and last payment from the mensa card
* @param intent a nfc intent
* @param context the context to show the dialog in
*/
fun readBalance(intent: Intent, context: Context) {
val tag: Tag? = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
val isoDep = IsoDep.get(tag)
try {
isoDep.connect()
val card = DesfireProtocol(isoDep)
val settings = Utils.selectAppFile(card, appId, fileId)
if (settings is DesfireFileSettings.ValueDesfireFileSettings) {
val data = try {
card.readValue(fileId)
} catch (ex: Exception) { 0 }
lookAtMe(context, data, settings.value).show()
}
} catch (ex: Exception) {
Log.w(className,"could not connect to tag", ex)
}
}
/**
* generate the values for current balance and last payment
* if the easter egg is active use schmeckles as currency
* 0.0000075 = 1.11 / 148 / 1000 (dollar / shm / card multiplier)
* @param context the context to access resources
* @param currentRaw the raw card value of the current balance
* @param lastRaw the raw card value of the last payment
* @return the message containing all values
*/
private fun lookAtMe(context: Context, currentRaw: Int, lastRaw: Int): MaterialDialog {
val dialog = MaterialDialog(context)
.customView(R.layout.dialog_mensa_credit)
val current = if (!PreferencesController.oGiants) {
String.format("%.2f €", (currentRaw.toFloat() / 1000))
} else {
String.format("%.4f shm", (currentRaw.toFloat() * 0.0000075))
}
val last = if (!PreferencesController.oGiants) {
String.format("%.2f €", (lastRaw.toFloat() / 1000))
} else {
String.format("%.4f shm", (lastRaw.toFloat() * 0.0000075))
}
dialog.txtView_current.text = current
dialog.txtView_last.text = context.resources.getString(R.string.mensa_last, last)
return dialog
}
}
}

View File

@ -0,0 +1,183 @@
/**
* ProjectLaogai
*
* Copyright 2019-2020 <seil0@mosad.xyz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
package org.mosad.seil0.projectlaogai.controller
import android.content.Context
import android.graphics.Color
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.hsoparser.Course
/**
* The PreferencesController class
* contains all preferences and global variables that exist in this app
*/
class PreferencesController {
companion object {
var coursesCacheTime: Long = 0
var mensaCacheTime: Long = 0
var timetableCacheTime: Long = 0
var cColorPrimary: Int = Color.BLACK
var cColorAccent: Int = Color.parseColor("#3F51B5")
var cCourse = Course("https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0", "AI3")
var cShowBuffet = true
var oGiants = false
// the save function
fun save(context: Context) {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
// save the update times (cache)
with (sharedPref.edit()) {
putLong(context.getString(R.string.save_key_coursesCacheTime),
coursesCacheTime
)
putLong(context.getString(R.string.save_key_mensaCacheTime),
mensaCacheTime
)
putLong(context.getString(R.string.save_key_timetableCacheTime),
timetableCacheTime
)
apply()
}
}
/**
* save the course locally
*/
fun saveCourse(context: Context, course: Course) {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
with (sharedPref.edit()) {
putString(context.getString(R.string.save_key_course), course.courseName)
putString(context.getString(R.string.save_key_courseTTLink), course.courseLink)
apply()
}
cCourse = course
}
/**
* save the primary color
*/
fun saveColorPrimary(context: Context, colorPrimary: Int) {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
with (sharedPref.edit()) {
putInt(context.getString(R.string.save_key_colorPrimary),
colorPrimary
)
apply()
}
cColorPrimary = colorPrimary
}
/**
* save the accent color
*/
fun saveColorAccent(context: Context, colorAccent: Int) {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
with (sharedPref.edit()) {
putInt(context.getString(R.string.save_key_colorAccent),
colorAccent
)
apply()
}
cColorAccent = colorAccent
}
/**
* save showBuffet
*/
fun saveShowBuffet(context: Context, showBuffet: Boolean) {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
with (sharedPref.edit()) {
putBoolean(context.getString(R.string.save_key_showBuffet),
showBuffet
)
apply()
}
cShowBuffet = showBuffet
}
// the load function
fun load(context: Context) {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
// load the update times (cache)
coursesCacheTime = sharedPref.getLong(context.getString(
R.string.save_key_coursesCacheTime
), 0)
mensaCacheTime = sharedPref.getLong(context.getString(
R.string.save_key_mensaCacheTime
), 0)
timetableCacheTime = sharedPref.getLong(context.getString(
R.string.save_key_timetableCacheTime
), 0)
// load saved course
cCourse = Course(
sharedPref.getString(context.getString(R.string.save_key_courseTTLink), "https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0")!!,
sharedPref.getString(context.getString(R.string.save_key_course), "AI3")!!
)
// load saved colors
cColorPrimary = sharedPref.getInt(context.getString(
R.string.save_key_colorPrimary
), Color.BLACK)
cColorAccent = sharedPref.getInt(context.getString(
R.string.save_key_colorAccent
), Color.parseColor("#3F51B5"))
// load showBuffet
cShowBuffet = sharedPref.getBoolean(context.getString(
R.string.save_key_showBuffet
), true)
}
}
}

View File

@ -0,0 +1,126 @@
/**
* 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.*
import org.json.JSONObject
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.coursesCacheTime
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.mensaCacheTime
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.timetableCacheTime
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.net.URL
import kotlin.Exception
class TCoRAPIController {
companion object {
private const val className = "TCoRAPIController"
private const val tcorBaseURL = "https://tcor.mosad.xyz"
/**
* get the json object from tcor api and write it as file (cache)
*/
fun getCoursesList(context: Context) = GlobalScope.launch {
try {
val url = URL("$tcorBaseURL/courseList")
val file = File(context.filesDir, "courses.json")
// read data from the API
val coursesObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
// write the json object to a file
withContext(Dispatchers.IO) {
val writer = BufferedWriter(FileWriter(file))
writer.write(coursesObject.toString())
writer.close()
}
// update cache time
coursesCacheTime = System.currentTimeMillis() / 1000
PreferencesController.save(context)
} catch (ex: Exception) {
Log.e(className, "failed to get /courseList", ex)
}
}
/**
* get the json object from tcor api and write it as file (cache)
*/
fun getMensa(context: Context) = GlobalScope.launch {
try {
val url = URL("$tcorBaseURL/mensamenu")
val file = File(context.filesDir, "mensa.json")
// read data from the API
val mensaObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
// write the json object to a file
withContext(Dispatchers.IO) {
val writer = BufferedWriter(FileWriter(file))
writer.write(mensaObject.toString())
writer.close()
}
// update cache time
mensaCacheTime = System.currentTimeMillis() / 1000
PreferencesController.save(context)
} catch (ex: Exception) {
Log.e(className, "failed to get /mensamenu", ex)
}
}
/**
* get the json object from tcor api and write it as file (cache)
*/
fun getTimetable(courseName: String, week: Int, context: Context) = GlobalScope.launch {
try {
val url = URL("$tcorBaseURL/timetable?courseName=$courseName&week=$week")
val file = File(context.filesDir, "timetable-$courseName-$week.json")
// read data from the API
val timetableObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
// write the json object to a file
withContext(Dispatchers.IO) {
val writer = BufferedWriter(FileWriter(file))
writer.write(timetableObject.toString())
writer.close()
}
// update cache time
timetableCacheTime = System.currentTimeMillis() / 1000
PreferencesController.save(context)
} catch (ex: Exception) {
Log.e(className, "failed to get /timetable", ex)
}
}
}
}

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2018 <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,20 +22,25 @@
package org.mosad.seil0.projectlaogai.fragments package org.mosad.seil0.projectlaogai.fragments
import android.graphics.Typeface
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater 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.TextView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.afollestad.materialdialogs.MaterialDialog
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.MainActivity
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.hsoparser.* import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaMenu
import org.mosad.seil0.projectlaogai.uicomponents.LessonCardView import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.timetables
import org.mosad.seil0.projectlaogai.hsoparser.Meal
import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar
import org.mosad.seil0.projectlaogai.hsoparser.TimetableDay
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
import java.text.SimpleDateFormat
import java.util.* import java.util.*
/** /**
@ -44,19 +49,14 @@ import java.util.*
*/ */
class HomeFragment : Fragment() { class HomeFragment : Fragment() {
private lateinit var linLayoutTimeTable: LinearLayout private val className = "HomeFragment"
private var mainActivity = MainActivity() 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? {
val view: View = inflater.inflate(R.layout.fragment_home, container, false) val view: View = inflater.inflate(R.layout.fragment_home, container, false)
// init UI elements addMensaMenu()
linLayoutTimeTable = view.findViewById(R.id.linLayoutTimeTable) addTimeTable()
//setText()
addCurrentMensaMenu()
addCurrentTimeTable()
// Inflate the layout for this fragment // Inflate the layout for this fragment
return view return view
@ -65,49 +65,49 @@ class HomeFragment : Fragment() {
/** /**
* add the current mensa meal to the home screens * add the current mensa meal to the home screens
*/ */
private fun addCurrentMensaMenu() { private fun addMensaMenu() = GlobalScope.launch(Dispatchers.Default) {
doAsync {
val dayMenus: ArrayList<Meal>
val cal = Calendar.getInstance()
if (cal.get(Calendar.HOUR_OF_DAY) < 15) { var dayMeals: ArrayList<Meal>
dayMenus = MensaParser().getMensaMenuDay(mainActivity.getWeekMenu(), cal.get(Calendar.DAY_OF_WEEK)) val cal = Calendar.getInstance()
} else { val mensaCardView = DayCardView(context!!)
dayMenus = MensaParser().getMensaMenuDay(mainActivity.getWeekMenu(), cal.get(Calendar.DAY_OF_WEEK) + 1)
uiThread { withContext(Dispatchers.Main) {
// TODO Mensa closed today is showing
txtView_Menu1Heading.text = resources.getString(R.string.meal_1_tomorrow) if (isAdded) {
txtView_Menu2Heading.text = resources.getString(R.string.meal_2_tomorrow) if (cal.get(Calendar.HOUR_OF_DAY) < 15) {
dayMeals = mensaMenu.currentWeek.days[NotRetardedCalendar.getDayOfWeekIndex()].meals
mensaCardView.setDayHeading(activity!!.resources.getString(R.string.today_date, formatter.format(cal.time)))
} else {
dayMeals = mensaMenu.currentWeek.days[NotRetardedCalendar.getTomorrowWeekIndex()].meals
cal.add(Calendar.DATE, 1)
mensaCardView.setDayHeading(activity!!.resources.getString(R.string.tomorrow_date, formatter.format(cal.time)))
} }
}
uiThread { if (dayMeals.size >= 2) {
if (dayMenus.size >= 2) {
// get the index of the first meal, not a "Schneller Teller" // get the index of the first meal, not a "Schneller Teller"
loop@ for ((i, meal) in dayMenus.withIndex()) { loop@ for ((i, meal) in dayMeals.withIndex()) {
if(meal.heading.contains("Essen")) { if (meal.heading.contains("Essen")) {
for ((j, part) in dayMenus[i].parts.withIndex()) {
txtViewMenu1.append(part)
if(j < (dayMenus[i].parts.size - 2))
txtViewMenu1.append("\n")
}
for ((j, part) in dayMenus[i + 1].parts.withIndex()) { val meal1Layout = MealLinearLayout(context)
txtViewMenu2.append(part) meal1Layout.setMeal(dayMeals[i])
if(j < (dayMenus[i + 1].parts.size - 2)) mensaCardView.getLinLayoutDay().addView(meal1Layout)
txtViewMenu2.append("\n")
} val meal2Layout = MealLinearLayout(context)
meal2Layout.setMeal(dayMeals[i + 1])
meal2Layout.disableDivider()
mensaCardView.getLinLayoutDay().addView(meal2Layout)
break@loop break@loop
} }
} }
} else { } else {
txtViewMenu1.text = resources.getString(R.string.no_meal_today) mensaCardView.getLinLayoutDay().addView(getNoCard(resources.getString(R.string.mensa_closed)))
txtViewMenu2.text = resources.getString(R.string.no_meal_today)
} }
linLayout_Home.addView(mensaCardView)
} }
} }
} }
@ -115,51 +115,71 @@ class HomeFragment : Fragment() {
/** /**
* add the current timetable to the home screen * add the current timetable to the home screen
*/ */
private fun addCurrentTimeTable() { private fun addTimeTable() = GlobalScope.launch(Dispatchers.Default) {
val dayIndex = NotRetardedCalendar().getDayOfWeekIndex()
if (mainActivity.getTimeTableCurrentWeek().isNotEmpty() && dayIndex < 6) { withContext(Dispatchers.Main) {
val timeTableDay = mainActivity.getTimeTableCurrentWeek()[dayIndex] if (isAdded && timetables.isNotEmpty()) {
try {
for (i in 0..5) { val dayCardView = findNextDay(NotRetardedCalendar.getDayOfWeekIndex())
val lessonCardView = LessonCardView(context!!, null) linLayout_Home.addView(dayCardView)
} catch (ex: Exception) {
lessonCardView.getTxtViewLesson().text = resources.getString(R.string.string_new_line, timeTableDay[i].lessonSubject) Log.e(className, "could not load timetable", ex) // TODO send feedback
lessonCardView.getTxtViewLesson().append(timeTableDay[i].lessonTeacher + "\n") }
lessonCardView.getTxtViewLesson().append(timeTableDay[i].lessonRoom)
lessonCardView.getTxtViewTime().text = DataTypes().getTime()[i]
if(lessonCardView.getTxtViewLesson().text.length > 2)
linLayoutTimeTable.addView(lessonCardView)
} }
// add a card if there is no lesson today
if (linLayoutTimeTable.childCount == 0) {
// TODO we could display the next day with a lecture
val noLessonCardView = LessonCardView(context!!, null)
noLessonCardView.getTxtViewLesson().text = resources.getString(R.string.no_lesson_today)
linLayoutTimeTable.addView(noLessonCardView)
}
} else {
if (dayIndex == 6) {
// if that's the case it's sunday
val noLessonCardView = LessonCardView(context!!, null)
noLessonCardView.getTxtViewLesson().text = resources.getString(R.string.no_lesson_today)
linLayoutTimeTable.addView(noLessonCardView)
} else {
MaterialDialog(context!!)
.title(R.string.error)
.message(R.string.gen_tt_error)
.show()
// TODO log the error and send feedback
}
} }
} }
fun setMainActivity(mainActivity: MainActivity) { /**
this.mainActivity = mainActivity * find the next day with a lesson
* start at week 0, startDayIndex and search every cached week until we find a) a day with a timetable
* or b) we find no timetable and add a no lesson card
* @param startDayIndex the day index you want to start searching
* @return a DayCardView with all lessons added
*/
private fun findNextDay(startDayIndex: Int): DayCardView {
val dayCardView = DayCardView(context!!)
var dayTimetable: TimetableDay? = null
var dayIndexSearch = startDayIndex
var weekIndexSearch = 0
while (dayTimetable == null && weekIndexSearch < timetables.size) {
for (dayIndex in dayIndexSearch..5) {
dayTimetable = timetables[weekIndexSearch].timetable.days[dayIndex]
// some wired calendar magic, calculate the correct date to be shown ((timetable week - current week * 7) + (dayIndex - dayIndex of current week)
val daysToAdd = (timetables[weekIndexSearch].meta.weekNumberYear - NotRetardedCalendar.getWeekOfYear()) * 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex())
dayCardView.addTimetableDay(dayTimetable, daysToAdd)
// if there are no lessons don't show the dayCardView
if (dayCardView.getLinLayoutDay().childCount > 1)
return dayCardView
}
weekIndexSearch++
dayIndexSearch = 0
}
// there was no day found in the cached weeks, add no lesson card
dayCardView.setDayHeading(formatter.format(Calendar.getInstance().time))
dayCardView.getLinLayoutDay().addView(getNoCard(resources.getString(R.string.no_lesson_today))) // if there is no lecture at all show the no lesson card
return dayCardView
}
/**
* @param text the text to show on the card
* @return a TextView with the text and all needed parameters
*/
private fun getNoCard(text: String): TextView {
val noLesson = TextView(context)
noLesson.text = text
noLesson.textSize = 18.0F
noLesson.setTypeface(null, Typeface.BOLD)
noLesson.textAlignment = View.TEXT_ALIGNMENT_CENTER
noLesson.setPadding(7, 7, 7, 7)
return noLesson
} }
} }

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2018 <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
@ -23,18 +23,24 @@
package org.mosad.seil0.projectlaogai.fragments package org.mosad.seil0.projectlaogai.fragments
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater 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 androidx.fragment.app.Fragment
import org.jetbrains.anko.doAsync import kotlinx.android.synthetic.main.fragment_mensa.*
import org.jetbrains.anko.uiThread import kotlinx.coroutines.Dispatchers
import org.mosad.seil0.projectlaogai.MainActivity 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.uicomponents.MensaDayCardView import org.mosad.seil0.projectlaogai.controller.CacheController
import org.mosad.seil0.projectlaogai.uicomponents.MenuCardView import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaMenu
import java.util.* import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cShowBuffet
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.hsoparser.MensaWeek
import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
/** /**
* The mensa controller class * The mensa controller class
@ -42,78 +48,98 @@ import java.util.*
*/ */
class MensaFragment : Fragment() { class MensaFragment : Fragment() {
private lateinit var linLayoutMensaFragment: LinearLayout
private var mainActivity = MainActivity()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(R.layout.fragment_mensa, container, false) val view: View = inflater.inflate(R.layout.fragment_mensa, container, false)
linLayoutMensaFragment = view.findViewById(R.id.linLayout_MensaFragment) // init actions
refreshAction()
addCurrentWeek() GlobalScope.launch(Dispatchers.Default) {
val dayCurrent = if(NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
addWeek(mensaMenu.currentWeek, dayCurrent).join()
// add the next week
addWeek(mensaMenu.nextWeek, 0)
}
// TODO should we show a info if there is no more food this & next week?
return view return view
} }
private fun addCurrentWeek() { /**
* add all menus from dayStart to Friday for a given week
* @param menusWeek menu of type MensaWeek you want to add
* @param dayStart the first day of the week to add
*/
private fun addWeek(menusWeek: MensaWeek, dayStart: Int) = GlobalScope.launch(Dispatchers.Default) {
doAsync { withContext(Dispatchers.Main) {
uiThread { // only add the days dayStart to Fri since the mensa is closed on Sat/Sun
for (dayIndex in dayStart..4) {
var helpMeal = MealLinearLayout(context)
val dayCardView = DayCardView(context!!)
for(day in Calendar.getInstance().get(Calendar.DAY_OF_WEEK)..7) { if(menusWeek.days[dayIndex].meals.isNotEmpty())
dayCardView.setDayHeading(menusWeek.days[dayIndex].meals[0].day)
val strDay: String = when(day) { for (meal in menusWeek.days[dayIndex].meals) {
Calendar.MONDAY -> "Mon" val mealLayout = MealLinearLayout(context)
Calendar.TUESDAY -> "Die" mealLayout.setMeal(meal)
Calendar.WEDNESDAY -> "Mit"
Calendar.THURSDAY -> "Don" if(meal.heading != "Buffet" || cShowBuffet) {
Calendar.FRIDAY -> "Fre" dayCardView.getLinLayoutDay().addView(mealLayout)
Calendar.SATURDAY -> "Sam" helpMeal = mealLayout
else -> "TODAY" // the app will likely crash here
} }
val cardViewMensaDay = MensaDayCardView(context!!, null)
var add = false
for (meal in mainActivity.getWeekMenu()) {
//println("Day: " + meal.day)
if (meal.day.contains(strDay)) {
val menuViewMenu = MenuCardView(context!!, null)
menuViewMenu.setMenuHeading(meal.heading)
for ((i, part) in meal.parts.withIndex()) {
menuViewMenu.getTxtViewMenu().append(part)
if(i < (meal.parts.size - 2))
menuViewMenu.getTxtViewMenu().append("\n")
}
cardViewMensaDay.setDayHeading(meal.day) //TODO move this out of the first for loop, performance!!
cardViewMensaDay.getLinLayoutMensaDay().addView(menuViewMenu)
add = true
}
}
if(add)
linLayoutMensaFragment.addView(cardViewMensaDay)
} }
// add a card if there are no more meals in this week helpMeal.disableDivider()
if(linLayoutMensaFragment.childCount == 0) {
val cardViewNoMoreFood = MensaDayCardView(context!!, null) if(dayCardView.getLinLayoutDay().childCount > 1)
cardViewNoMoreFood.setDayHeading(resources.getString(R.string.no_more_food)) linLayout_Mensa.addView(dayCardView)
linLayoutMensaFragment.addView(cardViewNoMoreFood) }
}
}
}
/**
* initialize the refresh action
*/
private fun refreshAction() = GlobalScope.launch(Dispatchers.Default) {
withContext(Dispatchers.Main) {
// set the refresh listener
refreshLayout_Mensa.setOnRefreshListener {
updateMensaScreen()
} }
} }
} }
fun setMainActivity(mainActivity: MainActivity) { /**
this.mainActivity = mainActivity * refresh the mensa cache and update the mensa screen
*/
private fun updateMensaScreen() = GlobalScope.launch(Dispatchers.Default) {
// update the cache
TCoRAPIController.getMensa(context!!).join() // blocking since we want the new data
CacheController.readMensa(context!!)
withContext(Dispatchers.Main) {
// remove all menus from the layout
linLayout_Mensa.removeAllViews()
// add the refreshed menus
val dayCurrent = if (NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
addWeek(mensaMenu.currentWeek, dayCurrent).join()
// add the next week
addWeek(mensaMenu.nextWeek, 0)
refreshLayout_Mensa.isRefreshing = false
}
} }
} }

View File

@ -1,3 +1,25 @@
/**
* 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 package org.mosad.seil0.projectlaogai.fragments
import android.os.Bundle import android.os.Bundle
@ -5,8 +27,10 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import kotlinx.android.synthetic.main.fragment_moodle.* import android.webkit.WebSettings
import android.webkit.WebView
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import android.webkit.WebViewClient
/** /**
* The moodle screen controller class * The moodle screen controller class
@ -14,18 +38,21 @@ import org.mosad.seil0.projectlaogai.R
*/ */
class MoodleFragment : Fragment() { class MoodleFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { private lateinit var webView: WebView
val view: View = inflater.inflate(R.layout.fragment_settings, container, false) private lateinit var webSettings: WebSettings
//webView.loadUrl("www.google.de") override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(R.layout.fragment_moodle, container, false)
webView = view.findViewById(R.id.webView)
webView.loadUrl("https://elearning.hs-offenburg.de/moodle/")
webSettings = webView.settings
//webSettings.setJavaScriptEnabled(true) // Enable Javascript
webView.webViewClient = WebViewClient() // Force links and redirects to open in the WebView instead of in a browser
return view return view
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//webView.loadUrl("www.google.de")
}
} }

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2018 <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,24 +22,36 @@
package org.mosad.seil0.projectlaogai.fragments package org.mosad.seil0.projectlaogai.fragments
import android.graphics.Color import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import android.util.TypedValue
import android.view.LayoutInflater 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 androidx.fragment.app.Fragment
import com.afollestad.aesthetic.Aesthetic
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
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.listItemsSingleChoice
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.MainActivity
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.hsoparser.CourseTTLink import org.mosad.seil0.projectlaogai.controller.CacheController
import java.util.ArrayList import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.coursesList
import org.mosad.seil0.projectlaogai.controller.PreferencesController
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorAccent
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorPrimary
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cShowBuffet
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.hsoparser.DataTypes
import java.util.*
/** /**
* The settings controller class * The settings controller class
@ -49,12 +61,12 @@ 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 linLayoutInfo: LinearLayout private lateinit var linLayoutAbout: LinearLayout
private lateinit var linLayoutMainColor: LinearLayout private lateinit var linLayoutLicence: LinearLayout
private lateinit var viewPrimaryColor: View private lateinit var linLayoutTheme: LinearLayout
private lateinit var courseTTLinkList: ArrayList<CourseTTLink> private lateinit var linLayoutPrimaryColor: LinearLayout
private var mainActivity = MainActivity() private lateinit var linLayoutAccentColor: LinearLayout
private lateinit var switchBuffet: Switch
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@ -62,9 +74,12 @@ class SettingsFragment : Fragment() {
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)
linLayoutInfo = view.findViewById(R.id.linLayout_Info) linLayoutAbout = view.findViewById(R.id.linLayout_About)
linLayoutMainColor = view.findViewById(R.id.linLayout_MainColor) linLayoutLicence = view.findViewById(R.id.linLayout_Licence)
viewPrimaryColor = view.findViewById(R.id.view_PrimaryColor) linLayoutTheme = view.findViewById(R.id.linLayout_Theme)
linLayoutPrimaryColor = view.findViewById(R.id.linLayout_PrimaryColor)
linLayoutAccentColor = view.findViewById(R.id.linLayout_AccentColor)
switchBuffet = view.findViewById(R.id.switch_buffet)
initActions() initActions()
@ -75,7 +90,28 @@ class SettingsFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
txtView_Course.text = mainActivity.getCourse().course // initialize the settings gui
txtView_Course.text = cCourse.courseName
txtView_AboutDesc.text = resources.getString(R.string.about_version, BuildConfig.VERSION_NAME, getString(R.string.build_time))
switch_buffet.isChecked = cShowBuffet // init switch
val outValue = TypedValue()
activity!!.theme.resolveAttribute(R.attr.themeName, outValue, true)
when(outValue.string) {
"light" -> {
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryLight, activity!!.theme))
txtView_SelectedTheme.text = resources.getString(R.string.themeLight)
}
"dark" -> {
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryDark, activity!!.theme))
txtView_SelectedTheme.text = resources.getString(R.string.themeDark)
}
"black" -> {
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryDark, activity!!.theme))
txtView_SelectedTheme.text = resources.getString(R.string.themeBlack)
}
}
} }
/** /**
@ -86,59 +122,148 @@ class SettingsFragment : Fragment() {
// open a new dialog // open a new dialog
} }
linLayoutCourse.setOnClickListener { linLayoutUser.setOnLongClickListener {
// open a new dialog PreferencesController.oGiants = true // enable easter egg
val courseList = ArrayList<String>() return@setOnLongClickListener true
}
courseTTLinkList = mainActivity.getCourseTTLinkList() linLayoutCourse.setOnClickListener {
courseTTLinkList.forEach { (_, course) -> selectCourse(context!!)
courseList.add(course) }
linLayoutAbout.setOnClickListener {
// open a new info dialog
MaterialDialog(context!!)
.title(R.string.about_dialog_heading)
.message(R.string.about_dialog_text)
.show()
}
linLayoutLicence.setOnClickListener {
// do the theme magic, as the lib's theme support is broken
val outValue = TypedValue()
context!!.theme.resolveAttribute(R.attr.themeName, outValue, true)
val dialogCss = when (outValue.string) {
"light" -> R.string.license_dialog_style_light
else -> R.string.license_dialog_style_dark
} }
MaterialDialog(context!!).listItems(items = courseList){ _, index, text -> val themeId = when (outValue.string) {
txtView_Course.text = text // update txtView "light" -> R.style.AppTheme_Light
else -> R.style.LicensesDialogTheme_Dark
}
val dialog = MaterialDialog(context!!).cancelable(false) // open a new license dialog
LicensesDialog.Builder(context!!)
.setNotices(R.raw.notices)
.setTitle(R.string.licenses)
.setIncludeOwnLicense(true)
.setThemeResourceId(themeId)
.setNoticesCssStyle(dialogCss)
.build()
.show()
}
linLayoutTheme.setOnClickListener {
val themes = listOf(
resources.getString(R.string.themeLight),
resources.getString(R.string.themeDark),
resources.getString(R.string.themeBlack)
)
MaterialDialog(context!!).show {
listItemsSingleChoice(items = themes) { _, index, _ ->
Aesthetic.config {
when (index) {
0 -> activityTheme(R.style.AppTheme_Light)
1 -> activityTheme(R.style.AppTheme_Dark)
2 -> activityTheme(R.style.AppTheme_Black)
else -> activityTheme(R.style.AppTheme_Light)
}
apply()
}
}
}
}
linLayoutPrimaryColor.setOnClickListener {
// open a new color chooser dialog
MaterialDialog(context!!)
.title(R.string.primary_color)
.colorChooser(DataTypes().primaryColors, allowCustomArgb = true, initialSelection = cColorPrimary) { _, color ->
view_PrimaryColor.setBackgroundColor(color)
Aesthetic.config {
colorPrimary(color)
colorPrimaryDark(color)
apply()
}
PreferencesController.saveColorPrimary(context!!, color)
}
.positiveButton(R.string.select)
.show()
}
linLayoutAccentColor.setOnClickListener {
// open a new color chooser dialog
MaterialDialog(context!!)
.title(R.string.accent_color)
.colorChooser(DataTypes().accentColors, allowCustomArgb = true, initialSelection = cColorAccent) { _, color ->
view_AccentColor.setBackgroundColor(color)
Aesthetic.config {
colorAccent(color)
apply()
}
PreferencesController.saveColorAccent(context!!, color)
}
.positiveButton(R.string.select)
.show()
}
switchBuffet.setOnClickListener {
PreferencesController.saveShowBuffet(context!!, switchBuffet.isChecked)
}
}
fun selectCourse(context: Context) {
val courseNameList = ArrayList<String>()
coursesList.forEach { (_, courseName) ->
courseNameList.add(courseName)
}
// open a new dialog
MaterialDialog(context)
.title(R.string.select_course)
.listItems(items = courseNameList) { _, index, _ ->
val dialog = MaterialDialog(context).cancelable(false)
.cancelOnTouchOutside(false) .cancelOnTouchOutside(false)
.customView(R.layout.dialog_loading) .customView(R.layout.dialog_loading)
dialog.show() dialog.show()
doAsync { GlobalScope.launch(Dispatchers.Default) {
mainActivity.updateCourse(courseTTLinkList[index]) PreferencesController.saveCourse(context, coursesList[index]) // save the course
uiThread { // update current & next weeks timetable
val threads = listOf(
TCoRAPIController.getTimetable(cCourse.courseName, 0, context),
TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
)
threads.joinAll() // blocking since we want the new data
CacheController.readTimetable(cCourse.courseName, 0, context)
CacheController.readTimetable(cCourse.courseName, 1, context)
withContext(Dispatchers.Main) {
dialog.dismiss() dialog.dismiss()
txtView_Course.text = cCourse.courseName // update txtView
} }
} }
} }
.show() .show()
}
linLayoutInfo.setOnClickListener {
// open a new info dialog
MaterialDialog(context!!)
.title(R.string.about)
.message(R.string.about_text)
.show()
}
linLayoutMainColor.setOnClickListener {
// open a new color chooser dialog
val colors = intArrayOf(Color.BLACK, Color.RED, Color.GREEN, Color.BLUE)
MaterialDialog(context!!)
.title(R.string.primary_color)
.colorChooser(colors, initialSelection = Color.BLACK) { _, color ->
viewPrimaryColor.setBackgroundColor(color)
}
.positiveButton(R.string.select)
.show()
}
}
fun setMainActivity(mainActivity: MainActivity) {
this.mainActivity = mainActivity
} }
} }

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2018 <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,23 +22,23 @@
package org.mosad.seil0.projectlaogai.fragments package org.mosad.seil0.projectlaogai.fragments
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater 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.ScrollView
import org.jetbrains.anko.doAsync import androidx.fragment.app.Fragment
import org.jetbrains.anko.uiThread import com.afollestad.materialdialogs.MaterialDialog
import org.mosad.seil0.projectlaogai.MainActivity import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.android.synthetic.main.fragment_timetable.*
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.hsoparser.DataTypes import org.mosad.seil0.projectlaogai.controller.CacheController
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.timetables
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar
import org.mosad.seil0.projectlaogai.uicomponents.LessonCardView import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.MensaDayCardView
import java.text.SimpleDateFormat
import java.util.*
/** /**
* The timetable controller class * The timetable controller class
@ -46,95 +46,117 @@ import java.util.*
*/ */
class TimeTableFragment : Fragment() { class TimeTableFragment : Fragment() {
private lateinit var linLayoutTTFragment: LinearLayout private lateinit var scrollViewTimetable: ScrollView
private var mainActivity = MainActivity() private lateinit var faBtnAddLesson: FloatingActionButton
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(R.layout.fragment_time_table, container, false) val view: View = inflater.inflate(R.layout.fragment_timetable, container, false)
scrollViewTimetable = view.findViewById(R.id.scrollView_Timetable)
faBtnAddLesson = view.findViewById(R.id.faBtnAddLesson)
linLayoutTTFragment = view.findViewById(R.id.linLayout_TTFragment) // init actions
initActions()
if (mainActivity.getTimeTableCurrentWeek().isNotEmpty()) { if (timetables[0].timetable.days.isNotEmpty() && timetables[1].timetable.days.isNotEmpty()) {
addCurrentWeek() addInitWeeks()
} else { } else {
// TODO show card with error msg MaterialDialog(context!!)
.title(R.string.error)
.message(R.string.timetable_error)
.show()
} }
return view return view
} }
/** /**
* add the remaining days of the current week to the timetable screen * initialize the actions
*/ */
private fun addCurrentWeek() { private fun initActions() = GlobalScope.launch(Dispatchers.Default) {
val dayIndex = NotRetardedCalendar().getDayOfWeekIndex()
val formatter = SimpleDateFormat("E dd.MM", Locale.GERMANY) // TODO change to android call when min api is 24
val calendar = Calendar.getInstance()
doAsync { withContext(Dispatchers.Main) {
refreshLayout_Timetable.setOnRefreshListener {
updateTimetableScreen()
}
uiThread { faBtnAddLesson.setOnClickListener {
MaterialDialog(context!!)
.title(text = resources.getString(R.string.add_lesson))
.message(text = "wähle einen Studiengang aus:\n\nWähle eine Vorlesung aus: \n\n Diese Funktion ist noch nicht verfügbar")
.show()
}
// add current weeks days // hide the btnCardValue if the user is scrolling down
for(day in dayIndex..5) { scrollViewTimetable.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
val cardViewTimeTableDay = MensaDayCardView(context!!, null) if (scrollY > oldScrollY) {
cardViewTimeTableDay.setDayHeading(formatter.format(calendar.time)) faBtnAddLesson.hide()
} else {
// for each lessen of the day faBtnAddLesson.show()
for((i, lesson) in mainActivity.getTimeTableCurrentWeek()[day].withIndex()) {
val lessonCardView = LessonCardView(context!!, null)
lessonCardView.setBackgroundColor(Color.TRANSPARENT)
lessonCardView.getTxtViewLesson().text = resources.getString(R.string.string_new_line, lesson.lessonSubject)
lessonCardView.getTxtViewLesson().append(lesson.lessonTeacher + "\n")
lessonCardView.getTxtViewLesson().append(lesson.lessonRoom)
lessonCardView.getTxtViewTime().text = DataTypes().getTime()[i]
if(lessonCardView.getTxtViewLesson().text.length > 2)
cardViewTimeTableDay.getLinLayoutMensaDay().addView(lessonCardView)
}
calendar.add(Calendar.DATE,1)
linLayoutTTFragment.addView(cardViewTimeTableDay)
} }
// add next weeks days, max number = dayIndex, if timetable was loaded
if (mainActivity.getTimeTableNextWeek().isNotEmpty()) {
calendar.add(Calendar.DATE,1) // before this we are at a sunday (no lecture on sundays!)
for(day in 0..(dayIndex - 1)) {
val cardViewTimeTableDay = MensaDayCardView(context!!, null)
cardViewTimeTableDay.setDayHeading(formatter.format(calendar.time))
// for each lessen of the day
for((i, lesson) in mainActivity.getTimeTableNextWeek()[day].withIndex()) {
val lessonCardView = LessonCardView(context!!, null)
lessonCardView.setBackgroundColor(Color.TRANSPARENT)
lessonCardView.getTxtViewLesson().text = resources.getString(R.string.string_new_line, lesson.lessonSubject)
lessonCardView.getTxtViewLesson().append(lesson.lessonTeacher + "\n")
lessonCardView.getTxtViewLesson().append(lesson.lessonRoom)
lessonCardView.getTxtViewTime().text = DataTypes().getTime()[i]
if(lessonCardView.getTxtViewLesson().text.length > 2)
cardViewTimeTableDay.getLinLayoutMensaDay().addView(lessonCardView)
}
calendar.add(Calendar.DATE,1)
linLayoutTTFragment.addView(cardViewTimeTableDay)
}
}
// TODO if there is no lesson at one day , show a no lesson card
} }
} }
} }
fun setMainActivity(mainActivity: MainActivity) { /**
this.mainActivity = mainActivity * add the current and next weeks lessons
*/
private fun addInitWeeks() = GlobalScope.launch(Dispatchers.Default) {
val currentDayIndex = NotRetardedCalendar.getDayOfWeekIndex()
addWeek(currentDayIndex, 5, 0).join() // add current week
addWeek(0, currentDayIndex - 1, 1) // add next week
}
private fun addWeek(startDayIndex: Int, dayEndIndex: Int, weekIndex: Int) = GlobalScope.launch(Dispatchers.Default) {
val timetable = timetables[weekIndex].timetable
val timetableMeta = timetables[weekIndex].meta
withContext(Dispatchers.Main) {
for (dayIndex in startDayIndex..dayEndIndex) {
val dayCardView = DayCardView(context!!)
// some wired calendar magic, calculate the correct date to be shown ((timetable week - current week * 7) + (dayIndex - dayIndex of current week)
val daysToAdd =(timetableMeta.weekNumberYear - NotRetardedCalendar.getWeekOfYear()) * 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex())
dayCardView.addTimetableDay(timetable.days[dayIndex], daysToAdd)
// if there are no lessons don't show the dayCardView
if (dayCardView.getLinLayoutDay().childCount > 1)
linLayout_Timetable.addView(dayCardView)
}
}
}
private fun updateTimetableScreen() = GlobalScope.launch(Dispatchers.Default) {
// update the cache
val threads = listOf(
TCoRAPIController.getTimetable(cCourse.courseName, 0, context!!),
TCoRAPIController.getTimetable(cCourse.courseName, 1, context!!)
)
threads.joinAll() // blocking since we want the new data
CacheController.readTimetable(cCourse.courseName, 0, context!!)
CacheController.readTimetable(cCourse.courseName, 1, context!!)
withContext(Dispatchers.Main) {
// remove all menus from the layout
linLayout_Timetable.removeAllViews()
// add the refreshed timetables
val dayIndex = NotRetardedCalendar.getDayOfWeekIndex()
// add current week
addWeek(dayIndex, 5, 0).join()
// add next week
addWeek(0, dayIndex - 1, 1)
refreshLayout_Timetable.isRefreshing = false
}
} }
} }

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2018 <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,10 +22,55 @@
package org.mosad.seil0.projectlaogai.hsoparser package org.mosad.seil0.projectlaogai.hsoparser
import android.graphics.Color
import java.util.* import java.util.*
import kotlin.collections.ArrayList
class DataTypes { class DataTypes {
val times = arrayOf("8.00 - 9.30", "9.45 - 11.15" ,"11.35 - 13.05", "14.00 -15.30", "15.45 - 17.15", "17.30 - 19.00") val times =
arrayOf("8.00 - 9.30", "9.45 - 11.15", "11.35 - 13.05", "14.00 -15.30", "15.45 - 17.15", "17.30 - 19.00")
val primaryColors = intArrayOf(
Color.parseColor("#E53935"),
Color.parseColor("#E91E63"),
Color.parseColor("#9C27B0"),
Color.parseColor("#673AB7"),
Color.parseColor("#3F51B5"),
Color.parseColor("#2196F3"),
Color.parseColor("#03A9F4"),
Color.parseColor("#00BCD4"),
Color.parseColor("#009688"),
Color.parseColor("#4CAF50"),
Color.parseColor("#8BC34A"),
Color.parseColor("#CDDC39"),
Color.parseColor("#FDD835"),
Color.parseColor("#FFB300"),
Color.parseColor("#FB8C00"),
Color.parseColor("#FF5722"),
Color.parseColor("#795548"),
Color.parseColor("#9E9E9E"),
Color.parseColor("#607B8B"),
Color.parseColor("#000000")
)
val accentColors = intArrayOf(
Color.parseColor("#FF1744"),
Color.parseColor("#F50057"),
Color.parseColor("#D500F9"),
Color.parseColor("#3F51B5"),
Color.parseColor("#3D5AFE"),
Color.parseColor("#2979FF"),
Color.parseColor("#00B0FF"),
Color.parseColor("#00E5FF"),
Color.parseColor("#1DE9B6"),
Color.parseColor("#00E676"),
Color.parseColor("#C6FF00"),
Color.parseColor("#FFD600"),
Color.parseColor("#FFC400"),
Color.parseColor("#FF9100"),
Color.parseColor("#FF3D00"),
Color.parseColor("#000000")
)
init { init {
// do something // do something
@ -38,26 +83,70 @@ class DataTypes {
} }
class NotRetardedCalendar { class NotRetardedCalendar {
private val calendar = Calendar.getInstance()!! companion object {
private val calendar = Calendar.getInstance()
fun getDayOfWeekIndex(): Int { fun getDayOfWeekIndex(): Int {
return when(calendar.get(Calendar.DAY_OF_WEEK)) { return when (calendar.get(Calendar.DAY_OF_WEEK)) {
Calendar.MONDAY -> 0 Calendar.MONDAY -> 0
Calendar.TUESDAY -> 1 Calendar.TUESDAY -> 1
Calendar.WEDNESDAY -> 2 Calendar.WEDNESDAY -> 2
Calendar.THURSDAY -> 3 Calendar.THURSDAY -> 3
Calendar.FRIDAY -> 4 Calendar.FRIDAY -> 4
Calendar.SATURDAY -> 5 Calendar.SATURDAY -> 5
Calendar.SUNDAY -> 6 Calendar.SUNDAY -> 6
else -> 7 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 class Lesson(val lessonSubject: String, val lessonTeacher: String, val lessonRoom:String, val lessonRemark: String) // data classes for the course part
data class Course(val courseLink: String, val courseName: String)
data class CourseTTLink(val courseTTLink: String, val course: String) // 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) data class Meals(val meals: ArrayList<Meal>)
data class MensaWeek(val days: Array<Meals> = Array(7) { Meals(ArrayList()) })
data class MensaMeta(val updateTime: Long = 0, val mensaName: String = "")
data class MensaMenu(val meta: MensaMeta = MensaMeta(), val currentWeek: MensaWeek = MensaWeek(), val nextWeek: MensaWeek = MensaWeek())
// data classes for the timetable part
data class Lesson(
val lessonSubject: String,
val lessonTeacher: String,
val lessonRoom: String,
val lessonRemark: String
)
data class TimetableDay(val timeslots: Array<ArrayList<Lesson>> = Array(6) { ArrayList<Lesson>() })
data class TimetableWeek(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 TimetableCourseWeek(val meta: TimetableCourseMeta = TimetableCourseMeta(), var timetable: TimetableWeek = TimetableWeek())

View File

@ -1,80 +0,0 @@
/**
* ProjectLaogai
*
* Copyright 2018 <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.hsoparser
import org.jsoup.Jsoup
import java.util.*
class MensaParser {
private var mealList = ArrayList<Meal>()
init {
// do something
}
/**
* returns the mensa menu for the whole week
*/
fun getMensaMenu(): ArrayList<Meal> {
val menuHTML = Jsoup.connect("https://www.swfr.de/de/essen-trinken/speiseplaene/mensa-offenburg/").get()
menuHTML.select("#speiseplan-tabs").select("div.tab-content").select("div.menu-tagesplan").forEachIndexed { _, element ->
val day = element.select("h3").text()
for (i in 0 .. (element.select("div.row h4").size - 1)) {
try {
val heading = element.select("div.row h4")[i].text()
val parts = ArrayList<String>(element.select("div.row").select("div.menu-info")[i].html().substringBefore("<span").replace("<br>", " ").split("\n"))
val additives = element.select("div.row").select("div.menu-info")[i].select("span.show-with-allergenes").text()
mealList.add(Meal(day, heading, parts, additives))
} catch (e: Exception) {
// catch
}
}
}
return mealList
}
/**
* return the mensa menu of a given day (Mon - Sat)
*/
fun getMensaMenuDay(mealList: ArrayList<Meal>, day: Int): ArrayList<Meal> {
val dayMenus = ArrayList<Meal>()
val strDay: String = when (day) {
Calendar.MONDAY -> "Mon"
Calendar.TUESDAY -> "Die"
Calendar.WEDNESDAY -> "Mit"
Calendar.THURSDAY -> "Don"
Calendar.FRIDAY -> "Fre"
Calendar.SATURDAY -> "Sam"
else -> "TODAY"
}
for (meal in mealList) {
if (meal.day.contains(strDay))
dayMenus.add(meal)
}
return dayMenus
}
}

View File

@ -1,94 +0,0 @@
/**
* ProjectLaogai
*
* Copyright 2018 <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.hsoparser
import org.jsoup.Jsoup
class TimeTableParser {
private val days = arrayOf("Monday", "Tuesday" ,"Wednesday", "Thursday", "Friday", "Saturday")
private var courseTTLinkList = ArrayList<CourseTTLink>()
private var timeTableWeek = arrayOf<Array<Lesson>>()
init {
// create the timetable array
for (i in 0..5) {
var timeTableDay = arrayOf<Lesson>()
for (j in 0..5) {
timeTableDay += Lesson("", "","","")
}
timeTableWeek += timeTableDay
}
}
/**
* get the timetable from the given url
* the timetable is organised per row not per column;
* Mon 1, Tue 1, Wed 1, Thur 1, Fri 1, Sat 1, Mon 2 and so on
*/
fun getTimeTable(courseTTURL: String): Array<Array<Lesson>> {
val scheduleHTML = Jsoup.connect(courseTTURL).get()
//val week = scheduleHTML.select("h1.timetable-caption").text()
//println("$week successful!\n")
scheduleHTML.select("table.timetable").select("td.lastcol").forEachIndexed { index, element ->
timeTableWeek[index % 6][index / 6] = Lesson(element.select("div.lesson-subject").text(), element.select("div.lesson-teacher").text(), element.select("div.lesson-room").text(), element.select("div.lesson-remark").text())
}
return timeTableWeek
}
/**
* parse all courses from the courses site at https://www.hs-offenburg.de/studium/vorlesungsplaene/
*/
fun getCourseTTLinks(): ArrayList<CourseTTLink> {
val courseHTML = Jsoup.connect("https://www.hs-offenburg.de/studium/vorlesungsplaene/").get()
courseHTML.select("ul.index-group").select("li.Class").select("a[href]").forEachIndexed { _, element ->
courseTTLinkList.add(CourseTTLink(element.attr("href"),element.text()))
}
return courseTTLinkList
}
@Suppress("unused")
fun printTimeTableWeek (timeTableWeek: Array<Array<Lesson>>) {
for (j in 0..5) print(days[j].padEnd(25 ,' ') + " | ")
println()
for (j in 0..5) print("-".padEnd(26 + (j.toFloat().div(j).toInt()), '-') + "+")
println()
for (i in 0..5) {
for (j in 0..5) print(timeTableWeek[j][i].lessonSubject.padEnd(25 ,' ').substring(0,25) + " | ")
println()
for (j in 0..5) print(timeTableWeek[j][i].lessonTeacher.padEnd(25 ,' ').substring(0,25) + " | ")
println()
for (j in 0..5) print(timeTableWeek[j][i].lessonRoom.padEnd(25 ,' ').substring(0,25) + " | ")
println()
for (j in 0..5) print("-".padEnd(26 + (j.toFloat().div(j).toInt()), '-') + "+")
println()
}
println()
}
}

View File

@ -0,0 +1,89 @@
/**
* 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.Color
import android.widget.LinearLayout
import androidx.cardview.widget.CardView
import kotlinx.android.synthetic.main.cardview_day.view.*
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.hsoparser.DataTypes
import org.mosad.seil0.projectlaogai.hsoparser.TimetableDay
import java.text.SimpleDateFormat
import java.util.*
class DayCardView(context: Context) : CardView(context) {
private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault())
init {
inflate(context, R.layout.cardview_day,this)
// workaround to prevent a white border
this.setBackgroundColor(Color.TRANSPARENT)
}
fun getLinLayoutDay() : LinearLayout {
return linLayout_Day
}
fun setDayHeading(heading: String) {
txtView_DayHeading.text = heading
}
/**
* add the lessons of one day to the dayCardView
* @param timetable a timetable containing the day (and it's lessons) to be added
*/
fun addTimetableDay(timetable: TimetableDay, daysToAdd: Int) {
var lastLesson = LessonLinearLayout(context)
// set the heading
val cal = Calendar.getInstance()
cal.add(Calendar.DATE, daysToAdd)
txtView_DayHeading.text = formatter.format(cal.time)
// for every timeslot of that timetable
for ((tsIndex, timeslot) in timetable.timeslots.withIndex()) {
for (lesson in timeslot) {
if (lesson.lessonSubject.isNotEmpty()) {
val lessonLayout = LessonLinearLayout(context)
lessonLayout.setLesson(lesson, DataTypes().getTime()[tsIndex])
linLayout_Day.addView(lessonLayout)
if (lesson != timeslot.last()) {
lessonLayout.disableDivider()
}
lastLesson = lessonLayout
}
}
}
lastLesson.disableDivider() // disable the divider for the last lesson of the day
}
}

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2018 <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
@ -23,31 +23,28 @@
package org.mosad.seil0.projectlaogai.uicomponents package org.mosad.seil0.projectlaogai.uicomponents
import android.content.Context import android.content.Context
import android.graphics.Color import android.view.View
import android.util.AttributeSet import android.widget.LinearLayout
import android.widget.TextView import androidx.cardview.widget.CardView
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
class LessonCardView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : androidx.cardview.widget.CardView(context, attrs){ class LessonLinearLayout(context: Context?) : LinearLayout(context) {
private var txtViewLesson: TextView
private var txtViewTime: TextView
init { init {
inflate(context, R.layout.lesson_cardview,this) CardView.inflate(context, R.layout.linearlayout_lesson, this)
txtViewLesson = findViewById(R.id.txtView_Lesson)
txtViewTime = findViewById(R.id.txtView_Time)
// workaround to prevent a white border
this.setBackgroundColor(Color.TRANSPARENT)
} }
fun getTxtViewLesson(): TextView { fun setLesson(lesson: Lesson, time: String) {
return txtViewLesson txtView_lessonTime.text = time
txtView_lessonSubject.text = lesson.lessonSubject
txtView_lessonTeacher.text = lesson.lessonTeacher
txtView_lessonRoom.text = lesson.lessonRoom
} }
fun getTxtViewTime(): TextView { fun disableDivider() {
return txtViewTime divider_lesson.visibility = View.GONE
} }
} }

View File

@ -1,7 +1,7 @@
/** /**
* ProjectLaogai * ProjectLaogai
* *
* Copyright 2018 <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
@ -23,32 +23,31 @@
package org.mosad.seil0.projectlaogai.uicomponents package org.mosad.seil0.projectlaogai.uicomponents
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import androidx.cardview.widget.CardView
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
class MensaDayCardView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : androidx.cardview.widget.CardView(context, attrs){ class MealLinearLayout(context: Context?): LinearLayout(context) {
private var linLayoutMensaDay: LinearLayout
private var txtViewDayHeading: TextView
init { init {
inflate(context, R.layout.mensaday_cardview,this) CardView.inflate(context, R.layout.linearlayout_meal, this)
linLayoutMensaDay = findViewById(R.id.linLayout_MensaDay)
txtViewDayHeading = findViewById(R.id.txtView_DayHeading)
// workaround to prevent a white border
//this.setBackgroundColor(Color.TRANSPARENT)
} }
fun getLinLayoutMensaDay(): LinearLayout { fun setMeal(meal: Meal) {
return linLayoutMensaDay txtView_MealHeading.text = meal.heading
meal.parts.forEachIndexed { partIndex, part ->
txtView_Meal.append(part)
if(partIndex < (meal.parts.size - 1))
txtView_Meal.append("\n")
}
} }
fun setDayHeading(heading: String) { fun disableDivider() {
txtViewDayHeading.text = heading divider_meal.visibility = View.GONE
} }
} }

View File

@ -1,31 +0,0 @@
package org.mosad.seil0.projectlaogai.uicomponents
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.widget.TextView
import org.mosad.seil0.projectlaogai.R
class MenuCardView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : androidx.cardview.widget.CardView(context, attrs){
private var txtViewMenuHeading: TextView
private var txtViewMenu: TextView
init {
inflate(context, R.layout.menu_cardview,this)
txtViewMenuHeading = findViewById(R.id.txtView_MenuHeading)
txtViewMenu = findViewById(R.id.txtView_Menu)
// workaround to prevent a white border
this.setBackgroundColor(Color.TRANSPARENT)
}
fun setMenuHeading(heading: String) {
txtViewMenuHeading.text = heading
}
fun getTxtViewMenu(): TextView {
return txtViewMenu
}
}

View File

@ -1,12 +1,9 @@
<?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 <item android:drawable="@color/colorPrimary"/>
android:drawable="@color/colorPrimary"/>
<!--TODO when minAPI is 23 use this--> <item android:gravity="center" android:width="144dp" android:height="144dp">
<!--<item android:gravity="center" android:width="144dp" android:height="144dp">-->
<item android:gravity="center">
<bitmap <bitmap
android:gravity="fill_horizontal|fill_vertical" android:gravity="fill_horizontal|fill_vertical"
android:src="@mipmap/ic_laogai_icon_splash"/> android:src="@mipmap/ic_laogai_icon_splash"/>

View File

@ -2,8 +2,8 @@
android:shape="rectangle"> android:shape="rectangle">
<gradient <gradient
android:angle="135" android:angle="135"
android:centerColor="@color/colorPrimary" android:centerColor="?colorPrimary"
android:endColor="@color/colorPrimaryDark" android:endColor="?colorPrimaryDark"
android:startColor="@color/colorPrimary" android:startColor="?colorPrimary"
android:type="linear"/> android:type="linear"/>
</shape> </shape>

View File

@ -1,109 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.HomeFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="5dp"
android:clickable="false"
android:maxHeight="125dp"
app:cardUseCompatPadding="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" android:elevation="5dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_marginBottom="2dp">
<TextView
android:text="@string/meal_1"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_Menu1Heading"
android:textStyle="bold" android:textAlignment="center" android:textSize="16sp"
android:typeface="sans" android:fontFamily="sans-serif" android:paddingBottom="5dp"/>
<TextView
android:id="@+id/txtViewMenu1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:textAlignment="center"
android:textSize="16sp"
android:textStyle="bold"
android:typeface="sans"
android:textIsSelectable="true"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/cardView2"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:cardUseCompatPadding="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cardView" android:elevation="5dp"
android:layout_marginEnd="5dp" android:layout_marginStart="5dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_marginBottom="2dp">
<TextView
android:text="@string/meal_2"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_Menu2Heading"
android:textAlignment="center" android:textStyle="bold" android:textSize="16sp"
android:typeface="sans" android:fontFamily="sans-serif" android:paddingBottom="5dp"/>
<TextView
android:id="@+id/txtViewMenu2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:textAlignment="center"
android:textSize="16sp"
android:textStyle="bold"
android:typeface="sans"
android:textIsSelectable="true"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<ScrollView
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cardView2" android:id="@+id/scrollViewTimeTable"
android:background="@color/md_divider_dark_theme" android:paddingTop="6dp"
android:layout_marginTop="5dp">
<LinearLayout
android:id="@+id/linLayoutTimeTable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cardView2">
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.MensaFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/linLayout_MensaFragment"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
>
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -1,147 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.SettingsFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="9dp" android:id="@+id/cardView_Info" app:cardElevation="5dp"
app:cardUseCompatPadding="true">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text="@string/info"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_Info"
android:typeface="sans"
android:textSize="18sp" android:textStyle="bold" android:fontFamily="sans-serif"
android:layout_margin="7dp" android:textColor="@color/colorPrimary"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:padding="7dp" 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"
android:textColor="@android:color/primary_text_light"/>
<TextView
android:text="@string/user"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_UserDesc"/>
</LinearLayout>
<View
android:id="@+id/divider2"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_margin="7dp"
android:id="@+id/linLayout_Course">
<TextView
android:text="@string/sample_course"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_Course"
android:textSize="16sp" android:textColor="@android:color/primary_text_light"
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>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_margin="7dp"
android:id="@+id/linLayout_Info">
<TextView
android:text="@string/about_txtView"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_About"
android:textStyle="bold" android:textSize="16sp"
android:textColor="@android:color/primary_text_light"/>
<TextView
android:text="@string/version"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_Version"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/cardView_Settings" app:cardElevation="5dp"
app:cardUseCompatPadding="true">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/linLayout_Settings">
<TextView
android:text="@string/settings"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_Settings"
android:typeface="sans"
android:textSize="18sp" android:textColor="@color/design_default_color_primary"
android:textStyle="bold" android:layout_margin="7dp" android:paddingTop="3dp"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_margin="7dp"
android:gravity="center_vertical|end"
android:clickable="true" android:id="@+id/linLayout_MainColor" android:focusable="true">
<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"
android:textColor="@android:color/primary_text_light"/>
<TextView
android:text="@string/main_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="@color/colorPrimary"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@android:color/background_light"
app:cardElevation="5dp"
app:cardUseCompatPadding="true" app:cardPreventCornerOverlap="false" app:contentPadding="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/txtView_Lesson"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/a_lesson"
android:textColor="@android:color/primary_text_light"
android:textSize="14sp"
android:textStyle="bold"
android:typeface="sans"/>
<TextView
android:id="@+id/txtView_Time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/a_time"/>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="9dp"
app:cardElevation="5dp"
app:cardBackgroundColor="@color/colorMensaDay"
app:cardUseCompatPadding="true">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/linLayout_MensaDay" android:paddingBottom="5dp">
<TextView
android:text="@string/sample_date"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_DayHeading" android:textAlignment="center"
android:textSize="20sp" android:textStyle="bold" android:typeface="sans" android:paddingBottom="5dp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="5dp"
android:clickable="false"
android:maxHeight="125dp"
app:cardUseCompatPadding="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="@string/meal_1"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_MenuHeading"
android:textStyle="bold" android:textAlignment="center" android:textSize="16sp"
android:typeface="sans" android:fontFamily="sans-serif" android:paddingBottom="5dp"/>
<TextView
android:id="@+id/txtView_Menu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:textAlignment="center"
android:textSize="16sp"
android:textStyle="bold"
android:typeface="sans"
android:textIsSelectable="true"/>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -20,7 +20,9 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="start" android:layout_gravity="start"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
android:background="?themeSecondary"
app:headerLayout="@layout/nav_header_main" app:headerLayout="@layout/nav_header_main"
app:itemTextColor="?colorAccent"
app:menu="@menu/activity_main_drawer"/> app:menu="@menu/activity_main_drawer"/>
</androidx.drawerlayout.widget.DrawerLayout> </androidx.drawerlayout.widget.DrawerLayout>

View File

@ -20,7 +20,7 @@
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary" android:background="?colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"/> app:popupTheme="@style/AppTheme.PopupOverlay"/>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardUseCompatPadding="true"
app:cardElevation="5dp"
app:cardBackgroundColor="?themeSecondary"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" android:id="@+id/linLayout_Day">
<TextView
android:text="@string/sample_date"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_DayHeading" android:textSize="18sp"
android:textAlignment="center" android:textStyle="bold" android:textColor="?colorAccent"/>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linLayout_lesson"
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:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/txtView_lessonSubject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="15sp" />
<TextView
android:id="@+id/txtView_lessonTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:text="@string/a_time"
android:textColor="@color/textSecondary" />
</LinearLayout>
<TextView
android:id="@+id/txtView_lessonTeacher"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15sp" />
<TextView
android:id="@+id/txtView_lessonRoom"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<View
android:id="@+id/divider_lesson"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="2dp"
android:background="?dividerColor" />
</LinearLayout>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linLayout_Meal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="7dp"
android:paddingTop="2dp"
android:paddingRight="7dp"
android:paddingBottom="2dp">
<TextView
android:id="@+id/txtView_MealHeading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:text="@string/meal"
android:textAlignment="center"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtView_Meal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:textSize="15sp" />
<View
android:id="@+id/divider_meal"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="3dp"
android:background="?dividerColor" />
</LinearLayout>

View File

@ -4,21 +4,22 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
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="@drawable/side_nav_bar" android:background="@color/colorPrimary"
android:paddingBottom="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin" android:paddingTop="@dimen/activity_vertical_margin"
android:theme="@style/ThemeOverlay.AppCompat.Dark" android:theme="@style/ThemeOverlay.AppCompat.Dark"
android:orientation="vertical" android:orientation="vertical"
android:gravity="bottom"> android:gravity="bottom"
android:id="@+id/nav_header_main">
<ImageView <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" android:paddingTop="@dimen/nav_header_vertical_spacing"
app:srcCompat="@mipmap/ic_laogai_icon" app:srcCompat="@mipmap/ic_laogai_icon"
android:contentDescription="@string/nav_header_desc" android:contentDescription="@string/app_name"
android:id="@+id/imageView"/> android:id="@+id/imageView"/>
<TextView <TextView
@ -26,7 +27,7 @@
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:textAppearance="@style/TextAppearance.AppCompat.Body1" android:id="@+id/txtView_nav_header_title"/>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="license_dialog_style_light" translatable="false">
body {
background-color: #ffffff;
color: #000000;
font-family: sans-serif;
overflow-wrap: break-word;
}
pre {
background-color: #eeeeee;
padding: 1em;
white-space: pre-wrap;
}
</string>
<string name="license_dialog_style_dark" translatable="false">
body {
background-color: #303030;
color: #ffffff;
font-family: sans-serif;
overflow-wrap: break-word;
}
pre {
background-color: #424242;
padding: 1em;
white-space: pre-wrap;
}
li a {
color: #21a3df;
}
</string>
</resources>

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,34 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linLayout_mensacredit"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/txtView_Heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/mensa_credit"
android:textAlignment="center"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtView_current"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:layout_marginBottom="7dp"
android:text="16,95 €"
android:textAlignment="center"
android:textSize="20sp" />
<TextView
android:id="@+id/txtView_last"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/mensa_last"
android:textAlignment="center"
android:textSize="16sp" />
</LinearLayout>

View File

@ -3,7 +3,8 @@
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.TimeTableFragment"> tools:context=".fragments.HomeFragment"
android:background="?themePrimary">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -15,9 +16,11 @@
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/linLayout_TTFragment"> android:layout_height="wrap_content"
android:id="@+id/linLayout_Home">
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout> </FrameLayout>

View File

@ -0,0 +1,28 @@
<?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.MensaFragment"
android:background="?themePrimary">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/refreshLayout_Mensa">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/scrollView_Mensa">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="false"
android:id="@+id/linLayout_Mensa"/>
</ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</FrameLayout>

View File

@ -8,6 +8,7 @@
<!-- TODO: Update blank fragment layout --> <!-- TODO: Update blank fragment layout -->
<WebView <WebView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:id="@+id/webView"/> android:layout_height="match_parent"
android:id="@+id/webView"/>
</FrameLayout> </FrameLayout>

View File

@ -0,0 +1,283 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.SettingsFragment"
android:background="?themePrimary">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="9dp"
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
android:text="@string/info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtView_Info"
android:typeface="sans"
android:textSize="18sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:layout_margin="7dp"
android:textColor="?colorAccent"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="7dp"
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>
<View
android:id="@+id/divider2"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?dividerColor"
/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:id="@+id/linLayout_Course">
<TextView
android:text="@string/sample_course"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtView_Course"
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>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?dividerColor"
/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="7dp"
android:id="@+id/linLayout_About">
<TextView
android:text="@string/about_txtView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtView_About"
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>
<View
android:id="@+id/divider6"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?dividerColor"
/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" android:id="@+id/linLayout_Licence"
android:layout_margin="7dp">
<TextView
android:text="@string/licenses"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/textView3"
android:textStyle="bold" android:textSize="16sp"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/cardView_Settings"
app:cardElevation="5dp"
app:cardUseCompatPadding="true"
app:cardBackgroundColor="?themeSecondary">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/linLayout_Settings"
>
<TextView
android:text="@string/settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtView_Settings"
android:typeface="sans"
android:textSize="18sp"
android:textColor="?colorAccent"
android:textStyle="bold"
android:paddingTop="3dp" android:layout_margin="7dp"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" android:id="@+id/linLayout_Theme"
android:layout_margin="7dp">
<TextView
android:text="@string/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_height="wrap_content"
android:gravity="center_vertical|end"
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:textStyle="bold"
android:text="@string/show_buffet"
android:layout_margin="7dp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -0,0 +1,32 @@
<?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.TimeTableFragment"
android:background="?themePrimary">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/refreshLayout_Timetable">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent" android:id="@+id/scrollView_Timetable">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="false"
android:id="@+id/linLayout_Timetable"/>
</ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:src="@drawable/icon_custom_black"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true" android:id="@+id/faBtnAddLesson" android:elevation="7dp"
android:visibility="gone" android:layout_gravity="bottom|center|end" android:layout_marginEnd="11dp"
android:layout_marginBottom="11dp" android:focusable="true"/>
</FrameLayout>

View File

@ -4,7 +4,10 @@
tools:showIn="navigation_view"> tools:showIn="navigation_view">
<group android:checkableBehavior="single"> <group android:checkableBehavior="single">
<item android:id="@+id/nav_home" android:title="@string/home" android:icon="@drawable/ic_baseline_home_24dp"/> <item
android:id="@+id/nav_home"
android:title="@string/home"
android:icon="@drawable/ic_baseline_home_24dp"/>
<item <item
android:id="@+id/nav_mensa" android:id="@+id/nav_mensa"
android:icon="@drawable/ic_local_dining_black_24dp" android:icon="@drawable/ic_local_dining_black_24dp"

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<notices>
<notice>
<name>Material Dialogs</name>
<url>https://github.com/afollestad/material-dialogs</url>
<copyright>Copyright (C) Aidan Follestad</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>Aesthetic</name>
<url>https://github.com/afollestad/aesthetic</url>
<copyright>Copyright Aidan Follestad</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>gson</name>
<url>https://github.com/google/gson</url>
<copyright>Copyright 2008 Google Inc.</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>kotlinx.coroutines</name>
<url>https://github.com/Kotlin/kotlinx.coroutines</url>
<copyright>Copyright JetBrains</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>farebot part for desfire cards</name>
<url>https://github.com/codebutler/farebot</url>
<copyright>Copyright 2011-2012, 2014, 2016 Eric Butler</copyright>
<license>GNU General Public License 3.0</license>
</notice>
<notice>
<name>Android Support Libraries</name>
<url>http://developer.android.com/tools/support-library/index.html</url>
<copyright>Copyright (C) 2016 The Android Open Source Project</copyright>
<license>Apache Software License 2.0</license>
</notice>
</notices>

View File

@ -1,28 +1,66 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="navigation_drawer_close">Navigationsleiste schließen</string>
<string name="navigation_drawer_open">Navigationsleiste öffnen</string>
<!-- nav-view -->
<string name="home">Home</string> <string name="home">Home</string>
<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="settings">Einstellungen</string> <string name="settings">Einstellungen</string>
<string name="meal_1">Essen 1</string>
<string name="meal_2">Essen 2</string> <!-- fragment_home -->
<string name="meal_1_tomorrow">Essen 1, Morgen</string> <string name="meal">Essen</string>
<string name="meal_2_tomorrow">Essen 1, Morgen</string> <string name="today_date">Heute, %1$s</string>
<string name="no_meal_today">heute keine Essensausgabe</string> <string name="tomorrow_date">Morgen, %1$s</string>
<string name="mensa_closed">keine Essensausgabe</string>
<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>
<string name="error">Fehler</string>
<string name="no_tt_error">Stundenplan konnte nicht geladen werden!</string> <!-- fragment_timetable -->
<string name="gen_tt_error">Allgemeiner Stundenplan Fehler!"</string> <string name="add_lesson">Eine Vorlesung hinzufügen</string>
<!-- fragment_settings -->
<string name="info">Info</string> <string name="info">Info</string>
<string name="user">Benutzer</string> <string name="user">Benutzer</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="about_dialog_heading">Über</string>
<string name="licenses">Lizenzen</string>
<string name="theme">Design</string>
<string name="themeLight">Hell</string>
<string name="themeDark">Dunkel</string>
<string name="themeBlack">Schwarz</string>
<string name="primary_color">Primärfarbe</string> <string name="primary_color">Primärfarbe</string>
<string name="main_color_desc">Die Primärfarbe, standard ist Schwarz</string> <string name="primary_color_desc">Zum Ändern tippen, Standard ist Schwarz.</string>
<string name="select">auswählen</string> <string name="accent_color">Akzentfarbe</string>
<string name="about">über</string> <string name="accent_color_desc">Zum Ändern tippen, Standard ist Indigo.</string>
<string name="show_buffet">Buffet immer anzeigen</string>
<!-- dialogs -->
<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="navigation_drawer_close">Navigationsleiste schließen</string> <string name="select">auswählen</string>
<string name="navigation_drawer_open">Navigationsleiste öffnen</string> <string name="close">schließen</string>
<string name="mensa_credit">Mensa-Guthaben</string>
<string name="mensa_current">aktuell: %1$s\n</string>
<string name="mensa_last">letzte Abbuchung: %1$s</string>
<!-- errors -->
<string name="error">Fehler</string>
<string name="timetable_error">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

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr format="string" name="themeName"/>
<attr format="color" name="themePrimary"/>
<attr format="color" name="themeSecondary"/>
<attr format="color" name="textPrimary"/>
<attr format="color" name="textSecondary"/>
<attr format="color" name="dividerColor"/>
</resources>

View File

@ -2,7 +2,27 @@
<resources> <resources>
<color name="colorPrimary">#000000</color> <color name="colorPrimary">#000000</color>
<color name="colorPrimaryDark">#000000</color> <color name="colorPrimaryDark">#000000</color>
<color name="colorAccent">#d81b60</color> <color name="colorAccent">#3f51b5</color>
<color name="colorMensaDay">#ebe8e9</color> <color name="ic_launcher_background">#ffffff</color>
<color name="ic_launcher_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-->
<color name="themePrimaryDark">#000000</color>
<color name="themeSecondaryDark">#303030</color>
<color name="themeTertiaryDark">#424242</color>
<color name="textPrimaryDark">#ffffff</color>
<color name="textSecondaryDark">#e0e0e0</color>
<color name="dividerDark">#232323</color>
<color name="themePrimaryLight">#ffffff</color>
<color name="themeSecondaryLight">#ffffff</color>
<color name="textPrimaryLight">#000000</color>
<color name="textSecondaryLight">#818181</color>
<color name="dividerLight">#e0e0e0</color>
</resources> </resources>

View File

@ -2,52 +2,88 @@
<string name="app_name" translatable="false">Project Laogai</string> <string name="app_name" translatable="false">Project Laogai</string>
<string name="navigation_drawer_open">Open navigation drawer</string> <string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string> <string name="navigation_drawer_close">Close navigation drawer</string>
<string name="nav_header_title" translatable="false">hso App 0.3.1</string> <string name="nav_header_title" translatable="false">Project Laogai</string>
<string name="nav_header_subtitle" translatable="false">seil0@mosad.xyz</string> <string name="nav_header_subtitle" translatable="false">seil0@mosad.xyz</string>
<string name="nav_header_desc" translatable="false">Project Laogai</string>
<!-- nav-view -->
<string name="home">Home</string> <string name="home">Home</string>
<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="settings">Settings</string> <string name="settings">Settings</string>
<string name="meal_1">Meal 1</string> <!-- fragment_home -->
<string name="meal_2">Meal 2</string> <string name="meal">Meal</string>
<string name="meal_1_tomorrow">Meal 1, tomorrow</string> <string name="today_date">Today, %1$s</string>
<string name="meal_2_tomorrow">Meal 2, tomorrow</string> <string name="tomorrow_date">Tomorrow, %1$s</string>
<string name="no_meal_today">Mensa closed today</string> <string name="mensa_closed">the Mensa is closed</string>
<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_timetable -->
<string name="error">Error</string> <string name="add_lesson">Add a lesson</string>
<string name="no_tt_error">Could not load timetable!"</string>
<string name="gen_tt_error">There was an error with the timetable!"</string>
<string name="sample_user" translatable="false">SampleUser@stud.hs-offenburg.de</string>
<string name="sample_course" translatable="false">SampleCourse 3</string>
<string name="sample_date" translatable="false">Montag, 30.02</string>
<string name="a_lesson" translatable="false">a lesson\na teacher\na room</string>
<string name="a_time" translatable="false">0.00 23.59</string>
<!-- fragment_settings -->
<string name="info">Info</string> <string name="info">Info</string>
<string name="user">User</string> <string name="user">User</string>
<string name="course_desc">Tap to change course</string> <string name="course_desc">Tap to change course</string>
<string name="primary_color">primary color</string> <string name="about_dialog_heading">About</string>
<string name="main_color_desc">The primary color, default is black</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="select">select</string>
<string name="version" translatable="false">version 0.3.1</string>
<string name="about">about</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_text" translatable="false">"This software is made by @Seil0 and is published under the terms and <string name="about_version" translatable="false">Version %1$s (%2$s)</string>
conditions of GPL 3. For further information visit \ngit.mosad.xyz/Seil0/ProjectLaogai \n\n© 2018 <string name="licenses">Licenses</string>
seil0@mosad.xyz " <string name="theme">Theme</string>
</string> <string name="themeLight">Light</string>
<string name="themeDark">Dark</string>
<string name="themeBlack">Black</string>
<string name="primary_color">Primary color</string>
<string name="primary_color_desc">Tap to change, default is black.</string>
<string name="accent_color">Accent color</string>
<string name="accent_color_desc">Tap to change, default is indigo.</string>
<string name="show_buffet">Always show buffet</string>
<!-- dialogs -->
<string name="select_course">Select your course</string>
<string name="loading_timetable">loading timetable …</string> <string name="loading_timetable">loading timetable …</string>
<string name="select">select</string>
<string name="close">close</string>
<string name="mensa_credit">Mensa credit</string>
<string name="mensa_current">current: %1$s\n</string>
<string name="mensa_last">last: %1$s</string>
<string name="string_new_line" translatable="false">%1$s\n</string> <!-- sample strings -->
<string name="sample_user" translatable="false">spinefield@stud.hs-offenburg.de</string>
<string name="sample_course" translatable="false">SampleCourse 3</string>
<string name="sample_date" translatable="false">Montag, 30.02</string>
<string name="a_time" translatable="false">0.00 23.59</string>
<!-- errors -->
<string name="error">Error</string>
<string name="timetable_error">Could not load 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 -->
<string name="preference_file_key" translatable="false">org.mosad.seil0.projectlaogai_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_colorAccent" translatable="false">org.mosad.seil0.projectlaogai.colorAccent</string>
<string name="save_key_showBuffet" translatable="false">org.mosad.seil0.projectlaogai.showBuffet</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_timetableCacheTime" translatable="false">org.mosad.seil0.projectlaogai.timetableCacheTime</string>
</resources> </resources>

View File

@ -1,11 +1,50 @@
<resources> <resources>
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>
<item name="md_color_button_text">@color/colorAccent</item>
</style>
<style name="AppTheme.Light" parent="AppTheme">
<item name="themeName">light</item>
<item name="themePrimary">@color/themePrimaryLight</item>
<item name="themeSecondary">@color/themeSecondaryLight</item>
<item name="android:textColor">@color/textPrimaryLight</item>
<item name="android:textColorPrimary">@color/textPrimaryLight</item>
<item name="textPrimary">@color/textPrimaryLight</item>
<item name="dividerColor">@color/dividerLight</item>
<item name="md_background_color">@color/themeSecondaryLight</item>
<item name="md_color_title">@color/textPrimaryLight</item>
<item name="md_color_content">@color/textPrimaryLight</item>
</style>
<style name="AppTheme.Dark" parent="AppTheme">
<item name="themeName">dark</item>
<item name="themePrimary">@color/themePrimaryDark</item>
<item name="themeSecondary">@color/themeSecondaryDark</item>
<item name="android:textColor">@color/textPrimaryDark</item>
<item name="android:textColorPrimary">@color/textPrimaryDark</item>
<item name="textPrimary">@color/textPrimaryDark</item>
<item name="dividerColor">@color/dividerDark</item>
<item name="md_background_color">@color/themeSecondaryDark</item>
<item name="md_color_title">@color/textPrimaryDark</item>
<item name="md_color_content">@color/textPrimaryDark</item>
</style>
<style name="AppTheme.Black" parent="AppTheme">
<item name="themeName">black</item>
<item name="themePrimary">@color/themePrimaryDark</item>
<item name="themeSecondary">@color/themePrimaryDark</item>
<item name="android:textColor">@color/textPrimaryDark</item>
<item name="android:textColorPrimary">@color/textPrimaryDark</item>
<item name="textPrimary">@color/textPrimaryDark</item>
<item name="dividerColor">@color/dividerDark</item>
<item name="md_background_color">@color/themeSecondaryDark</item>
<item name="md_color_title">@color/textPrimaryDark</item>
<item name="md_color_content">@color/textPrimaryDark</item>
</style> </style>
<style name="AppTheme.NoActionBar"> <style name="AppTheme.NoActionBar">
@ -19,4 +58,8 @@
<item name="android:windowBackground">@drawable/background_splash</item> <item name="android:windowBackground">@drawable/background_splash</item>
</style> </style>
<style name="LicensesDialogTheme.Dark" parent="Theme.AppCompat.Dialog">
<item name="android:windowBackground">@color/themeSecondaryDark</item>
</style>
</resources> </resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="file" path="mensa.json"/>
<exclude domain="file" path="courses.json"/>
</full-backup-content>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
</tech-list>
</resources>

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.10' ext.kotlin_version = '1.3.61'
repositories { repositories {
google() google()
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.2.1' classpath 'com.android.tools.build:gradle:3.5.3'
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
@ -19,7 +19,6 @@ allprojects {
repositories { repositories {
google() google()
jcenter() jcenter()
maven { url "https://dl.bintray.com/drummer-aidan/maven/" }
} }
} }

View File

@ -0,0 +1,8 @@
ProjectLaogai ist eine App um den Vorlesungsplan und den Mensa-Speiseplan der Hochschule Offenburg anzuzeigen.
Features:
* schaue was es diese und nächste Woche in der Mensa zu Essen gibt
* schaue dir deinen Vorlesungsplan an
* lass dir das aktuelle Guthaben der Mensakarte anzeigen
* öffne moodle direkt in der App
* viele lustige Bugs

View File

@ -0,0 +1 @@
eine App für die Hochschule Offenburg

View File

@ -0,0 +1,8 @@
This release 0.5.0 is called "artistical Apollon".
* new: it's now possible to choose a theme (light, dark or black)
* new: you can now check your current mensa card balance
* change: updated some libs, updated kotlin to 1.3.41
* change: added a license dialog for all used libraries
* fix: the mensa should now show the correct meals on sunday/monday
* fix: the timetable should now show the correct on the sunday/monday change

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,8 @@
ProjectLaogai is an app to access the timetable and the mensa menu of Hochschule Offenburg.
Features:
* check out the mensa menu of this and next week
* access your timetable
* check the current balance of your mensa card
* open moodle
* probably some funny bugs

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

View File

@ -0,0 +1 @@
an app for the Hochschule Offenburg

View File

@ -0,0 +1 @@
ProjectLaogai

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-4.10.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

22
gradlew vendored
View File

@ -1,5 +1,21 @@
#!/usr/bin/env sh #!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
############################################################################## ##############################################################################
## ##
## Gradle start up script for UN*X ## Gradle start up script for UN*X
@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS="" DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD="maximum"
@ -109,8 +125,8 @@ 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"`

18
gradlew.bat vendored
View File

@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%" == "" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@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= set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome