diff --git a/.drone.yml b/.drone.yml
new file mode 100644
index 0000000..cfb7155
--- /dev/null
+++ b/.drone.yml
@@ -0,0 +1,9 @@
+kind: pipeline
+name: default
+
+steps:
+- name: assembleRelease
+ image: gradle:jdk8
+ commands:
+ - gradle assembleRelease
+
diff --git a/README.md b/README.md
index d4fdedf..b72a3f5 100644
--- a/README.md
+++ b/README.md
@@ -4,18 +4,19 @@
# ProjectLaogai "hso App"
ProjectLaogai is a app to access the timetable and the mensa menu of Hochschule Offenburg.
-[](https://f-droid.org/packages/org.mosad.seil0.projectlaogai/)
+[](https://f-droid.org/packages/org.mosad.seil0.projectlaogai/)
## 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
## Screenshots
-[](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project_Laogai/ProjectLaogai_HomeScreen.png)
-[](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project_Laogai/ProjectLaogai_Mensa.png)
-[](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project_Laogai/ProjectLaogai_Timetable.png)
-[](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project_Laogai/ProjectLaogai_Settings.png)
-[](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project_Laogai/ProjectLaogai_NavDrawer.png)
+[](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.png)
+[](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.png)
+[](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.png)
+[](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.png)
+[](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa_dark.png)
-ProjectLaogai © 2019 mosad [www.mosad.xyz](http://www.mosad.xyz), Project by [@Seil0](https://git.mosad.xyz/Seil0)
\ No newline at end of file
+ProjectLaogai © 2019 [@Seil0](https://git.mosad.xyz/Seil0), a [mosad](http://www.mosad.xyz) Project
diff --git a/app/build.gradle b/app/build.gradle
index 430579e..c63918f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -7,13 +7,13 @@ apply plugin: 'kotlin-android-extensions'
android {
signingConfigs {
}
- compileSdkVersion 28
+ compileSdkVersion 29
defaultConfig {
applicationId "org.mosad.seil0.projectlaogai"
- minSdkVersion 21
- targetSdkVersion 28
- versionCode 13
- versionName "0.4.1"
+ minSdkVersion 23
+ targetSdkVersion 29
+ versionCode 14
+ versionName "0.5.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "build_time", buildTime()
setProperty("archivesBaseName", "projectlaogai-$versionName")
@@ -25,9 +25,24 @@ android {
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
-
}
+
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'
+ ]
+ }
}
}
@@ -35,20 +50,22 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.anko:anko-commons:0.10.8'
- implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
- implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha5'
+ implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.afollestad:aesthetic:1.0.0-beta05'
- implementation 'com.afollestad.material-dialogs:core:2.8.1'
- implementation 'com.afollestad.material-dialogs:color:2.8.1'
+ 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'
- androidTestImplementation 'androidx.test:runner:1.1.1'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
+ androidTestImplementation 'androidx.test:runner:1.2.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
static def buildTime() {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d81709c..2693fae 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,7 @@
+
+ android:theme="@style/AppTheme.Light">
+
@@ -26,8 +28,18 @@
+ android:theme="@style/AppTheme.Light"
+ android:screenOrientation="portrait"
+ android:launchMode="singleTop">
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/codebutler/farebot/Utils.kt b/app/src/main/java/com/codebutler/farebot/Utils.kt
new file mode 100644
index 0000000..5abf037
--- /dev/null
+++ b/app/src/main/java/com/codebutler/farebot/Utils.kt
@@ -0,0 +1,227 @@
+/**
+ * Utils.kt
+ *
+ * Copyright (C) 2011 Eric Butler
+ * Copyright (C) 2019
+ *
+ * Authors:
+ * Eric Butler
+ *
+ * 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 .
+ */
+
+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 findInList(list: List, matcher: Matcher): T? {
+ for (item in list) {
+ if (matcher.matches(item)) {
+ return item
+ }
+ }
+ return null
+ }
+
+ interface Matcher {
+ 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
+ }
+
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireException.kt b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireException.kt
new file mode 100644
index 0000000..aa7de73
--- /dev/null
+++ b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireException.kt
@@ -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)
+}
diff --git a/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireFile.kt b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireFile.kt
new file mode 100644
index 0000000..cb70096
--- /dev/null
+++ b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireFile.kt
@@ -0,0 +1,104 @@
+/**
+ * DesfireFile.kt
+ *
+ * Copyright (C) 2011 Eric Butler
+ * Copyright (C) 2019
+ *
+ * Authors:
+ * Eric Butler
+ *
+ * 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 .
+ */
+
+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
+
+ init {
+
+ val settings = fileSettings as RecordDesfireFileSettings
+
+ val records = arrayOfNulls(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 = object : Parcelable.Creator {
+ override fun createFromParcel(source: Parcel): DesfireFile {
+ val fileId = source.readInt()
+
+ val isError = source.readInt() == 1
+
+ return if (!isError) {
+ val fileSettings =
+ source.readParcelable(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 {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireFileSettings.kt b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireFileSettings.kt
new file mode 100644
index 0000000..5e1266d
--- /dev/null
+++ b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireFileSettings.kt
@@ -0,0 +1,248 @@
+/**
+ * DesfireFileSettings.kt
+ *
+ * Copyright (C) 2011 Eric Butler
+ * Copyright (C) 2019
+ *
+ * Authors:
+ * Eric Butler
+ *
+ * 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 .
+ */
+
+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 = object : Parcelable.Creator {
+ 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 {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireManufacturingData.kt b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireManufacturingData.kt
new file mode 100644
index 0000000..cbbbd56
--- /dev/null
+++ b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireManufacturingData.kt
@@ -0,0 +1,181 @@
+/**
+ * DesfireManufacturingData.kt
+ *
+ * Copyright (C) 2011 Eric Butler
+ * Copyright (C) 2019
+ *
+ * Authors:
+ * Eric Butler
+ *
+ * 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 .
+ */
+
+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 =
+ object : Parcelable.Creator {
+ override fun createFromParcel(source: Parcel): DesfireManufacturingData {
+ return DesfireManufacturingData(source)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireProtocol.kt b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireProtocol.kt
new file mode 100644
index 0000000..e205734
--- /dev/null
+++ b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireProtocol.kt
@@ -0,0 +1,214 @@
+/**
+ * DesfireProtocol.kt
+ *
+ * Copyright (C) 2011 Eric Butler
+ * Copyright (C) 2019
+ *
+ * Authors:
+ * Eric Butler
+ *
+ * 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 .
+ */
+
+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()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireRecord.kt b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireRecord.kt
new file mode 100644
index 0000000..b328183
--- /dev/null
+++ b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireRecord.kt
@@ -0,0 +1,26 @@
+/*
+ * DesfireRecord.kt
+ *
+ * Copyright (C) 2011 Eric Butler
+ * Copyright (C) 2019
+ *
+ * Authors:
+ * Eric Butler
+ *
+ * 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 .
+ */
+
+package com.codebutler.farebot.card.desfire
+
+class DesfireRecord(val data: ByteArray)
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/MainActivity.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/MainActivity.kt
index 8d2aa82..1ef92a1 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/MainActivity.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/MainActivity.kt
@@ -22,7 +22,13 @@
package org.mosad.seil0.projectlaogai
-import android.graphics.Color
+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.view.Menu
import android.view.MenuItem
@@ -32,27 +38,29 @@ import androidx.core.view.GravityCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import com.afollestad.aesthetic.Aesthetic
+import com.afollestad.aesthetic.NavigationViewMode
import com.google.android.material.navigation.NavigationView
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.app_bar_main.*
import org.mosad.seil0.projectlaogai.controller.CacheController
+import org.mosad.seil0.projectlaogai.controller.NFCMensaCard
import org.mosad.seil0.projectlaogai.controller.PreferencesController
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorAccent
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorPrimary
-import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
-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 org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.fragments.*
-import java.sql.Date
-import java.util.*
import kotlin.system.measureTimeMillis
+// TODO save the current fragment to show it when the app is restarted
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
private var activeFragment: Fragment = HomeFragment() // the currently active fragment, home at the start
+ private lateinit var adapter: NfcAdapter
+ private lateinit var pendingIntent: PendingIntent
+ private lateinit var intentFiltersArray: Array
+ private lateinit var techListsArray: Array>
+ private var useNFC = false
+
override fun onCreate(savedInstanceState: Bundle?) {
Aesthetic.attach(this)
super.onCreate(savedInstanceState)
@@ -61,26 +69,8 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
// load mensa, timetable and color
load()
-
- // If we haven't set any defaults, do that now
- if (Aesthetic.isFirstTime) {
- // this is executed on the first app start, use this to show tutorial etc.
- Aesthetic.config {
- colorPrimary(Color.BLACK)
- colorPrimaryDark(Color.BLACK)
- colorAccent(Color.parseColor("#3F51B5"))
- apply()
- }
-
- SettingsFragment().selectCourse(this)
- } else {
- Aesthetic.config {
- colorPrimary(cColorPrimary)
- colorPrimaryDark(cColorPrimary)
- colorAccent(cColorAccent)
- apply()
- }
- }
+ initAesthetic()
+ initForegroundDispatch()
//init home fragment
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
@@ -94,22 +84,40 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
toggle.syncState()
nav_view.setNavigationItemSelectedListener(this)
+
+ // if we get an NFC read intent while the app is closed call readBalance
+ if (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action)
+ NFCMensaCard.readBalance(intent, this)
}
+ 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() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START)
} else {
+ // TODO only call on double tap
super.onBackPressed()
}
}
@@ -132,22 +140,13 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
override fun onNavigationItemSelected(item: MenuItem): Boolean {
// Handle navigation view item clicks here.
- when (item.itemId) {
- R.id.nav_home -> {
- activeFragment = HomeFragment()
- }
- R.id.nav_mensa -> {
- activeFragment = MensaFragment()
- }
- R.id.nav_timetable -> {
- activeFragment = TimeTableFragment()
- }
- R.id.nav_moodle -> {
- activeFragment = MoodleFragment()
- }
- R.id.nav_settings -> {
- activeFragment = SettingsFragment()
- }
+ activeFragment = when(item.itemId) {
+ R.id.nav_home -> HomeFragment()
+ R.id.nav_mensa -> MensaFragment()
+ R.id.nav_timetable -> TimeTableFragment()
+ R.id.nav_moodle -> MoodleFragment()
+ R.id.nav_settings -> SettingsFragment()
+ else -> HomeFragment()
}
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
@@ -155,6 +154,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
fragmentTransaction.commit()
drawer_layout.closeDrawer(GravityCompat.START)
+
return true
}
@@ -163,56 +163,48 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
*/
private fun load() {
val startupTime = measureTimeMillis {
- // load the settings
- PreferencesController.load(this) // this must be finished before doing anything else
-
- val tcor = TCoRAPIController(this)
- val currentTime = System.currentTimeMillis() / 1000
- val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
- val cal = Calendar.getInstance()
-
- // timetable sunday workaround
- cal.time = Date(timetableCacheTime * 1000)
- val timetableCacheDay = cal.get(Calendar.DAY_OF_WEEK)
-
- // TODO this sill backfire if someone has to update before the server finished updating the timetable at 0001/0101
- // update blocking if a) it`s monday and the last cache was not on a monday or b) the cache is older than 6 days
- if((currentDay == Calendar.MONDAY && timetableCacheDay != Calendar.MONDAY) || (System.currentTimeMillis() / 1000) - timetableCacheTime > 518400) {
- println("updating timetable after sunday!")
- val jobA = TCoRAPIController.getTimetable(cCourse.courseName, 0, this)
- val jobB = TCoRAPIController.getTimetable(cCourse.courseName, 1, this)
-
- jobA.get()
- jobB.get()
- }
-
- // mensa sunday workaround
- cal.time = Date(System.currentTimeMillis()) // reset to current time
-
- // update blocking if it's sunday after 1500
- // TODO and the last update was before 1500
- if(currentDay == Calendar.SUNDAY && cal.get(Calendar.HOUR_OF_DAY) >= 15) {
- val jobA = TCoRAPIController.getMensa(this)
- jobA.get()
- }
-
- // get the cached files
- val cache = CacheController(this)
- cache.readStartCache(cCourse.courseName)
-
- // check if an update is necessary
- if(currentTime - coursesCacheTime > 86400)
- tcor.getCoursesList()
-
- if(currentTime - mensaCacheTime > 10800)
- TCoRAPIController.getMensa(this)
-
- if(currentTime - timetableCacheTime > 10800) {
- TCoRAPIController.getTimetable(cCourse.courseName, 0, this)
- TCoRAPIController.getTimetable(cCourse.courseName, 1, this)
- }
+ PreferencesController.load(this) // load the settings, must be finished before doing anything else
+ CacheController(this) // load the cache
}
println("startup completed in $startupTime ms")
}
+ private fun initAesthetic() {
+ // If we haven't set any defaults, do that now
+ if (Aesthetic.isFirstTime) {
+ // this is executed on the first app start, use this to show tutorial etc.
+ Aesthetic.config {
+ activityTheme(R.style.AppTheme_Light)
+ apply()
+ }
+
+ SettingsFragment().selectCourse(this) // FIXME this is not working
+ }
+
+ Aesthetic.config {
+ colorPrimary(cColorPrimary)
+ colorPrimaryDark(cColorPrimary)
+ colorAccent(cColorAccent)
+ navigationViewMode(NavigationViewMode.SELECTED_ACCENT)
+ apply()
+ }
+
+ }
+
+ private fun initForegroundDispatch() {
+ val nfcManager = this.getSystemService(Context.NFC_SERVICE) as NfcManager
+ val nfcAdapter = nfcManager.defaultAdapter
+
+ if (nfcAdapter != null) {
+ useNFC = true
+ intentFiltersArray = arrayOf(IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED).apply { addDataType("*/*") })
+ techListsArray = arrayOf(arrayOf(NfcA::class.java.name))
+ adapter = NfcAdapter.getDefaultAdapter(this)
+ pendingIntent = PendingIntent.getActivity(
+ this, 0,
+ Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0
+ )
+ }
+ }
+
}
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/CacheController.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/CacheController.kt
index bd8c065..09c6aba 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/CacheController.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/CacheController.kt
@@ -24,27 +24,91 @@ package org.mosad.seil0.projectlaogai.controller
import android.content.Context
import com.google.gson.Gson
+import com.google.gson.GsonBuilder
import com.google.gson.JsonParser
-import org.mosad.seil0.projectlaogai.hsoparser.Course
-import org.mosad.seil0.projectlaogai.hsoparser.MensaWeek
-import org.mosad.seil0.projectlaogai.hsoparser.TimetableWeek
+import com.google.gson.reflect.TypeToken
+import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
+import org.mosad.seil0.projectlaogai.hsoparser.*
import java.io.BufferedReader
import java.io.File
import java.io.FileReader
-import com.google.gson.reflect.TypeToken
+import java.util.*
+import kotlin.collections.ArrayList
class CacheController(cont: Context) {
private val context = cont
+ init {
+ val cal = Calendar.getInstance()
+ val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
+ val currentTime = System.currentTimeMillis() / 1000
+
+ // check if we need to update the mensa data before displaying it
+ readMensa(context)
+ cal.time = Date(mensaMenu.meta.updateTime * 1000)
+
+ // if a) it's monday and the last cache update was on sunday or b) the cache is older than 24hr, update blocking
+ if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - mensaMenu.meta.updateTime) > 86400) {
+ println("update mensa blocking")
+ TCoRAPIController.getMensa(context).get()
+ }
+
+ // check if we need to update the timetables before displaying them
+ readTimetable(cCourse.courseName, 0, context)
+ cal.time = Date(timetables[0].meta.updateTime * 1000)
+
+ // if a) it`s monday and the last cache update was not on a sunday or b) the cache is older than 24hr, update blocking
+ if((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - timetables[0].meta.updateTime) > 86400) {
+ println("updating timetable after sunday!")
+ val jobA = TCoRAPIController.getTimetable(cCourse.courseName, 0, context)
+ val jobB = TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
+
+ jobA.get()
+ jobB.get()
+ }
+
+ // check if an update is necessary, not blocking
+ if(currentTime - PreferencesController.coursesCacheTime > 86400)
+ TCoRAPIController.getCoursesList(context)
+
+ if(currentTime - PreferencesController.mensaCacheTime > 10800)
+ TCoRAPIController.getMensa(context)
+
+ if(currentTime - PreferencesController.timetableCacheTime > 10800) {
+ TCoRAPIController.getTimetable(cCourse.courseName, 0, context)
+ TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
+ }
+
+ readStartCache(cCourse.courseName)
+ }
+
companion object {
var coursesList = ArrayList()
- var mensaCurrentWeek = MensaWeek()
- var mensaNextWeek = MensaWeek()
- var timetables = ArrayList()
+ var timetables = ArrayList()
+ var mensaMenu = MensaMenu()
/**
- * read current and next weeks mensa menus from the cached file
+ * read the courses list from the cached file
+ * add them to the coursesList object
+ */
+ private fun readCoursesList(context: Context) {
+ val file = File(context.filesDir, "courses.json")
+
+ // make sure the file exists
+ if (!file.exists())
+ TCoRAPIController.getCoursesList(context).get()
+
+ val fileReader = FileReader(file)
+ val bufferedReader = BufferedReader(fileReader)
+ val coursesObject = JsonParser().parse(bufferedReader.readLine()).asJsonObject
+
+ coursesList = Gson().fromJson(coursesObject.getAsJsonArray("courses"), object : TypeToken>() {}.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")
@@ -54,16 +118,11 @@ class CacheController(cont: Context) {
TCoRAPIController.getMensa(context).get()
}
-
val fileReader = FileReader(file)
val bufferedReader = BufferedReader(fileReader)
val mensaObject = JsonParser().parse(bufferedReader.readLine()).asJsonObject
- val currentWeek = mensaObject.getAsJsonObject("currentWeek")
- val nextWeek = mensaObject.getAsJsonObject("nextWeek")
-
- mensaCurrentWeek = Gson().fromJson(currentWeek, MensaWeek().javaClass)
- mensaNextWeek = Gson().fromJson(nextWeek, MensaWeek().javaClass)
+ mensaMenu = GsonBuilder().create().fromJson(mensaObject, MensaMenu().javaClass)
}
/**
@@ -83,10 +142,10 @@ class CacheController(cont: Context) {
val timetableObject = JsonParser().parse(bufferedReader.readLine()).asJsonObject
// make sure you add the single weeks in the exact order!
- if (timetables.size == week) {
- timetables.add(Gson().fromJson(timetableObject.getAsJsonObject("timetable"), TimetableWeek().javaClass))
- } else if (timetables.size >= week) {
- timetables[week] = Gson().fromJson(timetableObject.getAsJsonObject("timetable"), TimetableWeek().javaClass)
+ if (timetables.size > week) {
+ timetables[week] = (Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass))
+ } else {
+ timetables.add(Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass))
}
}
}
@@ -96,28 +155,10 @@ class CacheController(cont: Context) {
* @param courseName the course name (e.g AI1)
*/
fun readStartCache(courseName: String) {
- readCoursesList()
+ readCoursesList(context)
readMensa(context)
readTimetable(courseName, 0, context)
readTimetable(courseName, 1, context)
}
- /**
- * read the courses list from the cached file
- * add them to the coursesList object
- */
- private fun readCoursesList() {
- val file = File(context.filesDir, "courses.json")
-
- // make sure the file exists
- if (!file.exists())
- TCoRAPIController(context).getCoursesList().get()
-
- val fileReader = FileReader(file)
- val bufferedReader = BufferedReader(fileReader)
- val coursesObject = JsonParser().parse(bufferedReader.readLine()).asJsonObject
-
- coursesList = Gson().fromJson(coursesObject.getAsJsonArray("courses"), object : TypeToken>() {}.type)
- }
-
}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/NFCMensaCard.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/NFCMensaCard.kt
new file mode 100644
index 0000000..e3fbb77
--- /dev/null
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/NFCMensaCard.kt
@@ -0,0 +1,105 @@
+/**
+ * ProjectLaogai
+ *
+ * Copyright 2019
+ *
+ * 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.i(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
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/PreferencesController.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/PreferencesController.kt
index 4901306..d6a43fa 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/PreferencesController.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/PreferencesController.kt
@@ -42,6 +42,7 @@ class PreferencesController {
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) {
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/TCoRAPIController.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/TCoRAPIController.kt
index af16d67..2d03274 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/controller/TCoRAPIController.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/controller/TCoRAPIController.kt
@@ -23,76 +23,98 @@
package org.mosad.seil0.projectlaogai.controller
import android.content.Context
+import android.util.Log
import org.jetbrains.anko.doAsync
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.*
+import java.io.BufferedWriter
+import java.io.File
+import java.io.FileWriter
import java.net.URL
+import kotlin.Exception
-class TCoRAPIController(cont: Context) {
- private val context = cont
+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) = doAsync {
+ 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
+ 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) = doAsync {
- val url = URL("https://tcor.mosad.xyz/mensamenu")
- val file = File(context.filesDir, "mensa.json")
+ 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())
+ // read data from the API
+ val mensaObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
- // write the json object to a file
- val writer = BufferedWriter(FileWriter(file))
- writer.write(mensaObject.toString())
- writer.close()
+ // write the json object to a file
+ val writer = BufferedWriter(FileWriter(file))
+ writer.write(mensaObject.toString())
+ writer.close()
+
+ // update cache time
+ mensaCacheTime = System.currentTimeMillis() / 1000
+ PreferencesController.save(context)
+ } catch (ex: Exception) {
+ Log.e(className, "failed to get /mensamenu", ex)
+ }
- // update cache time
- mensaCacheTime = System.currentTimeMillis() / 1000
- PreferencesController.save(context)
}
/**
* get the json object from tcor api and write it as file (cache)
*/
fun getTimetable(courseName: String, week: Int, context: Context) = doAsync {
- val url = URL("https://tcor.mosad.xyz/timetable?courseName=$courseName&week=$week")
- val file = File(context.filesDir, "timetable-$courseName-$week.json")
+ 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 mensaObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
+ // read data from the API
+ val mensaObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
- // write the json object to a file
- val writer = BufferedWriter(FileWriter(file))
- writer.write(mensaObject.toString())
- writer.close()
+ // write the json object to a file
+ val writer = BufferedWriter(FileWriter(file))
+ writer.write(mensaObject.toString())
+ writer.close()
+
+ // update cache time
+ timetableCacheTime = System.currentTimeMillis() / 1000
+ PreferencesController.save(context)
+ } catch (ex: Exception) {
+ Log.e(className, "failed to get /timetable", ex)
+ }
- // update cache time
- timetableCacheTime = System.currentTimeMillis() / 1000
- PreferencesController.save(context)
}
}
- /**
- * get the json object from tcor api and write it as file (cache)
- */
- fun getCoursesList() = doAsync {
- val url = URL("https://tcor.mosad.xyz/courses")
- 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
- val writer = BufferedWriter(FileWriter(file))
- writer.write(coursesObject.toString())
- writer.close()
-
- // update cache time
- coursesCacheTime = System.currentTimeMillis() / 1000
- PreferencesController.save(context)
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/HomeFragment.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/HomeFragment.kt
index ab5ac75..3973212 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/HomeFragment.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/HomeFragment.kt
@@ -24,25 +24,22 @@ package org.mosad.seil0.projectlaogai.fragments
import android.graphics.Typeface
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
-import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
-import com.afollestad.materialdialogs.MaterialDialog
import kotlinx.android.synthetic.main.fragment_home.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
import org.mosad.seil0.projectlaogai.R
-import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaCurrentWeek
+import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaMenu
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.timetables
-import org.mosad.seil0.projectlaogai.hsoparser.DataTypes
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.LessonLinearLayout
import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
import java.text.SimpleDateFormat
import java.util.*
@@ -53,6 +50,7 @@ import java.util.*
*/
class HomeFragment : Fragment() {
+ private val className = "HomeFragment"
private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault())
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@@ -60,7 +58,7 @@ class HomeFragment : Fragment() {
val view: View = inflater.inflate(R.layout.fragment_home, container, false)
addMensaMenu().get()
- addTimeTable()
+ addTimeTable().get()
// Inflate the layout for this fragment
return view
@@ -77,38 +75,41 @@ class HomeFragment : Fragment() {
uiThread {
- if (cal.get(Calendar.HOUR_OF_DAY) < 15) {
- dayMeals = mensaCurrentWeek.days[NotRetardedCalendar().getDayOfWeekIndex()].meals
- mensaCardView.setDayHeading(resources.getString(R.string.today_date, formatter.format(cal.time)))
- } else {
- dayMeals = mensaCurrentWeek.days[NotRetardedCalendar().getTomorrowWeekIndex()].meals
- cal.add(Calendar.DATE, 1)
- mensaCardView.setDayHeading(resources.getString(R.string.tomorrow_date, formatter.format(cal.time)))
- }
-
- if (dayMeals.size >= 2) {
- // get the index of the first meal, not a "Schneller Teller"
- loop@ for ((i, meal) in dayMeals.withIndex()) {
- if (meal.heading.contains("Essen")) {
-
- val meal1Layout = MealLinearLayout(context)
- meal1Layout.setMeal(dayMeals[i])
- mensaCardView.getLinLayoutDay().addView(meal1Layout)
-
- val meal2Layout = MealLinearLayout(context)
- meal2Layout.setMeal(dayMeals[i + 1])
- meal2Layout.disableDivider()
- mensaCardView.getLinLayoutDay().addView(meal2Layout)
-
- break@loop
- }
+ if (isAdded) {
+ 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)))
}
- } else {
- mensaCardView.getLinLayoutDay().addView(getNoCard(resources.getString(R.string.mensa_closed)))
+ if (dayMeals.size >= 2) {
+ // get the index of the first meal, not a "Schneller Teller"
+ loop@ for ((i, meal) in dayMeals.withIndex()) {
+ if (meal.heading.contains("Essen")) {
+
+ val meal1Layout = MealLinearLayout(context)
+ meal1Layout.setMeal(dayMeals[i])
+ mensaCardView.getLinLayoutDay().addView(meal1Layout)
+
+ val meal2Layout = MealLinearLayout(context)
+ meal2Layout.setMeal(dayMeals[i + 1])
+ meal2Layout.disableDivider()
+ mensaCardView.getLinLayoutDay().addView(meal2Layout)
+
+ break@loop
+ }
+ }
+
+ } else {
+ mensaCardView.getLinLayoutDay().addView(getNoCard(resources.getString(R.string.mensa_closed)))
+ }
+
+ linLayout_Home.addView(mensaCardView)
}
- linLayout_Home.addView(mensaCardView)
}
}
@@ -117,99 +118,52 @@ class HomeFragment : Fragment() {
* add the current timetable to the home screen
*/
private fun addTimeTable() = doAsync {
- val dayIndex = NotRetardedCalendar().getDayOfWeekIndex()
- val cal = Calendar.getInstance()
- var dayCardView: DayCardView
uiThread {
- if (timetables.isNotEmpty() && dayIndex < 6) {
-
- // first check the current day
- dayCardView = addDayTimetable(timetables[0].days[dayIndex])
- dayCardView.setDayHeading(resources.getString(R.string.today_date, formatter.format(cal.time)))
-
- // if there are no lessons try to find the next day with a lesson
- if (dayCardView.getLinLayoutDay().childCount <= 1)
- dayCardView = findNextDay(0, dayIndex + 1)
-
- linLayout_Home.addView(dayCardView)
- } else if (dayIndex == 6) {
- // if that's the case it's sunday
- dayCardView = findNextDay(1, 0)
- linLayout_Home.addView(dayCardView)
- } else {
- MaterialDialog(context!!)
- .title(R.string.error)
- .message(R.string.timetable_error)
- .show()
- // TODO log the error and send feedback
- }
-
- }
-
- }
-
- /**
- * add the timetable of one day to the home-screen
- * @param dayTimetable the day you wish to add
- */
- private fun addDayTimetable(dayTimetable: TimetableDay) : DayCardView{
- var helpLesson = LessonLinearLayout(context)
- val dayCardView = DayCardView(context!!)
-
- for ((tsIndex, timeslot) in dayTimetable.timeslots.withIndex()) {
-
- for(lesson in timeslot) {
- if(lesson.lessonSubject.isNotEmpty()) {
-
- val lessonLayout = LessonLinearLayout(context)
- lessonLayout.setLesson(lesson, DataTypes().getTime()[tsIndex])
- dayCardView.getLinLayoutDay().addView(lessonLayout)
-
- if (lesson != timeslot.last()) {
- lessonLayout.disableDivider()
- }
-
- helpLesson = lessonLayout
+ if (isAdded && timetables.isNotEmpty()) {
+ try {
+ val dayCardView = findNextDay(NotRetardedCalendar.getDayOfWeekIndex())
+ linLayout_Home.addView(dayCardView)
+ } catch (ex: Exception) {
+ Log.e(className, "could not load timetable", ex) // TODO send feedback
}
}
}
- helpLesson.disableDivider()
-
- return dayCardView
}
/**
* find the next day with a lesson
- * @param startWeekIndex the week you want to start searching
+ * 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(startWeekIndex: Int, startDayIndex: Int) : DayCardView{
- val cal = Calendar.getInstance()
- var dayCardView = DayCardView(context!!)
+ private fun findNextDay(startDayIndex: Int) : DayCardView {
+ val dayCardView = DayCardView(context!!)
var dayTimetable: TimetableDay? = null
var dayIndexSearch = startDayIndex
- var weekIndexSearch = startWeekIndex
- loop@ while (dayTimetable == null && weekIndexSearch <= timetables.size) {
- for (i in (dayIndexSearch) ..5) {
- dayTimetable = timetables[weekIndexSearch].days[i]
- cal.add(Calendar.DATE, 1)
+ var weekIndexSearch = 0
- // add the timetable to the card, if it contains at least one lesson break!
- dayCardView = addDayTimetable(dayTimetable)
- dayCardView.setDayHeading(formatter.format(cal.time))
+ 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
}
- dayIndexSearch = 0
+
weekIndexSearch++
- cal.add(Calendar.DATE, 1)
+ 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
@@ -222,7 +176,6 @@ class HomeFragment : Fragment() {
private fun getNoCard(text: String): TextView {
val noLesson = TextView(context)
noLesson.text = text
- noLesson.setTextColor(ContextCompat.getColor(context!!, R.color.textPrimary))
noLesson.textSize = 18.0F
noLesson.setTypeface(null, Typeface.BOLD)
noLesson.textAlignment = View.TEXT_ALIGNMENT_CENTER
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/MensaFragment.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/MensaFragment.kt
index 109ae0f..771637e 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/MensaFragment.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/MensaFragment.kt
@@ -30,9 +30,9 @@ import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_mensa.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
+import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.CacheController
-import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaCurrentWeek
-import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaNextWeek
+import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaMenu
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cShowBuffet
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.hsoparser.MensaWeek
@@ -48,17 +48,17 @@ class MensaFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- val view: View = inflater.inflate(org.mosad.seil0.projectlaogai.R.layout.fragment_mensa, container, false)
+ val view: View = inflater.inflate(R.layout.fragment_mensa, container, false)
// init actions
refreshAction()
// add the current week (week starts on sunday)
- val dayCurrent = if(NotRetardedCalendar().getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar().getDayOfWeekIndex()
- addWeek(mensaCurrentWeek, dayCurrent).get()
+ val dayCurrent = if(NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
+ addWeek(mensaMenu.currentWeek, dayCurrent).get()
// add the next week
- addWeek(mensaNextWeek, 0)
+ addWeek(mensaMenu.nextWeek, 0)
// TODO should we show a info if there is no more food this & next week?
@@ -128,11 +128,11 @@ class MensaFragment : Fragment() {
linLayout_Mensa.removeAllViews()
// add the refreshed menus
- val dayCurrent = if (NotRetardedCalendar().getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar().getDayOfWeekIndex()
- addWeek(mensaCurrentWeek, dayCurrent).get()
+ val dayCurrent = if (NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
+ addWeek(mensaMenu.currentWeek, dayCurrent).get()
// add the next week
- addWeek(mensaNextWeek, 0)
+ addWeek(mensaMenu.nextWeek, 0)
refreshLayout_Mensa.isRefreshing = false
}
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/SettingsFragment.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/SettingsFragment.kt
index 972e243..50565b9 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/SettingsFragment.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/SettingsFragment.kt
@@ -24,6 +24,7 @@ package org.mosad.seil0.projectlaogai.fragments
import android.content.Context
import android.os.Bundle
+import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -35,22 +36,25 @@ import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.color.colorChooser
import com.afollestad.materialdialogs.customview.customView
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 org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
import org.mosad.seil0.projectlaogai.BuildConfig
+import org.mosad.seil0.projectlaogai.R
+import org.mosad.seil0.projectlaogai.controller.CacheController
+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.R
-import org.mosad.seil0.projectlaogai.controller.CacheController
-import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.coursesList
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.hsoparser.DataTypes
import java.util.*
+
/**
* The settings controller class
* contains all needed parts to display and the settings screen
@@ -60,6 +64,8 @@ class SettingsFragment : Fragment() {
private lateinit var linLayoutUser: LinearLayout
private lateinit var linLayoutCourse: LinearLayout
private lateinit var linLayoutAbout: LinearLayout
+ private lateinit var linLayoutLicence: LinearLayout
+ private lateinit var linLayoutTheme: LinearLayout
private lateinit var linLayoutPrimaryColor: LinearLayout
private lateinit var linLayoutAccentColor: LinearLayout
private lateinit var switchBuffet: Switch
@@ -71,6 +77,8 @@ class SettingsFragment : Fragment() {
linLayoutUser = view.findViewById(R.id.linLayout_User)
linLayoutCourse = view.findViewById(R.id.linLayout_Course)
linLayoutAbout = view.findViewById(R.id.linLayout_About)
+ linLayoutLicence = view.findViewById(R.id.linLayout_Licence)
+ 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)
@@ -84,9 +92,28 @@ class SettingsFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ // 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)
+ }
+ }
+
}
/**
@@ -97,9 +124,13 @@ class SettingsFragment : Fragment() {
// open a new dialog
}
+ linLayoutUser.setOnLongClickListener {
+ PreferencesController.oGiants = true // enable easter egg
+ return@setOnLongClickListener true
+ }
+
linLayoutCourse.setOnClickListener {
selectCourse(context!!)
- txtView_Course.text = cCourse.courseName // update txtView
}
linLayoutAbout.setOnClickListener {
@@ -110,6 +141,52 @@ class SettingsFragment : Fragment() {
.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
+ }
+
+ val themeId = when (outValue.string) {
+ "light" -> R.style.AppTheme_Light
+ else -> R.style.LicensesDialogTheme_Dark
+ }
+
+ // 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)
+ }
+ }
+ }
+ }
+ }
+
linLayoutPrimaryColor.setOnClickListener {
// open a new color chooser dialog
MaterialDialog(context!!)
@@ -184,6 +261,7 @@ class SettingsFragment : Fragment() {
uiThread {
dialog.dismiss()
+ txtView_Course.text = cCourse.courseName // update txtView
}
}
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/TimeTableFragment.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/TimeTableFragment.kt
index 824c706..d4a2dc5 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/TimeTableFragment.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/fragments/TimeTableFragment.kt
@@ -26,8 +26,10 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.ScrollView
import androidx.fragment.app.Fragment
import com.afollestad.materialdialogs.MaterialDialog
+import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.android.synthetic.main.fragment_timetable.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
@@ -36,12 +38,8 @@ 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.DataTypes
import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar
-import org.mosad.seil0.projectlaogai.hsoparser.TimetableWeek
-import org.mosad.seil0.projectlaogai.uicomponents.*
-import java.text.SimpleDateFormat
-import java.util.*
+import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
/**
* The timetable controller class
@@ -49,16 +47,19 @@ import java.util.*
*/
class TimeTableFragment : Fragment() {
- private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault()) // TODO change to android call when min api is 24
+ private lateinit var scrollViewTimetable: ScrollView
+ private lateinit var faBtnAddLesson: FloatingActionButton
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(R.layout.fragment_timetable, container, false)
+ scrollViewTimetable = view.findViewById(R.id.scrollView_Timetable)
+ faBtnAddLesson = view.findViewById(R.id.faBtnAddLesson)
// init actions
- refreshAction()
+ initActions()
- if (timetables[0].days.isNotEmpty() && timetables[1].days.isNotEmpty()) {
+ if (timetables[0].timetable.days.isNotEmpty() && timetables[1].timetable.days.isNotEmpty()) {
addInitWeeks()
} else {
MaterialDialog(context!!)
@@ -70,70 +71,64 @@ class TimeTableFragment : Fragment() {
return view
}
+ /**
+ * initialize the actions
+ */
+ private fun initActions() = doAsync {
+
+ uiThread {
+ refreshLayout_Timetable.setOnRefreshListener {
+ updateTimetableScreen()
+ }
+
+ faBtnAddLesson.setOnClickListener {
+ MaterialDialog(context!!)
+ .title(text = "Vorlesung hinzufügen")
+ .message(text = "wähle einen Studiengang aus:\n\nWähle eine Vorlesung aus: \n\n Diese Funktion ist noch nicht verfügbar")
+ .show()
+ }
+
+ // hide the btnCardValue if the user is scrolling down
+ scrollViewTimetable.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
+ if (scrollY > oldScrollY) {
+ faBtnAddLesson.hide()
+ } else {
+ faBtnAddLesson.show()
+ }
+ }
+
+ }
+
+ }
+
/**
* add the current and next weeks lessons
*/
private fun addInitWeeks() = doAsync {
- val dayIndex = NotRetardedCalendar().getDayOfWeekIndex()
- val calendar = Calendar.getInstance()
+ val currentDayIndex = NotRetardedCalendar.getDayOfWeekIndex()
- // add current week
- addWeek(dayIndex, 5, timetables[0], calendar).get()
-
- // add next week
- addWeek(0, dayIndex - 1, timetables[1], calendar)
+ addWeek(currentDayIndex, 5, 0).get() // add current week
+ addWeek(0, currentDayIndex - 1, 1) // add next week
}
- private fun addWeek(dayStart: Int, dayEnd: Int, timetable: TimetableWeek, calendar: Calendar) = doAsync {
+ private fun addWeek(startDayIndex: Int, dayEndIndex: Int, weekIndex: Int) = doAsync {
+ val timetable = timetables[weekIndex].timetable
+ val timetableMeta = timetables[weekIndex].meta
+
uiThread {
- for (day in dayStart..dayEnd) {
- var helpLesson = LessonLinearLayout(context)
+ for (dayIndex in startDayIndex..dayEndIndex) {
val dayCardView = DayCardView(context!!)
- dayCardView.setDayHeading(formatter.format(calendar.time))
- // for each timeslot of the day
- for ((tsIndex, timeslot) in timetable.days[day].timeslots.withIndex()) {
-
- for(lesson in timeslot) {
-
- if(lesson.lessonSubject.isNotEmpty()) {
-
- val lessonLayout = LessonLinearLayout(context)
- lessonLayout.setLesson(lesson, DataTypes().getTime()[tsIndex])
- dayCardView.getLinLayoutDay().addView(lessonLayout)
-
- if (lesson != timeslot.last())
- lessonLayout.disableDivider()
-
- helpLesson = lessonLayout
- }
- }
- }
-
- helpLesson.disableDivider()
- calendar.add(Calendar.DATE, 1)
+ // 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)
}
-
- calendar.add(Calendar.DATE, 1) // before this we are at a sunday (no lecture on sundays!)
- }
- }
-
- /**
- * initialize the refresh action
- */
- private fun refreshAction() = doAsync {
- uiThread {
- // set the refresh listener
- refreshLayout_Timetable.setOnRefreshListener {
- updateTimetableScreen()
- }
-
}
}
@@ -150,14 +145,13 @@ class TimeTableFragment : Fragment() {
linLayout_Timetable.removeAllViews()
// add the refreshed timetables
- val dayIndex = NotRetardedCalendar().getDayOfWeekIndex()
- val calendar = Calendar.getInstance()
+ val dayIndex = NotRetardedCalendar.getDayOfWeekIndex()
// add current week
- addWeek(dayIndex, 5, timetables[0], calendar).get()
+ addWeek(dayIndex, 5, 0).get()
// add next week
- addWeek(0, dayIndex - 1, timetables[1], calendar)
+ addWeek(0, dayIndex - 1, 1)
refreshLayout_Timetable.isRefreshing = false
}
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/hsoparser/DataTypes.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/hsoparser/DataTypes.kt
index acd972d..f3936f3 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/hsoparser/DataTypes.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/hsoparser/DataTypes.kt
@@ -27,7 +27,8 @@ import java.util.*
import kotlin.collections.ArrayList
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"),
@@ -69,7 +70,7 @@ class DataTypes {
Color.parseColor("#FF9100"),
Color.parseColor("#FF3D00"),
Color.parseColor("#000000")
- )
+ )
init {
// do something
@@ -82,45 +83,70 @@ class DataTypes {
}
class NotRetardedCalendar {
- private val calendar = Calendar.getInstance()!!
+ companion object {
+ private val calendar = Calendar.getInstance()
- fun getDayOfWeekIndex(): Int {
- return when(calendar.get(Calendar.DAY_OF_WEEK)) {
- Calendar.MONDAY -> 0
- Calendar.TUESDAY -> 1
- Calendar.WEDNESDAY -> 2
- Calendar.THURSDAY -> 3
- Calendar.FRIDAY -> 4
- Calendar.SATURDAY -> 5
- Calendar.SUNDAY -> 6
- else -> 7
+ fun getDayOfWeekIndex(): Int {
+ return when (calendar.get(Calendar.DAY_OF_WEEK)) {
+ Calendar.MONDAY -> 0
+ Calendar.TUESDAY -> 1
+ Calendar.WEDNESDAY -> 2
+ Calendar.THURSDAY -> 3
+ Calendar.FRIDAY -> 4
+ Calendar.SATURDAY -> 5
+ Calendar.SUNDAY -> 6
+ else -> 7
+ }
}
- }
- fun getTomorrowWeekIndex(): Int {
- return when(calendar.get(Calendar.DAY_OF_WEEK)) {
- Calendar.MONDAY -> 1
- Calendar.TUESDAY -> 2
- Calendar.WEDNESDAY -> 3
- Calendar.THURSDAY -> 4
- Calendar.FRIDAY -> 5
- Calendar.SATURDAY -> 6
- Calendar.SUNDAY -> 0
- else -> 7
+ fun getTomorrowWeekIndex(): Int {
+ return when (calendar.get(Calendar.DAY_OF_WEEK)) {
+ Calendar.MONDAY -> 1
+ Calendar.TUESDAY -> 2
+ Calendar.WEDNESDAY -> 3
+ Calendar.THURSDAY -> 4
+ Calendar.FRIDAY -> 5
+ Calendar.SATURDAY -> 6
+ Calendar.SUNDAY -> 0
+ else -> 7
+ }
+ }
+
+ fun getWeekOfYear(): Int {
+ return when (calendar.get(Calendar.DAY_OF_WEEK)) {
+ Calendar.SUNDAY -> Calendar.getInstance().get(Calendar.WEEK_OF_YEAR) - 1
+ else -> Calendar.getInstance().get(Calendar.WEEK_OF_YEAR)
+ }
}
}
}
+// data classes for the course part
data class Course(val courseLink: String, val courseName: String)
+// data classes for the Mensa part
data class Meal(val day: String, val heading: String, val parts: ArrayList, val additives: String)
data class Meals(val meals: ArrayList)
data class MensaWeek(val days: Array = Array(7) { Meals(ArrayList()) })
-data class Lesson(val lessonSubject: String, val lessonTeacher: String, val lessonRoom:String, val lessonRemark: String)
+data class MensaMeta(val updateTime: Long = 0, val mensaName: String = "")
-data class TimetableDay( val timeslots: Array> = Array(6) { ArrayList()})
+data class MensaMenu(val meta: MensaMeta = MensaMeta(), val currentWeek: MensaWeek = MensaWeek(), val nextWeek: MensaWeek = MensaWeek())
-data class TimetableWeek(val days: Array = Array(6) { TimetableDay() })
\ No newline at end of file
+// 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> = Array(6) { ArrayList() })
+
+data class TimetableWeek(val days: Array = 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())
diff --git a/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/DayCardView.kt b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/DayCardView.kt
index 7ab1287..b1e9a95 100644
--- a/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/DayCardView.kt
+++ b/app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/DayCardView.kt
@@ -28,9 +28,15 @@ 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)
@@ -46,4 +52,38 @@ class DayCardView(context: Context) : CardView(context) {
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
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/background_splash.xml b/app/src/main/res/drawable/background_splash.xml
index f9df9fc..9de6881 100644
--- a/app/src/main/res/drawable/background_splash.xml
+++ b/app/src/main/res/drawable/background_splash.xml
@@ -3,9 +3,7 @@
-
-
- -
+
-
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
deleted file mode 100644
index 6429411..0000000
--- a/app/src/main/res/layout/fragment_settings.xml
+++ /dev/null
@@ -1,185 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/linearlayout_lesson.xml b/app/src/main/res/layout/linearlayout_lesson.xml
deleted file mode 100644
index 4012441..0000000
--- a/app/src/main/res/layout/linearlayout_lesson.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/linearlayout_meal.xml b/app/src/main/res/layout/linearlayout_meal.xml
deleted file mode 100644
index 15752aa..0000000
--- a/app/src/main/res/layout/linearlayout_meal.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layouts/activities/layout/activity_main.xml
similarity index 95%
rename from app/src/main/res/layout/activity_main.xml
rename to app/src/main/res/layouts/activities/layout/activity_main.xml
index 7912343..6259fe8 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layouts/activities/layout/activity_main.xml
@@ -20,8 +20,9 @@
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
+ android:background="?themeSecondary"
app:headerLayout="@layout/nav_header_main"
app:itemTextColor="?colorAccent"
app:menu="@menu/activity_main_drawer"/>
-
+
diff --git a/app/src/main/res/layout/app_bar_main.xml b/app/src/main/res/layouts/activities/layout/app_bar_main.xml
similarity index 100%
rename from app/src/main/res/layout/app_bar_main.xml
rename to app/src/main/res/layouts/activities/layout/app_bar_main.xml
diff --git a/app/src/main/res/layout/cardview_day.xml b/app/src/main/res/layouts/activities/layout/cardview_day.xml
similarity index 78%
rename from app/src/main/res/layout/cardview_day.xml
rename to app/src/main/res/layouts/activities/layout/cardview_day.xml
index 8c27625..1fdcca4 100644
--- a/app/src/main/res/layout/cardview_day.xml
+++ b/app/src/main/res/layouts/activities/layout/cardview_day.xml
@@ -2,8 +2,10 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layouts/activities/layout/linearlayout_meal.xml b/app/src/main/res/layouts/activities/layout/linearlayout_meal.xml
new file mode 100644
index 0000000..519307e
--- /dev/null
+++ b/app/src/main/res/layouts/activities/layout/linearlayout_meal.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/nav_header_main.xml b/app/src/main/res/layouts/activities/layout/nav_header_main.xml
similarity index 96%
rename from app/src/main/res/layout/nav_header_main.xml
rename to app/src/main/res/layouts/activities/layout/nav_header_main.xml
index 087f997..67d4683 100644
--- a/app/src/main/res/layout/nav_header_main.xml
+++ b/app/src/main/res/layouts/activities/layout/nav_header_main.xml
@@ -19,7 +19,7 @@
android:layout_height="wrap_content"
android:paddingTop="@dimen/nav_header_vertical_spacing"
app:srcCompat="@mipmap/ic_laogai_icon"
- android:contentDescription="@string/nav_header_desc"
+ android:contentDescription="@string/app_name"
android:id="@+id/imageView"/>
+
+
+ body {
+ background-color: #ffffff;
+ color: #000000;
+ font-family: sans-serif;
+ overflow-wrap: break-word;
+ }
+ pre {
+ background-color: #eeeeee;
+ padding: 1em;
+ white-space: pre-wrap;
+ }
+
+
+
+ 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;
+ }
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_loading.xml b/app/src/main/res/layouts/dialogs/layout/dialog_loading.xml
similarity index 100%
rename from app/src/main/res/layout/dialog_loading.xml
rename to app/src/main/res/layouts/dialogs/layout/dialog_loading.xml
diff --git a/app/src/main/res/layouts/dialogs/layout/dialog_mensa_credit.xml b/app/src/main/res/layouts/dialogs/layout/dialog_mensa_credit.xml
new file mode 100644
index 0000000..9eeee12
--- /dev/null
+++ b/app/src/main/res/layouts/dialogs/layout/dialog_mensa_credit.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layouts/fragments/layout/fragment_home.xml
similarity index 88%
rename from app/src/main/res/layout/fragment_home.xml
rename to app/src/main/res/layouts/fragments/layout/fragment_home.xml
index 7d10427..0971b9c 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layouts/fragments/layout/fragment_home.xml
@@ -3,7 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".fragments.HomeFragment">
+ tools:context=".fragments.HomeFragment"
+ android:background="?themePrimary">
+ tools:context=".fragments.MensaFragment"
+ android:background="?themePrimary">
+ android:layout_height="match_parent"
+ android:id="@+id/scrollView_Mensa">
diff --git a/app/src/main/res/layout/fragment_moodle.xml b/app/src/main/res/layouts/fragments/layout/fragment_moodle.xml
similarity index 100%
rename from app/src/main/res/layout/fragment_moodle.xml
rename to app/src/main/res/layouts/fragments/layout/fragment_moodle.xml
diff --git a/app/src/main/res/layouts/fragments/layout/fragment_settings.xml b/app/src/main/res/layouts/fragments/layout/fragment_settings.xml
new file mode 100644
index 0000000..c33c639
--- /dev/null
+++ b/app/src/main/res/layouts/fragments/layout/fragment_settings.xml
@@ -0,0 +1,283 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_timetable.xml b/app/src/main/res/layouts/fragments/layout/fragment_timetable.xml
similarity index 56%
rename from app/src/main/res/layout/fragment_timetable.xml
rename to app/src/main/res/layouts/fragments/layout/fragment_timetable.xml
index 86982a6..94e22d4 100644
--- a/app/src/main/res/layout/fragment_timetable.xml
+++ b/app/src/main/res/layouts/fragments/layout/fragment_timetable.xml
@@ -3,7 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".fragments.TimeTableFragment">
+ tools:context=".fragments.TimeTableFragment"
+ android:background="?themePrimary">
+ android:layout_height="match_parent" android:id="@+id/scrollView_Timetable">
+
\ No newline at end of file
diff --git a/app/src/main/res/raw/notices.xml b/app/src/main/res/raw/notices.xml
new file mode 100644
index 0000000..99c8b86
--- /dev/null
+++ b/app/src/main/res/raw/notices.xml
@@ -0,0 +1,39 @@
+
+
+
+ Material Dialogs
+ https://github.com/afollestad/material-dialogs
+ Copyright (C) Aidan Follestad
+ Apache Software License 2.0
+
+
+ Aesthetic
+ https://github.com/afollestad/aesthetic
+ Copyright Aidan Follestad
+ Apache Software License 2.0
+
+
+ gson
+ https://github.com/google/gson
+ Copyright 2008 Google Inc.
+ Apache Software License 2.0
+
+
+ anko
+ https://github.com/Kotlin/anko
+ Copyright JetBrains
+ Apache Software License 2.0
+
+
+ farebot part for desfire cards
+ https://github.com/codebutler/farebot
+ Copyright 2011-2012, 2014, 2016 Eric Butler
+ GNU General Public License 3.0
+
+
+ Android Support Libraries
+ http://developer.android.com/tools/support-library/index.html
+ Copyright (C) 2016 The Android Open Source Project
+ Apache Software License 2.0
+
+
diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml
index 85d7943..64a267b 100644
--- a/app/src/main/res/values-de-rDE/strings.xml
+++ b/app/src/main/res/values-de-rDE/strings.xml
@@ -1,30 +1,50 @@
+ Navigationsleiste schließen
+ Navigationsleiste öffnen
+
+
Home
Mensa
Stundenplan
Moodle
Einstellungen
+
+
Essen
Heute, %1$s
Morgen, %1$s
keine Essensausgabe
Diese Woche keine weitere Essensausgabe
heute keine Vorlesung!
- Fehler
- Stundenplan konnte nicht geladen werden!
+
+
Info
Benutzer
Tippen, um den Kurs zu ändern
+ Über
+ Lizenzen
+ Design
+ Hell
+ Dunkel
+ Schwarz
Hauptfarbe
Die Primärfarbe, Standard ist Schwarz.
Akzentfarbe
Die Akzentfarbe, Standard ist indigo
- auswählen
- Über
- lade Stundenplan …
- Navigationsleiste schließen
- Navigationsleiste öffnen
Buffet immer anzeigen
- Wähle deinen Studiengang aus
+
+
+ Wähle deinen Studiengang
+ lade Stundenplan …
+ auswählen
+ schließen
+ Mensa-Guthaben
+ aktuell: %1$s\n
+ letzte Abbuchung: %1$s
+
+
+ Fehler
+ Stundenplan konnte nicht geladen werden!
+
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..da9cee6
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 7f52220..92bab79 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -2,28 +2,27 @@
#000000
#000000
- #3F51B5
- #FFFFFF
-
- #FFFFFF
- #FFFFFF
- #FAFAFA
+ #3f51b5
+ #ffffff
+ #ffffff
+ #ffffff
#000000
#818181
- #FFFFFF
- #E0E0E0
- #000000
- #818181
-
#000000
#303030
#424242
+ #ffffff
+ #e0e0e0
+ #232323
+
+ #ffffff
+ #ffffff
+ #000000
+ #818181
+ #e0e0e0
- #FFFFFF
- #FFFFFF
- #424242
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index aadff31..57efb0b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -4,45 +4,62 @@
Close navigation drawer
Project Laogai
seil0@mosad.xyz
- Project Laogai
+
Home
Mensa
Timetable
Moodle
Settings
+
Meal
Today, %1$s
Tomorrow, %1$s
the Mensa is closed
No more Food this week
-
"No lecture today!"
- Error
- Could not load timetable!"
- SampleUser@stud.hs-offenburg.de
+
+ Info
+ User
+ Tap to change course
+ About
+ "This software is made by @Seil0 and is published under the terms and conditions of GPL 3. For further information visit \ngit.mosad.xyz/Seil0/ProjectLaogai \n\n© 2018-2019 seil0@mosad.xyz "
+ hso App by @Seil0
+ Version %1$s (%2$s)
+ Licenses
+ Theme
+ Light
+ Dark
+ Black
+ Primary color
+ The primary color, default is black.
+ Accent color
+ The accent color, default is indigo.
+ Always show buffet
+
+
+ Select your course
+ loading timetable …
+ select
+ close
+ Mensa credit
+ current: %1$s\n
+ last: %1$s
+
+
+
+ spinefield@stud.hs-offenburg.de
SampleCourse 3
Montag, 30.02
0.00 – 23.59
- Info
- User
- Tap to change course
- Select your course
- primary color
- The primary color, default is black.
- accent color
- The accent color, default is indigo.
- always show buffet
- select
- hso App by @Seil0
- version %1$s (%2$s)
- About
- "This software is made by @Seil0 and is published under the terms and conditions of GPL 3. For further information visit \ngit.mosad.xyz/Seil0/ProjectLaogai \n\n© 2018-2019 seil0@mosad.xyz "
- loading timetable …
+
+ Error
+ Could not load timetable!"
+
org.mosad.seil0.projectlaogai.course
org.mosad.seil0.projectlaogai.courseTTLink
org.mosad.seil0.projectlaogai.colorPrimary
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index a27a731..8981642 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,11 +1,50 @@
-
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/nfc_tech_filter.xml b/app/src/main/res/xml/nfc_tech_filter.xml
new file mode 100644
index 0000000..eb44974
--- /dev/null
+++ b/app/src/main/res/xml/nfc_tech_filter.xml
@@ -0,0 +1,6 @@
+
+
+
+ android.nfc.tech.NfcA
+
+
diff --git a/build.gradle b/build.gradle
index 3cedbfe..8d482e6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,13 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.3.31'
+ ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.4.0'
+ classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
diff --git a/fastlane/metadata/android/de-DE/full_description.txt b/fastlane/metadata/android/de-DE/full_description.txt
new file mode 100644
index 0000000..71eca07
--- /dev/null
+++ b/fastlane/metadata/android/de-DE/full_description.txt
@@ -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
diff --git a/fastlane/metadata/android/de/short_description.txt b/fastlane/metadata/android/de-DE/short_description.txt
similarity index 100%
rename from fastlane/metadata/android/de/short_description.txt
rename to fastlane/metadata/android/de-DE/short_description.txt
diff --git a/fastlane/metadata/android/de/full_description.txt b/fastlane/metadata/android/de/full_description.txt
deleted file mode 100644
index 8acd636..0000000
--- a/fastlane/metadata/android/de/full_description.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-ProjectLaogai ist eine App um den Stundenplan und das Mensa-Essen der Hochschule Offenburg anzuzeigen.
-
-Features:
-* schaue was es diese und nächste WOche in der Mensa zu Essen gibt
-* schaue dir deinen Stundenplan an
-* öffne moodle direkt in der App
-* viele lustige Bugs
diff --git a/fastlane/metadata/android/en-US/changelogs/14.txt b/fastlane/metadata/android/en-US/changelogs/14.txt
new file mode 100644
index 0000000..b3cceea
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/14.txt
@@ -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
diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
index aa3f7ce..c080ed0 100644
--- a/fastlane/metadata/android/en-US/full_description.txt
+++ b/fastlane/metadata/android/en-US/full_description.txt
@@ -3,5 +3,6 @@ ProjectLaogai is an app to access the timetable and the mensa menu of Hochschule
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
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/ProjectLaogai_Mensa_dark.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/ProjectLaogai_Mensa_dark.png
new file mode 100644
index 0000000..714d246
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/ProjectLaogai_Mensa_dark.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/ProjectLaogai_Settings_dark.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/ProjectLaogai_Settings_dark.png
new file mode 100644
index 0000000..c19082d
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/ProjectLaogai_Settings_dark.png differ
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index f6b961f..5c2d1cf 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index f59e369..7c4388a 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,5 @@
-#Thu Apr 25 17:58:09 WEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
diff --git a/gradlew b/gradlew
index cccdd3d..8e25e6c 100755
--- a/gradlew
+++ b/gradlew
@@ -1,5 +1,21 @@
#!/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
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
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.
-DEFAULT_JVM_OPTS=""
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
diff --git a/gradlew.bat b/gradlew.bat
index e95643d..24467a1 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -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
@rem ##########################################################################
@rem
@@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
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.
-set DEFAULT_JVM_OPTS=
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome