release 0.5.0

Merge branch 'develop' of Seil0/ProjectLaogai into master
This commit is contained in:
Jannik 2019-09-30 21:28:54 +02:00 committed by Gitea
commit 605bf6248d
59 changed files with 2390 additions and 712 deletions

9
.drone.yml Normal file
View File

@ -0,0 +1,9 @@
kind: pipeline
name: default
steps:
- name: assembleRelease
image: gradle:jdk8
commands:
- gradle assembleRelease

View File

@ -4,18 +4,19 @@
# ProjectLaogai "hso App" # ProjectLaogai "hso App"
ProjectLaogai is a app to access the timetable and the mensa menu of Hochschule Offenburg. ProjectLaogai is a app to access the timetable and the mensa menu of Hochschule Offenburg.
[<img src="https://f-droid.org/badge/get-it-on.png" height="75">](https://f-droid.org/packages/org.mosad.seil0.projectlaogai/) [<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="75">](https://f-droid.org/packages/org.mosad.seil0.projectlaogai/)
## Features ## Features
* check out the mensa menu of this and next week * check out the mensa menu of this and next week
* access your timetable * access your timetable
* check the current balance of your mensa card
* open moodle * open moodle
* probably some funny bugs * probably some funny bugs
## Screenshots ## Screenshots
[<img src="https://raw.githubusercontent.com/Seil0/Seil0.github.io/master/images/Project_Laogai/ProjectLaogai_HomeScreen.png" width=180>](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project_Laogai/ProjectLaogai_HomeScreen.png) [<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.png)
[<img src="https://raw.githubusercontent.com/Seil0/Seil0.github.io/master/images/Project_Laogai/ProjectLaogai_Mensa.png" width=180>](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project_Laogai/ProjectLaogai_Mensa.png) [<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.png)
[<img src="https://raw.githubusercontent.com/Seil0/Seil0.github.io/master/images/Project_Laogai/ProjectLaogai_Timetable.png" width=180>](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project_Laogai/ProjectLaogai_Timetable.png) [<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.png)
[<img src="https://raw.githubusercontent.com/Seil0/Seil0.github.io/master/images/Project_Laogai/ProjectLaogai_Settings.png" width=180>](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project_Laogai/ProjectLaogai_Settings.png) [<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.png)
[<img src="https://raw.githubusercontent.com/Seil0/Seil0.github.io/master/images/Project_Laogai/ProjectLaogai_NavDrawer.png" width=180>](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project_Laogai/ProjectLaogai_NavDrawer.png) [<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa_dark.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa_dark.png)
ProjectLaogai © 2019 mosad [www.mosad.xyz](http://www.mosad.xyz), Project by [@Seil0](https://git.mosad.xyz/Seil0) ProjectLaogai © 2019 [@Seil0](https://git.mosad.xyz/Seil0), a [mosad](http://www.mosad.xyz) Project

View File

@ -7,13 +7,13 @@ apply plugin: 'kotlin-android-extensions'
android { android {
signingConfigs { signingConfigs {
} }
compileSdkVersion 28 compileSdkVersion 29
defaultConfig { defaultConfig {
applicationId "org.mosad.seil0.projectlaogai" applicationId "org.mosad.seil0.projectlaogai"
minSdkVersion 21 minSdkVersion 23
targetSdkVersion 28 targetSdkVersion 29
versionCode 13 versionCode 14
versionName "0.4.1" versionName "0.5.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "build_time", buildTime() resValue "string", "build_time", buildTime()
setProperty("archivesBaseName", "projectlaogai-$versionName") setProperty("archivesBaseName", "projectlaogai-$versionName")
@ -25,9 +25,24 @@ android {
shrinkResources false shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
sourceSets {
main {
res.srcDirs =
[
'src/main/res/layouts/activities',
'src/main/res/layouts/dialogs',
'src/main/res/layouts/fragments',
'src/main/res/layouts',
'src/main/res'
]
}
} }
} }
@ -35,20 +50,22 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.anko:anko-commons:0.10.8' implementation 'org.jetbrains.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.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 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
implementation 'com.google.android.material:material:1.0.0' implementation 'com.google.android.material:material:1.0.0'
implementation 'com.google.code.gson:gson:2.8.5' implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.afollestad:aesthetic:1.0.0-beta05' implementation 'com.afollestad:aesthetic:1.0.0-beta05'
implementation 'com.afollestad.material-dialogs:core:2.8.1' implementation 'com.afollestad.material-dialogs:core:3.1.1'
implementation 'com.afollestad.material-dialogs:color:2.8.1' implementation 'com.afollestad.material-dialogs:color:3.1.1'
implementation 'de.psdev.licensesdialog:licensesdialog:2.1.0'
implementation 'org.apache.commons:commons-lang3:3.9'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
} }
static def buildTime() { static def buildTime() {

View File

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

View File

@ -0,0 +1,227 @@
/**
* Utils.kt
*
* Copyright (C) 2011 Eric Butler
* Copyright (C) 2019 <seil0@mosad.xyz>
*
* Authors:
* Eric Butler <eric@codebutler.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.codebutler.farebot
import android.app.Activity
import android.app.AlertDialog
import android.util.Log
import android.view.WindowManager
import com.codebutler.farebot.card.desfire.DesfireException
import com.codebutler.farebot.card.desfire.DesfireFileSettings
import com.codebutler.farebot.card.desfire.DesfireProtocol
import org.w3c.dom.Node
import java.io.StringWriter
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import kotlin.experimental.and
class Utils {
companion object {
private val TAG = Utils::class.java.name
@Suppress("unused")
fun showError(activity: Activity, ex: Exception) {
Log.e(activity.javaClass.name, ex.message, ex)
AlertDialog.Builder(activity)
.setMessage(getErrorMessage(ex))
.show()
}
@Suppress("unused")
fun showErrorAndFinish(activity: Activity, ex: Exception) {
try {
Log.e(activity.javaClass.name, getErrorMessage(ex))
ex.printStackTrace()
AlertDialog.Builder(activity)
.setMessage(getErrorMessage(ex))
.setCancelable(false)
.setPositiveButton(android.R.string.ok) { _, _ -> activity.finish() }
.show()
} catch (unused: WindowManager.BadTokenException) {
/* Ignore... happens if the activity was destroyed */
}
}
@Throws(Exception::class)
fun getHexString(b: ByteArray): String {
var result = ""
for (i in b.indices) {
result += ((b[i] and 0xff.toByte()) + 0x100).toString(16).substring(1)
}
return result
}
@Suppress("unused")
fun getHexString(b: ByteArray, defaultResult: String): String {
return try {
getHexString(b)
} catch (ex: Exception) {
defaultResult
}
}
@Suppress("unused")
fun hexStringToByteArray(s: String): ByteArray {
if (s.length % 2 != 0) {
throw IllegalArgumentException("Bad input string: $s")
}
val len = s.length
val data = ByteArray(len / 2)
var i = 0
while (i < len) {
data[i / 2] = ((Character.digit(s[i], 16) shl 4) + Character.digit(s[i + 1], 16)).toByte()
i += 2
}
return data
}
@JvmOverloads
fun byteArrayToInt(b: ByteArray, offset: Int = 0, length: Int = b.size): Int {
return byteArrayToLong(b, offset, length).toInt()
}
fun byteArrayToLong(b: ByteArray, offset: Int, length: Int): Long {
if (b.size < length)
throw IllegalArgumentException("length must be less than or equal to b.length")
var value: Long = 0
for (i in 0 until length) {
val shift = (length - 1 - i) * 8
value += ((b[i + offset].toInt() and 0x000000FF).toLong() shl shift)
}
return value
}
@Suppress("unused")
fun byteArraySlice(b: ByteArray, offset: Int, length: Int): ByteArray {
val ret = ByteArray(length)
for (i in 0 until length)
ret[i] = b[offset + i]
return ret
}
@Suppress("unused")
@Throws(Exception::class)
fun xmlNodeToString(node: Node): String {
// The amount of code required to do simple things in Java is incredible.
val source = DOMSource(node)
val stringWriter = StringWriter()
val result = StreamResult(stringWriter)
val factory = TransformerFactory.newInstance()
val transformer = factory.newTransformer()
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
transformer.uriResolver = null
transformer.transform(source, result)
return stringWriter.buffer.toString()
}
fun getErrorMessage(ex: Throwable): String {
var errorMessage: String? = ex.localizedMessage
if (errorMessage == null)
errorMessage = ex.message
if (errorMessage == null)
errorMessage = ex.toString()
if (ex.cause != null) {
var causeMessage: String? = ex.cause!!.localizedMessage
if (causeMessage == null)
causeMessage = ex.cause!!.message
if (causeMessage == null)
causeMessage = ex.cause.toString()
errorMessage += ": $causeMessage"
}
return errorMessage
}
@Suppress("unused")
fun <T> findInList(list: List<T>, matcher: Matcher<T>): T? {
for (item in list) {
if (matcher.matches(item)) {
return item
}
}
return null
}
interface Matcher<T> {
fun matches(t: T): Boolean
}
fun selectAppFile(tag: DesfireProtocol, appID: Int, fileID: Int): DesfireFileSettings? {
try {
tag.selectApp(appID)
} catch (e: DesfireException) {
Log.w(TAG, "App not found")
return null
}
return try {
tag.getFileSettings(fileID)
} catch (e: DesfireException) {
Log.w(TAG, "File not found")
null
}
}
fun arrayContains(arr: IntArray, item: Int): Boolean {
for (i in arr)
if (i == item)
return true
return false
}
@Suppress("unused")
fun containsAppFile(tag: DesfireProtocol, appID: Int, fileID: Int): Boolean {
try {
tag.selectApp(appID)
} catch (e: DesfireException) {
Log.w(TAG, "App not found")
Log.w(TAG, e)
return false
}
return try {
arrayContains(tag.fileList, fileID)
} catch (e: DesfireException) {
Log.w(TAG, "File not found")
Log.w(TAG, e)
false
}
}
}
}

View File

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

View File

@ -0,0 +1,104 @@
/**
* DesfireFile.kt
*
* Copyright (C) 2011 Eric Butler
* Copyright (C) 2019 <seil0@mosad.xyz>
*
* Authors:
* Eric Butler <eric@codebutler.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.codebutler.farebot.card.desfire
import android.os.Parcel
import android.os.Parcelable
import com.codebutler.farebot.card.desfire.DesfireFileSettings.RecordDesfireFileSettings
import org.apache.commons.lang3.ArrayUtils
open class DesfireFile private constructor(val id: Int, private val fileSettings: DesfireFileSettings?, val data: ByteArray) :
Parcelable {
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
if (this is InvalidDesfireFile) {
parcel.writeInt(1)
parcel.writeString(this.errorMessage)
} else {
parcel.writeInt(0)
parcel.writeParcelable(fileSettings, 0)
parcel.writeInt(data.size)
parcel.writeByteArray(data)
}
}
override fun describeContents(): Int {
return 0
}
class RecordDesfireFile(fileId: Int, fileSettings: DesfireFileSettings, fileData: ByteArray) :
DesfireFile(fileId, fileSettings, fileData) {
private val records: Array<DesfireRecord?>
init {
val settings = fileSettings as RecordDesfireFileSettings
val records = arrayOfNulls<DesfireRecord>(settings.curRecords)
for (i in 0 until settings.curRecords) {
val offset = settings.recordSize * i
records[i] = DesfireRecord(ArrayUtils.subarray(data, offset, offset + settings.recordSize))
}
this.records = records
}
}
class InvalidDesfireFile(fileId: Int, val errorMessage: String?) : DesfireFile(fileId, null, ByteArray(0))
companion object {
fun create(fileId: Int, fileSettings: DesfireFileSettings, fileData: ByteArray): DesfireFile {
return (fileSettings as? RecordDesfireFileSettings)?.let { RecordDesfireFile(fileId, it, fileData) }
?: DesfireFile(fileId, fileSettings, fileData)
}
@Suppress("unused")
@JvmField
val CREATOR: Parcelable.Creator<DesfireFile> = object : Parcelable.Creator<DesfireFile> {
override fun createFromParcel(source: Parcel): DesfireFile {
val fileId = source.readInt()
val isError = source.readInt() == 1
return if (!isError) {
val fileSettings =
source.readParcelable<Parcelable>(DesfireFileSettings::class.java.classLoader) as DesfireFileSettings
val dataLength = source.readInt()
val fileData = ByteArray(dataLength)
source.readByteArray(fileData)
create(fileId, fileSettings, fileData)
} else {
InvalidDesfireFile(fileId, source.readString())
}
}
override fun newArray(size: Int): Array<DesfireFile?> {
return arrayOfNulls(size)
}
}
}
}

View File

@ -0,0 +1,248 @@
/**
* DesfireFileSettings.kt
*
* Copyright (C) 2011 Eric Butler
* Copyright (C) 2019 <seil0@mosad.xyz>
*
* Authors:
* Eric Butler <eric@codebutler.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.codebutler.farebot.card.desfire
import android.os.Parcel
import android.os.Parcelable
import com.codebutler.farebot.Utils
import org.apache.commons.lang3.ArrayUtils
import java.io.ByteArrayInputStream
abstract class DesfireFileSettings : Parcelable {
private val fileType: Byte
private val commSetting: Byte
private val accessRights: ByteArray
@Suppress("unused")
val fileTypeName: String
get() {
return when (fileType) {
STANDARD_DATA_FILE -> "Standard"
BACKUP_DATA_FILE -> "Backup"
VALUE_FILE -> "Value"
LINEAR_RECORD_FILE -> "Linear Record"
CYCLIC_RECORD_FILE -> "Cyclic Record"
else -> "Unknown"
}
}
private constructor(stream: ByteArrayInputStream) {
fileType = stream.read().toByte()
commSetting = stream.read().toByte()
accessRights = ByteArray(2)
stream.read(accessRights, 0, accessRights.size)
}
private constructor(fileType: Byte, commSetting: Byte, accessRights: ByteArray) {
this.fileType = fileType
this.commSetting = commSetting
this.accessRights = accessRights
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeByte(fileType)
parcel.writeByte(commSetting)
parcel.writeInt(accessRights.size)
parcel.writeByteArray(accessRights)
}
override fun describeContents(): Int {
return 0
}
class StandardDesfireFileSettings : DesfireFileSettings {
private val fileSize: Int
internal constructor(stream: ByteArrayInputStream) : super(stream) {
val buf = ByteArray(3)
stream.read(buf, 0, buf.size)
ArrayUtils.reverse(buf)
fileSize = Utils.byteArrayToInt(buf)
}
internal constructor(
fileType: Byte,
commSetting: Byte,
accessRights: ByteArray,
fileSize: Int
) : super(fileType, commSetting, accessRights) {
this.fileSize = fileSize
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
super.writeToParcel(parcel, flags)
parcel.writeInt(fileSize)
}
}
class RecordDesfireFileSettings : DesfireFileSettings {
private val maxRecords: Int
val recordSize: Int
val curRecords: Int
constructor(stream: ByteArrayInputStream) : super(stream) {
var buf = ByteArray(3)
stream.read(buf, 0, buf.size)
ArrayUtils.reverse(buf)
recordSize = Utils.byteArrayToInt(buf)
buf = ByteArray(3)
stream.read(buf, 0, buf.size)
ArrayUtils.reverse(buf)
maxRecords = Utils.byteArrayToInt(buf)
buf = ByteArray(3)
stream.read(buf, 0, buf.size)
ArrayUtils.reverse(buf)
curRecords = Utils.byteArrayToInt(buf)
}
internal constructor(
fileType: Byte,
commSetting: Byte,
accessRights: ByteArray,
recordSize: Int,
maxRecords: Int,
curRecords: Int
) : super(fileType, commSetting, accessRights) {
this.recordSize = recordSize
this.maxRecords = maxRecords
this.curRecords = curRecords
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
super.writeToParcel(parcel, flags)
parcel.writeInt(recordSize)
parcel.writeInt(maxRecords)
parcel.writeInt(curRecords)
}
}
class ValueDesfireFileSettings(stream: ByteArrayInputStream) : DesfireFileSettings(stream) {
private val lowerLimit: Int
private val upperLimit: Int
val value: Int
private val limitedCreditEnabled: Byte
init {
var buf = ByteArray(4)
stream.read(buf, 0, buf.size)
ArrayUtils.reverse(buf)
lowerLimit = Utils.byteArrayToInt(buf)
buf = ByteArray(4)
stream.read(buf, 0, buf.size)
ArrayUtils.reverse(buf)
upperLimit = Utils.byteArrayToInt(buf)
buf = ByteArray(4)
stream.read(buf, 0, buf.size)
ArrayUtils.reverse(buf)
value = Utils.byteArrayToInt(buf)
buf = ByteArray(1)
stream.read(buf, 0, buf.size)
limitedCreditEnabled = buf[0]
//http://www.skyetek.com/docs/m2/desfire.pdf
//http://neteril.org/files/M075031_desfire.pdf
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
super.writeToParcel(parcel, flags)
parcel.writeInt(lowerLimit)
parcel.writeInt(upperLimit)
parcel.writeInt(value)
parcel.writeByte(limitedCreditEnabled)
}
}
class UnsupportedDesfireFileSettings(fileType: Byte) :
DesfireFileSettings(fileType, java.lang.Byte.MIN_VALUE, ByteArray(0))
companion object {
/* DesfireFile Types */
internal const val STANDARD_DATA_FILE = 0x00.toByte()
internal const val BACKUP_DATA_FILE = 0x01.toByte()
internal const val VALUE_FILE = 0x02.toByte()
internal const val LINEAR_RECORD_FILE = 0x03.toByte()
internal const val CYCLIC_RECORD_FILE = 0x04.toByte()
@Throws(DesfireException::class)
fun create(data: ByteArray): DesfireFileSettings {
val fileType = data[0]
val stream = ByteArrayInputStream(data)
return if (fileType == STANDARD_DATA_FILE || fileType == BACKUP_DATA_FILE)
StandardDesfireFileSettings(stream)
else if (fileType == LINEAR_RECORD_FILE || fileType == CYCLIC_RECORD_FILE)
RecordDesfireFileSettings(stream)
else if (fileType == VALUE_FILE)
ValueDesfireFileSettings(stream)
else
throw DesfireException("Unknown file type: " + Integer.toHexString(fileType.toInt()))
}
@Suppress("unused")
@JvmField
val CREATOR: Parcelable.Creator<DesfireFileSettings> = object : Parcelable.Creator<DesfireFileSettings> {
override fun createFromParcel(source: Parcel): DesfireFileSettings {
val fileType = source.readByte()
val commSetting = source.readByte()
val accessRights = ByteArray(source.readInt())
source.readByteArray(accessRights)
if (fileType == STANDARD_DATA_FILE || fileType == BACKUP_DATA_FILE) {
val fileSize = source.readInt()
return StandardDesfireFileSettings(fileType, commSetting, accessRights, fileSize)
} else if (fileType == LINEAR_RECORD_FILE || fileType == CYCLIC_RECORD_FILE) {
val recordSize = source.readInt()
val maxRecords = source.readInt()
val curRecords = source.readInt()
return RecordDesfireFileSettings(
fileType,
commSetting,
accessRights,
recordSize,
maxRecords,
curRecords
)
} else {
return UnsupportedDesfireFileSettings(fileType)
}
}
override fun newArray(size: Int): Array<DesfireFileSettings?> {
return arrayOfNulls(size)
}
}
}
}

View File

@ -0,0 +1,181 @@
/**
* DesfireManufacturingData.kt
*
* Copyright (C) 2011 Eric Butler
* Copyright (C) 2019 <seil0@mosad.xyz>
*
* Authors:
* Eric Butler <eric@codebutler.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.codebutler.farebot.card.desfire
import android.os.Parcel
import android.os.Parcelable
import com.codebutler.farebot.Utils
import org.w3c.dom.Element
import java.io.ByteArrayInputStream
class DesfireManufacturingData : Parcelable {
private val hwVendorID: Int
private val hwType: Int
private val hwSubType: Int
private val hwMajorVersion: Int
private val hwMinorVersion: Int
private val hwStorageSize: Int
private val hwProtocol: Int
private val swVendorID: Int
private val swType: Int
private val swSubType: Int
private val swMajorVersion: Int
private val swMinorVersion: Int
private val swStorageSize: Int
private val swProtocol: Int
private val uid: Int
private val batchNo: Int
private val weekProd: Int
private val yearProd: Int
constructor(data: ByteArray) {
val stream = ByteArrayInputStream(data)
hwVendorID = stream.read()
hwType = stream.read()
hwSubType = stream.read()
hwMajorVersion = stream.read()
hwMinorVersion = stream.read()
hwStorageSize = stream.read()
hwProtocol = stream.read()
swVendorID = stream.read()
swType = stream.read()
swSubType = stream.read()
swMajorVersion = stream.read()
swMinorVersion = stream.read()
swStorageSize = stream.read()
swProtocol = stream.read()
// FIXME: This has fewer digits than what's contained in EXTRA_ID, why?
var buf = ByteArray(7)
stream.read(buf, 0, buf.size)
uid = Utils.byteArrayToInt(buf)
// FIXME: This is returning a negative number. Probably is unsigned.
buf = ByteArray(5)
stream.read(buf, 0, buf.size)
batchNo = Utils.byteArrayToInt(buf)
// FIXME: These numbers aren't making sense.
weekProd = stream.read()
yearProd = stream.read()
}
private constructor(element: Element) {
hwVendorID = Integer.parseInt(element.getElementsByTagName("hw-vendor-id").item(0).textContent)
hwType = Integer.parseInt(element.getElementsByTagName("hw-type").item(0).textContent)
hwSubType = Integer.parseInt(element.getElementsByTagName("hw-sub-type").item(0).textContent)
hwMajorVersion = Integer.parseInt(element.getElementsByTagName("hw-major-version").item(0).textContent)
hwMinorVersion = Integer.parseInt(element.getElementsByTagName("hw-minor-version").item(0).textContent)
hwStorageSize = Integer.parseInt(element.getElementsByTagName("hw-storage-size").item(0).textContent)
hwProtocol = Integer.parseInt(element.getElementsByTagName("hw-protocol").item(0).textContent)
swVendorID = Integer.parseInt(element.getElementsByTagName("sw-vendor-id").item(0).textContent)
swType = Integer.parseInt(element.getElementsByTagName("sw-type").item(0).textContent)
swSubType = Integer.parseInt(element.getElementsByTagName("sw-sub-type").item(0).textContent)
swMajorVersion = Integer.parseInt(element.getElementsByTagName("sw-major-version").item(0).textContent)
swMinorVersion = Integer.parseInt(element.getElementsByTagName("sw-minor-version").item(0).textContent)
swStorageSize = Integer.parseInt(element.getElementsByTagName("sw-storage-size").item(0).textContent)
swProtocol = Integer.parseInt(element.getElementsByTagName("sw-protocol").item(0).textContent)
uid = Integer.parseInt(element.getElementsByTagName("uid").item(0).textContent)
batchNo = Integer.parseInt(element.getElementsByTagName("batch-no").item(0).textContent)
weekProd = Integer.parseInt(element.getElementsByTagName("week-prod").item(0).textContent)
yearProd = Integer.parseInt(element.getElementsByTagName("year-prod").item(0).textContent)
}
private constructor(parcel: Parcel) {
hwVendorID = parcel.readInt()
hwType = parcel.readInt()
hwSubType = parcel.readInt()
hwMajorVersion = parcel.readInt()
hwMinorVersion = parcel.readInt()
hwStorageSize = parcel.readInt()
hwProtocol = parcel.readInt()
swVendorID = parcel.readInt()
swType = parcel.readInt()
swSubType = parcel.readInt()
swMajorVersion = parcel.readInt()
swMinorVersion = parcel.readInt()
swStorageSize = parcel.readInt()
swProtocol = parcel.readInt()
uid = parcel.readInt()
batchNo = parcel.readInt()
weekProd = parcel.readInt()
yearProd = parcel.readInt()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(hwVendorID)
parcel.writeInt(hwType)
parcel.writeInt(hwSubType)
parcel.writeInt(hwMajorVersion)
parcel.writeInt(hwMinorVersion)
parcel.writeInt(hwStorageSize)
parcel.writeInt(hwProtocol)
parcel.writeInt(swVendorID)
parcel.writeInt(swType)
parcel.writeInt(swSubType)
parcel.writeInt(swMajorVersion)
parcel.writeInt(swMinorVersion)
parcel.writeInt(swStorageSize)
parcel.writeInt(swProtocol)
parcel.writeInt(uid)
parcel.writeInt(batchNo)
parcel.writeInt(weekProd)
parcel.writeInt(yearProd)
}
override fun describeContents(): Int {
return 0
}
companion object {
@Suppress("unused")
fun fromXml(element: Element): DesfireManufacturingData {
return DesfireManufacturingData(element)
}
@Suppress("unused")
@JvmField
val CREATOR: Parcelable.Creator<DesfireManufacturingData> =
object : Parcelable.Creator<DesfireManufacturingData> {
override fun createFromParcel(source: Parcel): DesfireManufacturingData {
return DesfireManufacturingData(source)
}
override fun newArray(size: Int): Array<DesfireManufacturingData?> {
return arrayOfNulls(size)
}
}
}
}

View File

@ -0,0 +1,214 @@
/**
* DesfireProtocol.kt
*
* Copyright (C) 2011 Eric Butler
* Copyright (C) 2019 <seil0@mosad.xyz>
*
* Authors:
* Eric Butler <eric@codebutler.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.codebutler.farebot.card.desfire
import android.nfc.tech.IsoDep
import com.codebutler.farebot.Utils
import java.io.ByteArrayOutputStream
import java.io.IOException
import org.apache.commons.lang3.ArrayUtils
import kotlin.experimental.and
class DesfireProtocol(private val mTagTech: IsoDep) {
@Suppress("unused")
val manufacturingData: DesfireManufacturingData
@Throws(DesfireException::class)
get() {
val respBuffer = sendRequest(GET_MANUFACTURING_DATA)
if (respBuffer.size != 28)
throw DesfireException("Invalid response")
return DesfireManufacturingData(respBuffer)
}
@Suppress("unused")
val appList: IntArray
@Throws(DesfireException::class)
get() {
val appDirBuf = sendRequest(GET_APPLICATION_DIRECTORY)
val appIds = IntArray(appDirBuf.size / 3)
var app = 0
while (app < appDirBuf.size) {
val appId = ByteArray(3)
System.arraycopy(appDirBuf, app, appId, 0, 3)
appIds[app / 3] = Utils.byteArrayToInt(appId)
app += 3
}
return appIds
}
val fileList: IntArray
@Throws(DesfireException::class)
get() {
val buf = sendRequest(GET_FILES)
val fileIds = IntArray(buf.size)
for (x in buf.indices) {
fileIds[x] = buf[x].toInt()
}
return fileIds
}
@Throws(DesfireException::class)
fun selectApp(appId: Int) {
val appIdBuff = ByteArray(3)
appIdBuff[0] = (appId and 0xFF0000 shr 16).toByte()
appIdBuff[1] = (appId and 0xFF00 shr 8).toByte()
appIdBuff[2] = (appId and 0xFF).toByte()
sendRequest(SELECT_APPLICATION, appIdBuff)
}
@Throws(DesfireException::class)
fun getFileSettings(fileNo: Int): DesfireFileSettings {
val data = sendRequest(GET_FILE_SETTINGS, byteArrayOf(fileNo.toByte()))
return DesfireFileSettings.create(data)
}
@Suppress("unused")
@Throws(DesfireException::class)
fun readFile(fileNo: Int): ByteArray {
return sendRequest(
READ_DATA,
byteArrayOf(
fileNo.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte()
)
)
}
@Suppress("unused")
@Throws(DesfireException::class)
fun readRecord(fileNum: Int): ByteArray {
return sendRequest(
READ_RECORD,
byteArrayOf(
fileNum.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte(),
0x0.toByte()
)
)
}
@Throws(DesfireException::class)
fun readValue(fileNum: Int): Int {
val buf = sendRequest(READ_VALUE, byteArrayOf(fileNum.toByte()))
ArrayUtils.reverse(buf)
return Utils.byteArrayToInt(buf)
}
@Throws(DesfireException::class)
private fun sendRequest(command: Byte, parameters: ByteArray? = null): ByteArray {
val output = ByteArrayOutputStream()
var recvBuffer: ByteArray
try {
recvBuffer = mTagTech.transceive(wrapMessage(command, parameters))
} catch (e: IOException) {
throw DesfireException(e)
}
while (true) {
if (recvBuffer[recvBuffer.size - 2] != 0x91.toByte())
throw DesfireException("Invalid response")
output.write(recvBuffer, 0, recvBuffer.size - 2)
val status = recvBuffer[recvBuffer.size - 1]
if (status == OPERATION_OK) {
break
} else if (status == ADDITIONAL_FRAME) {
try {
recvBuffer = mTagTech.transceive(wrapMessage(GET_ADDITIONAL_FRAME, null))
} catch (e: IOException) {
throw DesfireException(e)
}
} else if (status == PERMISSION_DENIED) {
throw DesfireException("Permission denied")
} else {
throw DesfireException("Unknown status code: " + Integer.toHexString((status and 0xFF.toByte()).toInt()))
}
}
return output.toByteArray()
}
@Throws(DesfireException::class)
private fun wrapMessage(command: Byte, parameters: ByteArray?): ByteArray {
val stream = ByteArrayOutputStream()
stream.write(0x90.toByte().toInt())
stream.write(command.toInt())
stream.write(0x00.toByte().toInt())
stream.write(0x00.toByte().toInt())
if (parameters != null) {
stream.write(parameters.size.toByte().toInt())
try {
stream.write(parameters)
} catch (e: IOException) {
throw DesfireException(e)
}
}
stream.write(0x00.toByte().toInt())
return stream.toByteArray()
}
companion object {
/* Commands */
internal const val GET_MANUFACTURING_DATA = 0x60.toByte()
internal const val GET_APPLICATION_DIRECTORY = 0x6A.toByte()
internal const val GET_ADDITIONAL_FRAME = 0xAF.toByte()
internal const val SELECT_APPLICATION = 0x5A.toByte()
internal const val READ_DATA = 0xBD.toByte()
internal const val READ_RECORD = 0xBB.toByte()
internal const val READ_VALUE = 0x6C.toByte()
internal const val GET_FILES = 0x6F.toByte()
internal const val GET_FILE_SETTINGS = 0xF5.toByte()
/* Status codes */
internal const val OPERATION_OK = 0x00.toByte()
internal const val PERMISSION_DENIED = 0x9D.toByte()
internal const val ADDITIONAL_FRAME = 0xAF.toByte()
}
}

View File

@ -0,0 +1,26 @@
/*
* DesfireRecord.kt
*
* Copyright (C) 2011 Eric Butler
* Copyright (C) 2019 <seil0@mosad.xyz>
*
* Authors:
* Eric Butler <eric@codebutler.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.codebutler.farebot.card.desfire
class DesfireRecord(val data: ByteArray)

View File

@ -22,7 +22,13 @@
package org.mosad.seil0.projectlaogai 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.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@ -32,27 +38,29 @@ import androidx.core.view.GravityCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.FragmentTransaction
import com.afollestad.aesthetic.Aesthetic import com.afollestad.aesthetic.Aesthetic
import com.afollestad.aesthetic.NavigationViewMode
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.app_bar_main.* import kotlinx.android.synthetic.main.app_bar_main.*
import org.mosad.seil0.projectlaogai.controller.CacheController 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
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorAccent 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.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 org.mosad.seil0.projectlaogai.fragments.*
import java.sql.Date
import java.util.*
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
// TODO save the current fragment to show it when the app is restarted
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
private var activeFragment: Fragment = HomeFragment() // the currently active fragment, home at the start private var activeFragment: Fragment = HomeFragment() // the currently active fragment, home at the start
private lateinit var adapter: NfcAdapter
private lateinit var pendingIntent: PendingIntent
private lateinit var intentFiltersArray: Array<IntentFilter>
private lateinit var techListsArray: Array<Array<String>>
private var useNFC = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Aesthetic.attach(this) Aesthetic.attach(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -61,26 +69,8 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
// load mensa, timetable and color // load mensa, timetable and color
load() load()
initAesthetic()
// If we haven't set any defaults, do that now initForegroundDispatch()
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()
}
}
//init home fragment //init home fragment
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction() val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
@ -94,22 +84,40 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
toggle.syncState() toggle.syncState()
nav_view.setNavigationItemSelectedListener(this) 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() { override fun onResume() {
super.onResume() super.onResume()
Aesthetic.resume(this) Aesthetic.resume(this)
if(useNFC)
adapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray)
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
Aesthetic.pause(this) Aesthetic.pause(this)
if(useNFC)
adapter.disableForegroundDispatch(this)
} }
override fun onBackPressed() { override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) { if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START) drawer_layout.closeDrawer(GravityCompat.START)
} else { } else {
// TODO only call on double tap
super.onBackPressed() super.onBackPressed()
} }
} }
@ -132,22 +140,13 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
override fun onNavigationItemSelected(item: MenuItem): Boolean { override fun onNavigationItemSelected(item: MenuItem): Boolean {
// Handle navigation view item clicks here. // Handle navigation view item clicks here.
when (item.itemId) { activeFragment = when(item.itemId) {
R.id.nav_home -> { R.id.nav_home -> HomeFragment()
activeFragment = HomeFragment() R.id.nav_mensa -> MensaFragment()
} R.id.nav_timetable -> TimeTableFragment()
R.id.nav_mensa -> { R.id.nav_moodle -> MoodleFragment()
activeFragment = MensaFragment() R.id.nav_settings -> SettingsFragment()
} else -> HomeFragment()
R.id.nav_timetable -> {
activeFragment = TimeTableFragment()
}
R.id.nav_moodle -> {
activeFragment = MoodleFragment()
}
R.id.nav_settings -> {
activeFragment = SettingsFragment()
}
} }
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction() val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
@ -155,6 +154,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
fragmentTransaction.commit() fragmentTransaction.commit()
drawer_layout.closeDrawer(GravityCompat.START) drawer_layout.closeDrawer(GravityCompat.START)
return true return true
} }
@ -163,56 +163,48 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
*/ */
private fun load() { private fun load() {
val startupTime = measureTimeMillis { val startupTime = measureTimeMillis {
// load the settings PreferencesController.load(this) // load the settings, must be finished before doing anything else
PreferencesController.load(this) // this must be finished before doing anything else CacheController(this) // load the cache
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)
}
} }
println("startup completed in $startupTime ms") 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
)
}
}
} }

View File

@ -24,27 +24,91 @@ package org.mosad.seil0.projectlaogai.controller
import android.content.Context import android.content.Context
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonParser import com.google.gson.JsonParser
import org.mosad.seil0.projectlaogai.hsoparser.Course import com.google.gson.reflect.TypeToken
import org.mosad.seil0.projectlaogai.hsoparser.MensaWeek import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
import org.mosad.seil0.projectlaogai.hsoparser.TimetableWeek import org.mosad.seil0.projectlaogai.hsoparser.*
import java.io.BufferedReader import java.io.BufferedReader
import java.io.File import java.io.File
import java.io.FileReader import java.io.FileReader
import com.google.gson.reflect.TypeToken import java.util.*
import kotlin.collections.ArrayList
class CacheController(cont: Context) { class CacheController(cont: Context) {
private val context = cont 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 { companion object {
var coursesList = ArrayList<Course>() var coursesList = ArrayList<Course>()
var mensaCurrentWeek = MensaWeek() var timetables = ArrayList<TimetableCourseWeek>()
var mensaNextWeek = MensaWeek() var mensaMenu = MensaMenu()
var timetables = ArrayList<TimetableWeek>()
/** /**
* 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<List<Course>>() {}.type)
}
/**
* get the MensaMenu object from the cached json,
* if cache is empty create the cache file
*/ */
fun readMensa(context: Context) { fun readMensa(context: Context) {
val file = File(context.filesDir, "mensa.json") val file = File(context.filesDir, "mensa.json")
@ -54,16 +118,11 @@ class CacheController(cont: Context) {
TCoRAPIController.getMensa(context).get() TCoRAPIController.getMensa(context).get()
} }
val fileReader = FileReader(file) val fileReader = FileReader(file)
val bufferedReader = BufferedReader(fileReader) val bufferedReader = BufferedReader(fileReader)
val mensaObject = JsonParser().parse(bufferedReader.readLine()).asJsonObject val mensaObject = JsonParser().parse(bufferedReader.readLine()).asJsonObject
val currentWeek = mensaObject.getAsJsonObject("currentWeek") mensaMenu = GsonBuilder().create().fromJson(mensaObject, MensaMenu().javaClass)
val nextWeek = mensaObject.getAsJsonObject("nextWeek")
mensaCurrentWeek = Gson().fromJson(currentWeek, MensaWeek().javaClass)
mensaNextWeek = Gson().fromJson(nextWeek, MensaWeek().javaClass)
} }
/** /**
@ -83,10 +142,10 @@ class CacheController(cont: Context) {
val timetableObject = JsonParser().parse(bufferedReader.readLine()).asJsonObject val timetableObject = JsonParser().parse(bufferedReader.readLine()).asJsonObject
// make sure you add the single weeks in the exact order! // make sure you add the single weeks in the exact order!
if (timetables.size == week) { if (timetables.size > week) {
timetables.add(Gson().fromJson(timetableObject.getAsJsonObject("timetable"), TimetableWeek().javaClass)) timetables[week] = (Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass))
} else if (timetables.size >= week) { } else {
timetables[week] = Gson().fromJson(timetableObject.getAsJsonObject("timetable"), TimetableWeek().javaClass) timetables.add(Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass))
} }
} }
} }
@ -96,28 +155,10 @@ class CacheController(cont: Context) {
* @param courseName the course name (e.g AI1) * @param courseName the course name (e.g AI1)
*/ */
fun readStartCache(courseName: String) { fun readStartCache(courseName: String) {
readCoursesList() readCoursesList(context)
readMensa(context) readMensa(context)
readTimetable(courseName, 0, context) readTimetable(courseName, 0, context)
readTimetable(courseName, 1, 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<List<Course>>() {}.type)
}
} }

View File

@ -0,0 +1,105 @@
/**
* ProjectLaogai
*
* Copyright 2019 <seil0@mosad.xyz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
package org.mosad.seil0.projectlaogai.controller
import android.content.Context
import android.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
}
}
}

View File

@ -42,6 +42,7 @@ class PreferencesController {
var cColorAccent: Int = Color.parseColor("#3F51B5") 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 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 cShowBuffet = true
var oGiants = false
// the save function // the save function
fun save(context: Context) { fun save(context: Context) {

View File

@ -23,76 +23,98 @@
package org.mosad.seil0.projectlaogai.controller package org.mosad.seil0.projectlaogai.controller
import android.content.Context import android.content.Context
import android.util.Log
import org.jetbrains.anko.doAsync import org.jetbrains.anko.doAsync
import org.json.JSONObject import org.json.JSONObject
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.coursesCacheTime 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.mensaCacheTime
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.timetableCacheTime 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 java.net.URL
import kotlin.Exception
class TCoRAPIController(cont: Context) { class TCoRAPIController {
private val context = cont
companion object { 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) * get the json object from tcor api and write it as file (cache)
*/ */
fun getMensa(context: Context) = doAsync { fun getMensa(context: Context) = doAsync {
val url = URL("https://tcor.mosad.xyz/mensamenu") try {
val file = File(context.filesDir, "mensa.json") val url = URL("$tcorBaseURL/mensamenu")
val file = File(context.filesDir, "mensa.json")
// read data from the API // read data from the API
val mensaObject = JSONObject(url.readText()) //JSONObject(inReader.readLine()) val mensaObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
// write the json object to a file // write the json object to a file
val writer = BufferedWriter(FileWriter(file)) val writer = BufferedWriter(FileWriter(file))
writer.write(mensaObject.toString()) writer.write(mensaObject.toString())
writer.close() 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) * get the json object from tcor api and write it as file (cache)
*/ */
fun getTimetable(courseName: String, week: Int, context: Context) = doAsync { fun getTimetable(courseName: String, week: Int, context: Context) = doAsync {
val url = URL("https://tcor.mosad.xyz/timetable?courseName=$courseName&week=$week") try {
val file = File(context.filesDir, "timetable-$courseName-$week.json") val url = URL("$tcorBaseURL/timetable?courseName=$courseName&week=$week")
val file = File(context.filesDir, "timetable-$courseName-$week.json")
// read data from the API // read data from the API
val mensaObject = JSONObject(url.readText()) //JSONObject(inReader.readLine()) val mensaObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
// write the json object to a file // write the json object to a file
val writer = BufferedWriter(FileWriter(file)) val writer = BufferedWriter(FileWriter(file))
writer.write(mensaObject.toString()) writer.write(mensaObject.toString())
writer.close() 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)
}
} }

View File

@ -24,25 +24,22 @@ package org.mosad.seil0.projectlaogai.fragments
import android.graphics.Typeface import android.graphics.Typeface
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.afollestad.materialdialogs.MaterialDialog
import kotlinx.android.synthetic.main.fragment_home.* import kotlinx.android.synthetic.main.fragment_home.*
import org.jetbrains.anko.doAsync import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread import org.jetbrains.anko.uiThread
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaCurrentWeek import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaMenu
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.timetables 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.Meal
import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar
import org.mosad.seil0.projectlaogai.hsoparser.TimetableDay import org.mosad.seil0.projectlaogai.hsoparser.TimetableDay
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.LessonLinearLayout
import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -53,6 +50,7 @@ import java.util.*
*/ */
class HomeFragment : Fragment() { class HomeFragment : Fragment() {
private val className = "HomeFragment"
private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault()) private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault())
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@ -60,7 +58,7 @@ class HomeFragment : Fragment() {
val view: View = inflater.inflate(R.layout.fragment_home, container, false) val view: View = inflater.inflate(R.layout.fragment_home, container, false)
addMensaMenu().get() addMensaMenu().get()
addTimeTable() addTimeTable().get()
// Inflate the layout for this fragment // Inflate the layout for this fragment
return view return view
@ -77,38 +75,41 @@ class HomeFragment : Fragment() {
uiThread { uiThread {
if (cal.get(Calendar.HOUR_OF_DAY) < 15) { if (isAdded) {
dayMeals = mensaCurrentWeek.days[NotRetardedCalendar().getDayOfWeekIndex()].meals if (cal.get(Calendar.HOUR_OF_DAY) < 15) {
mensaCardView.setDayHeading(resources.getString(R.string.today_date, formatter.format(cal.time))) dayMeals = mensaMenu.currentWeek.days[NotRetardedCalendar.getDayOfWeekIndex()].meals
} else { mensaCardView.setDayHeading(activity!!.resources.getString(R.string.today_date, formatter.format(cal.time)))
dayMeals = mensaCurrentWeek.days[NotRetardedCalendar().getTomorrowWeekIndex()].meals } else {
cal.add(Calendar.DATE, 1) dayMeals = mensaMenu.currentWeek.days[NotRetardedCalendar.getTomorrowWeekIndex()].meals
mensaCardView.setDayHeading(resources.getString(R.string.tomorrow_date, formatter.format(cal.time))) cal.add(Calendar.DATE, 1)
} mensaCardView.setDayHeading(activity!!.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
}
} }
} else { if (dayMeals.size >= 2) {
mensaCardView.getLinLayoutDay().addView(getNoCard(resources.getString(R.string.mensa_closed))) // 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 * add the current timetable to the home screen
*/ */
private fun addTimeTable() = doAsync { private fun addTimeTable() = doAsync {
val dayIndex = NotRetardedCalendar().getDayOfWeekIndex()
val cal = Calendar.getInstance()
var dayCardView: DayCardView
uiThread { uiThread {
if (timetables.isNotEmpty() && dayIndex < 6) { if (isAdded && timetables.isNotEmpty()) {
try {
// first check the current day val dayCardView = findNextDay(NotRetardedCalendar.getDayOfWeekIndex())
dayCardView = addDayTimetable(timetables[0].days[dayIndex]) linLayout_Home.addView(dayCardView)
dayCardView.setDayHeading(resources.getString(R.string.today_date, formatter.format(cal.time))) } catch (ex: Exception) {
Log.e(className, "could not load timetable", ex) // TODO send feedback
// 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
} }
} }
} }
helpLesson.disableDivider()
return dayCardView
} }
/** /**
* find the next day with a lesson * 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 * @param startDayIndex the day index you want to start searching
* @return a DayCardView with all lessons added * @return a DayCardView with all lessons added
*/ */
private fun findNextDay(startWeekIndex: Int, startDayIndex: Int) : DayCardView{ private fun findNextDay(startDayIndex: Int) : DayCardView {
val cal = Calendar.getInstance() val dayCardView = DayCardView(context!!)
var dayCardView = DayCardView(context!!)
var dayTimetable: TimetableDay? = null var dayTimetable: TimetableDay? = null
var dayIndexSearch = startDayIndex var dayIndexSearch = startDayIndex
var weekIndexSearch = startWeekIndex var weekIndexSearch = 0
loop@ while (dayTimetable == null && weekIndexSearch <= timetables.size) {
for (i in (dayIndexSearch) ..5) {
dayTimetable = timetables[weekIndexSearch].days[i]
cal.add(Calendar.DATE, 1)
// add the timetable to the card, if it contains at least one lesson break! while (dayTimetable == null && weekIndexSearch < timetables.size) {
dayCardView = addDayTimetable(dayTimetable) for (dayIndex in dayIndexSearch..5) {
dayCardView.setDayHeading(formatter.format(cal.time)) 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) if (dayCardView.getLinLayoutDay().childCount > 1)
return dayCardView return dayCardView
} }
dayIndexSearch = 0
weekIndexSearch++ 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.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 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 return dayCardView
@ -222,7 +176,6 @@ class HomeFragment : Fragment() {
private fun getNoCard(text: String): TextView { private fun getNoCard(text: String): TextView {
val noLesson = TextView(context) val noLesson = TextView(context)
noLesson.text = text noLesson.text = text
noLesson.setTextColor(ContextCompat.getColor(context!!, R.color.textPrimary))
noLesson.textSize = 18.0F noLesson.textSize = 18.0F
noLesson.setTypeface(null, Typeface.BOLD) noLesson.setTypeface(null, Typeface.BOLD)
noLesson.textAlignment = View.TEXT_ALIGNMENT_CENTER noLesson.textAlignment = View.TEXT_ALIGNMENT_CENTER

View File

@ -30,9 +30,9 @@ import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_mensa.* import kotlinx.android.synthetic.main.fragment_mensa.*
import org.jetbrains.anko.doAsync import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread 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
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.mensaNextWeek
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cShowBuffet import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cShowBuffet
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.hsoparser.MensaWeek import org.mosad.seil0.projectlaogai.hsoparser.MensaWeek
@ -48,17 +48,17 @@ class MensaFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(org.mosad.seil0.projectlaogai.R.layout.fragment_mensa, container, false) val view: View = inflater.inflate(R.layout.fragment_mensa, container, false)
// init actions // init actions
refreshAction() refreshAction()
// add the current week (week starts on sunday) // add the current week (week starts on sunday)
val dayCurrent = if(NotRetardedCalendar().getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar().getDayOfWeekIndex() val dayCurrent = if(NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
addWeek(mensaCurrentWeek, dayCurrent).get() addWeek(mensaMenu.currentWeek, dayCurrent).get()
// add the next week // 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? // 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() linLayout_Mensa.removeAllViews()
// add the refreshed menus // add the refreshed menus
val dayCurrent = if (NotRetardedCalendar().getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar().getDayOfWeekIndex() val dayCurrent = if (NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
addWeek(mensaCurrentWeek, dayCurrent).get() addWeek(mensaMenu.currentWeek, dayCurrent).get()
// add the next week // add the next week
addWeek(mensaNextWeek, 0) addWeek(mensaMenu.nextWeek, 0)
refreshLayout_Mensa.isRefreshing = false refreshLayout_Mensa.isRefreshing = false
} }

View File

@ -24,6 +24,7 @@ package org.mosad.seil0.projectlaogai.fragments
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -35,22 +36,25 @@ import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.color.colorChooser import com.afollestad.materialdialogs.color.colorChooser
import com.afollestad.materialdialogs.customview.customView import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.list.listItems import com.afollestad.materialdialogs.list.listItems
import com.afollestad.materialdialogs.list.listItemsSingleChoice
import de.psdev.licensesdialog.LicensesDialog
import kotlinx.android.synthetic.main.fragment_settings.* import kotlinx.android.synthetic.main.fragment_settings.*
import org.jetbrains.anko.doAsync import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread import org.jetbrains.anko.uiThread
import org.mosad.seil0.projectlaogai.BuildConfig import org.mosad.seil0.projectlaogai.BuildConfig
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.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
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorAccent 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.cColorPrimary
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cShowBuffet 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.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.hsoparser.DataTypes import org.mosad.seil0.projectlaogai.hsoparser.DataTypes
import java.util.* import java.util.*
/** /**
* The settings controller class * The settings controller class
* contains all needed parts to display and the settings screen * contains all needed parts to display and the settings screen
@ -60,6 +64,8 @@ class SettingsFragment : Fragment() {
private lateinit var linLayoutUser: LinearLayout private lateinit var linLayoutUser: LinearLayout
private lateinit var linLayoutCourse: LinearLayout private lateinit var linLayoutCourse: LinearLayout
private lateinit var linLayoutAbout: LinearLayout private lateinit var linLayoutAbout: LinearLayout
private lateinit var linLayoutLicence: LinearLayout
private lateinit var linLayoutTheme: LinearLayout
private lateinit var linLayoutPrimaryColor: LinearLayout private lateinit var linLayoutPrimaryColor: LinearLayout
private lateinit var linLayoutAccentColor: LinearLayout private lateinit var linLayoutAccentColor: LinearLayout
private lateinit var switchBuffet: Switch private lateinit var switchBuffet: Switch
@ -71,6 +77,8 @@ class SettingsFragment : Fragment() {
linLayoutUser = view.findViewById(R.id.linLayout_User) linLayoutUser = view.findViewById(R.id.linLayout_User)
linLayoutCourse = view.findViewById(R.id.linLayout_Course) linLayoutCourse = view.findViewById(R.id.linLayout_Course)
linLayoutAbout = view.findViewById(R.id.linLayout_About) 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) linLayoutPrimaryColor = view.findViewById(R.id.linLayout_PrimaryColor)
linLayoutAccentColor = view.findViewById(R.id.linLayout_AccentColor) linLayoutAccentColor = view.findViewById(R.id.linLayout_AccentColor)
switchBuffet = view.findViewById(R.id.switch_buffet) switchBuffet = view.findViewById(R.id.switch_buffet)
@ -84,9 +92,28 @@ class SettingsFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// initialize the settings gui
txtView_Course.text = cCourse.courseName txtView_Course.text = cCourse.courseName
txtView_AboutDesc.text = resources.getString(R.string.about_version, BuildConfig.VERSION_NAME, getString(R.string.build_time)) txtView_AboutDesc.text = resources.getString(R.string.about_version, BuildConfig.VERSION_NAME, getString(R.string.build_time))
switch_buffet.isChecked = cShowBuffet // init switch switch_buffet.isChecked = cShowBuffet // init switch
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 // open a new dialog
} }
linLayoutUser.setOnLongClickListener {
PreferencesController.oGiants = true // enable easter egg
return@setOnLongClickListener true
}
linLayoutCourse.setOnClickListener { linLayoutCourse.setOnClickListener {
selectCourse(context!!) selectCourse(context!!)
txtView_Course.text = cCourse.courseName // update txtView
} }
linLayoutAbout.setOnClickListener { linLayoutAbout.setOnClickListener {
@ -110,6 +141,52 @@ class SettingsFragment : Fragment() {
.show() .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 { linLayoutPrimaryColor.setOnClickListener {
// open a new color chooser dialog // open a new color chooser dialog
MaterialDialog(context!!) MaterialDialog(context!!)
@ -184,6 +261,7 @@ class SettingsFragment : Fragment() {
uiThread { uiThread {
dialog.dismiss() dialog.dismiss()
txtView_Course.text = cCourse.courseName // update txtView
} }
} }

View File

@ -26,8 +26,10 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ScrollView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.android.synthetic.main.fragment_timetable.* import kotlinx.android.synthetic.main.fragment_timetable.*
import org.jetbrains.anko.doAsync import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread 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.CacheController.Companion.timetables
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController 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.NotRetardedCalendar
import org.mosad.seil0.projectlaogai.hsoparser.TimetableWeek import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.*
import java.text.SimpleDateFormat
import java.util.*
/** /**
* The timetable controller class * The timetable controller class
@ -49,16 +47,19 @@ import java.util.*
*/ */
class TimeTableFragment : Fragment() { 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? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(R.layout.fragment_timetable, container, false) val view: View = inflater.inflate(R.layout.fragment_timetable, container, false)
scrollViewTimetable = view.findViewById(R.id.scrollView_Timetable)
faBtnAddLesson = view.findViewById(R.id.faBtnAddLesson)
// init actions // 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() addInitWeeks()
} else { } else {
MaterialDialog(context!!) MaterialDialog(context!!)
@ -70,70 +71,64 @@ class TimeTableFragment : Fragment() {
return view 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 * add the current and next weeks lessons
*/ */
private fun addInitWeeks() = doAsync { private fun addInitWeeks() = doAsync {
val dayIndex = NotRetardedCalendar().getDayOfWeekIndex() val currentDayIndex = NotRetardedCalendar.getDayOfWeekIndex()
val calendar = Calendar.getInstance()
// add current week addWeek(currentDayIndex, 5, 0).get() // add current week
addWeek(dayIndex, 5, timetables[0], calendar).get() addWeek(0, currentDayIndex - 1, 1) // add next week
// add next week
addWeek(0, dayIndex - 1, timetables[1], calendar)
} }
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 { uiThread {
for (day in dayStart..dayEnd) { for (dayIndex in startDayIndex..dayEndIndex) {
var helpLesson = LessonLinearLayout(context)
val dayCardView = DayCardView(context!!) val dayCardView = DayCardView(context!!)
dayCardView.setDayHeading(formatter.format(calendar.time))
// for each timeslot of the day // some wired calendar magic, calculate the correct date to be shown ((timetable week - current week * 7) + (dayIndex - dayIndex of current week)
for ((tsIndex, timeslot) in timetable.days[day].timeslots.withIndex()) { val daysToAdd =(timetableMeta.weekNumberYear - NotRetardedCalendar.getWeekOfYear()) * 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex())
dayCardView.addTimetableDay(timetable.days[dayIndex], daysToAdd)
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)
// if there are no lessons don't show the dayCardView // if there are no lessons don't show the dayCardView
if (dayCardView.getLinLayoutDay().childCount > 1) if (dayCardView.getLinLayoutDay().childCount > 1)
linLayout_Timetable.addView(dayCardView) 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() linLayout_Timetable.removeAllViews()
// add the refreshed timetables // add the refreshed timetables
val dayIndex = NotRetardedCalendar().getDayOfWeekIndex() val dayIndex = NotRetardedCalendar.getDayOfWeekIndex()
val calendar = Calendar.getInstance()
// add current week // add current week
addWeek(dayIndex, 5, timetables[0], calendar).get() addWeek(dayIndex, 5, 0).get()
// add next week // add next week
addWeek(0, dayIndex - 1, timetables[1], calendar) addWeek(0, dayIndex - 1, 1)
refreshLayout_Timetable.isRefreshing = false refreshLayout_Timetable.isRefreshing = false
} }

View File

@ -27,7 +27,8 @@ import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class DataTypes { class DataTypes {
val times = arrayOf("8.00 - 9.30", "9.45 - 11.15" ,"11.35 - 13.05", "14.00 -15.30", "15.45 - 17.15", "17.30 - 19.00") val times =
arrayOf("8.00 - 9.30", "9.45 - 11.15", "11.35 - 13.05", "14.00 -15.30", "15.45 - 17.15", "17.30 - 19.00")
val primaryColors = intArrayOf( val primaryColors = intArrayOf(
Color.parseColor("#E53935"), Color.parseColor("#E53935"),
@ -69,7 +70,7 @@ class DataTypes {
Color.parseColor("#FF9100"), Color.parseColor("#FF9100"),
Color.parseColor("#FF3D00"), Color.parseColor("#FF3D00"),
Color.parseColor("#000000") Color.parseColor("#000000")
) )
init { init {
// do something // do something
@ -82,45 +83,70 @@ class DataTypes {
} }
class NotRetardedCalendar { class NotRetardedCalendar {
private val calendar = Calendar.getInstance()!! companion object {
private val calendar = Calendar.getInstance()
fun getDayOfWeekIndex(): Int { fun getDayOfWeekIndex(): Int {
return when(calendar.get(Calendar.DAY_OF_WEEK)) { return when (calendar.get(Calendar.DAY_OF_WEEK)) {
Calendar.MONDAY -> 0 Calendar.MONDAY -> 0
Calendar.TUESDAY -> 1 Calendar.TUESDAY -> 1
Calendar.WEDNESDAY -> 2 Calendar.WEDNESDAY -> 2
Calendar.THURSDAY -> 3 Calendar.THURSDAY -> 3
Calendar.FRIDAY -> 4 Calendar.FRIDAY -> 4
Calendar.SATURDAY -> 5 Calendar.SATURDAY -> 5
Calendar.SUNDAY -> 6 Calendar.SUNDAY -> 6
else -> 7 else -> 7
}
} }
}
fun getTomorrowWeekIndex(): Int { fun getTomorrowWeekIndex(): Int {
return when(calendar.get(Calendar.DAY_OF_WEEK)) { return when (calendar.get(Calendar.DAY_OF_WEEK)) {
Calendar.MONDAY -> 1 Calendar.MONDAY -> 1
Calendar.TUESDAY -> 2 Calendar.TUESDAY -> 2
Calendar.WEDNESDAY -> 3 Calendar.WEDNESDAY -> 3
Calendar.THURSDAY -> 4 Calendar.THURSDAY -> 4
Calendar.FRIDAY -> 5 Calendar.FRIDAY -> 5
Calendar.SATURDAY -> 6 Calendar.SATURDAY -> 6
Calendar.SUNDAY -> 0 Calendar.SUNDAY -> 0
else -> 7 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 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<String>, val additives: String) data class Meal(val day: String, val heading: String, val parts: ArrayList<String>, val additives: String)
data class Meals(val meals: ArrayList<Meal>) data class Meals(val meals: ArrayList<Meal>)
data class MensaWeek(val days: Array<Meals> = Array(7) { Meals(ArrayList()) }) data class MensaWeek(val days: Array<Meals> = 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<ArrayList<Lesson>> = Array(6) { ArrayList<Lesson>()}) data class MensaMenu(val meta: MensaMeta = MensaMeta(), val currentWeek: MensaWeek = MensaWeek(), val nextWeek: MensaWeek = MensaWeek())
data class TimetableWeek(val days: Array<TimetableDay> = Array(6) { TimetableDay() }) // data classes for the timetable part
data class Lesson(
val lessonSubject: String,
val lessonTeacher: String,
val lessonRoom: String,
val lessonRemark: String
)
data class TimetableDay(val timeslots: Array<ArrayList<Lesson>> = Array(6) { ArrayList<Lesson>() })
data class TimetableWeek(val days: Array<TimetableDay> = Array(6) { TimetableDay() })
data class TimetableCourseMeta(val updateTime: Long = 0, val courseName: String = "", val weekIndex: Int = 0, val weekNumberYear: Int = 0, val link: String = "")
data class TimetableCourseWeek(val meta: TimetableCourseMeta = TimetableCourseMeta(), var timetable: TimetableWeek = TimetableWeek())

View File

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

View File

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

View File

@ -1,185 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.SettingsFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:background="@android:color/background_light">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="9dp" android:id="@+id/cardView_Info" app:cardElevation="5dp"
app:cardUseCompatPadding="true">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text="@string/info"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_Info"
android:typeface="sans"
android:textSize="18sp" android:textStyle="bold" android:fontFamily="sans-serif"
android:layout_margin="7dp" android:textColor="?colorAccent"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:padding="7dp" android:id="@+id/linLayout_User">
<TextView
android:text="@string/sample_user"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_User"
android:textSize="16sp" android:textStyle="bold"
android:textColor="@android:color/primary_text_light"/>
<TextView
android:text="@string/user"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_UserDesc"/>
</LinearLayout>
<View
android:id="@+id/divider2"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_margin="7dp"
android:id="@+id/linLayout_Course">
<TextView
android:text="@string/sample_course"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_Course"
android:textSize="16sp" android:textColor="@android:color/primary_text_light"
android:textStyle="bold"/>
<TextView
android:text="@string/course_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_CourseDesc"/>
</LinearLayout>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_margin="7dp"
android:id="@+id/linLayout_About">
<TextView
android:text="@string/about_txtView"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_About"
android:textStyle="bold" android:textSize="16sp"
android:textColor="@android:color/primary_text_light"/>
<TextView
android:text="@string/about_version"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_AboutDesc"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/cardView_Settings" app:cardElevation="5dp"
app:cardUseCompatPadding="true">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/linLayout_Settings" android:padding="7dp">
<TextView
android:text="@string/settings"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_Settings"
android:typeface="sans"
android:textSize="18sp" android:textColor="?colorAccent"
android:textStyle="bold" android:paddingTop="3dp"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|end"
android:clickable="true" android:id="@+id/linLayout_PrimaryColor" android:focusable="true">
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:text="@string/primary_color"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_PrimaryColor"
android:textSize="16sp" android:textStyle="bold"
android:textColor="@android:color/primary_text_light"/>
<TextView
android:text="@string/primary_color_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_PrimaryColorDesc"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_margin="7dp"
android:gravity="end">
<View
android:layout_width="40dp"
android:layout_height="40dp" android:id="@+id/view_PrimaryColor"
android:background="?colorPrimary"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|end"
android:clickable="true" android:id="@+id/linLayout_AccentColor" android:focusable="true">
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:text="@string/accent_color"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_AccentColor"
android:textSize="16sp" android:textStyle="bold"
android:textColor="@android:color/primary_text_light"/>
<TextView
android:text="@string/accent_color_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_AccentColorDesc"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_margin="7dp"
android:gravity="end">
<View
android:layout_width="40dp"
android:layout_height="40dp" android:id="@+id/view_AccentColor"
android:background="?colorAccent"/>
</LinearLayout>
</LinearLayout>
<Switch
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/switch_buffet"
android:textSize="16sp"
android:textColor="@android:color/primary_text_light" android:textStyle="bold"
android:text="@string/show_buffet"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

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

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" android:id="@+id/linLayout_Meal" android:padding="7dp">
<TextView
android:text="@string/meal"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_MealHeading" android:textSize="18sp"
android:textColor="@color/textPrimary" android:textAlignment="center" android:paddingBottom="5dp"
android:textStyle="bold"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/txtView_Meal" android:textSize="15sp"
android:textColor="@color/textPrimary" android:textAlignment="center"/>
<View
android:id="@+id/divider_meal"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
/>
</LinearLayout>

View File

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

View File

@ -2,8 +2,10 @@
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" app:cardUseCompatPadding="true" android:layout_height="wrap_content"
app:cardElevation="5dp" app:cardBackgroundColor="@color/themeSecondary" app:cardUseCompatPadding="true"
app:cardElevation="5dp"
app:cardBackgroundColor="?themeSecondary"
> >
<LinearLayout <LinearLayout

View File

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

View File

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

View File

@ -19,7 +19,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="@dimen/nav_header_vertical_spacing" android:paddingTop="@dimen/nav_header_vertical_spacing"
app:srcCompat="@mipmap/ic_laogai_icon" app:srcCompat="@mipmap/ic_laogai_icon"
android:contentDescription="@string/nav_header_desc" android:contentDescription="@string/app_name"
android:id="@+id/imageView"/> android:id="@+id/imageView"/>
<TextView <TextView

View File

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

View File

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

View File

@ -3,7 +3,8 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".fragments.HomeFragment"> tools:context=".fragments.HomeFragment"
android:background="?themePrimary">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -3,7 +3,8 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".fragments.MensaFragment"> tools:context=".fragments.MensaFragment"
android:background="?themePrimary">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -12,13 +13,13 @@
<ScrollView <ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:id="@+id/scrollView_Mensa">
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:animateLayoutChanges="false" android:animateLayoutChanges="false"
android:background="@color/themePrimary"
android:id="@+id/linLayout_Mensa"/> android:id="@+id/linLayout_Mensa"/>
</ScrollView> </ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

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

View File

@ -3,7 +3,8 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".fragments.TimeTableFragment"> tools:context=".fragments.TimeTableFragment"
android:background="?themePrimary">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -12,14 +13,20 @@
<ScrollView <ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent" android:id="@+id/scrollView_Timetable">
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:animateLayoutChanges="false" android:animateLayoutChanges="false"
android:background="@color/themePrimary"
android:id="@+id/linLayout_Timetable"/> android:id="@+id/linLayout_Timetable"/>
</ScrollView> </ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:src="@drawable/icon_custom_black"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true" android:id="@+id/faBtnAddLesson" android:elevation="7dp"
android:visibility="gone" android:layout_gravity="bottom|center|end" android:layout_marginEnd="11dp"
android:layout_marginBottom="11dp" android:focusable="true"/>
</FrameLayout> </FrameLayout>

View File

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

View File

@ -1,30 +1,50 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="navigation_drawer_close">Navigationsleiste schließen</string>
<string name="navigation_drawer_open">Navigationsleiste öffnen</string>
<!-- nav-view -->
<string name="home">Home</string> <string name="home">Home</string>
<string name="mensa">Mensa</string> <string name="mensa">Mensa</string>
<string name="timetable">Stundenplan</string> <string name="timetable">Stundenplan</string>
<string name="moodle">Moodle</string> <string name="moodle">Moodle</string>
<string name="settings">Einstellungen</string> <string name="settings">Einstellungen</string>
<!-- fragment_home -->
<string name="meal">Essen</string> <string name="meal">Essen</string>
<string name="today_date">Heute, %1$s</string> <string name="today_date">Heute, %1$s</string>
<string name="tomorrow_date">Morgen, %1$s</string> <string name="tomorrow_date">Morgen, %1$s</string>
<string name="mensa_closed">keine Essensausgabe</string> <string name="mensa_closed">keine Essensausgabe</string>
<string name="no_more_food">Diese Woche keine weitere Essensausgabe</string> <string name="no_more_food">Diese Woche keine weitere Essensausgabe</string>
<string name="no_lesson_today">heute keine Vorlesung!</string> <string name="no_lesson_today">heute keine Vorlesung!</string>
<string name="error">Fehler</string>
<string name="timetable_error">Stundenplan konnte nicht geladen werden!</string> <!-- fragment_settings -->
<string name="info">Info</string> <string name="info">Info</string>
<string name="user">Benutzer</string> <string name="user">Benutzer</string>
<string name="course_desc">Tippen, um den Kurs zu ändern</string> <string name="course_desc">Tippen, um den Kurs zu ändern</string>
<string name="about_dialog_heading">Über</string>
<string name="licenses">Lizenzen</string>
<string name="theme">Design</string>
<string name="themeLight">Hell</string>
<string name="themeDark">Dunkel</string>
<string name="themeBlack">Schwarz</string>
<string name="primary_color">Hauptfarbe</string> <string name="primary_color">Hauptfarbe</string>
<string name="primary_color_desc">Die Primärfarbe, Standard ist Schwarz.</string> <string name="primary_color_desc">Die Primärfarbe, Standard ist Schwarz.</string>
<string name="accent_color">Akzentfarbe</string> <string name="accent_color">Akzentfarbe</string>
<string name="accent_color_desc">Die Akzentfarbe, Standard ist indigo</string> <string name="accent_color_desc">Die Akzentfarbe, Standard ist indigo</string>
<string name="select">auswählen</string>
<string name="about_dialog_heading">Über</string>
<string name="loading_timetable">lade Stundenplan …</string>
<string name="navigation_drawer_close">Navigationsleiste schließen</string>
<string name="navigation_drawer_open">Navigationsleiste öffnen</string>
<string name="show_buffet">Buffet immer anzeigen</string> <string name="show_buffet">Buffet immer anzeigen</string>
<string name="select_course">Wähle deinen Studiengang aus</string>
<!-- dialogs -->
<string name="select_course">Wähle deinen Studiengang</string>
<string name="loading_timetable">lade Stundenplan …</string>
<string name="select">auswählen</string>
<string name="close">schließen</string>
<string name="mensa_credit">Mensa-Guthaben</string>
<string name="mensa_current">aktuell: %1$s\n</string>
<string name="mensa_last">letzte Abbuchung: %1$s</string>
<!-- errors -->
<string name="error">Fehler</string>
<string name="timetable_error">Stundenplan konnte nicht geladen werden!</string>
</resources> </resources>

View File

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

View File

@ -2,28 +2,27 @@
<resources> <resources>
<color name="colorPrimary">#000000</color> <color name="colorPrimary">#000000</color>
<color name="colorPrimaryDark">#000000</color> <color name="colorPrimaryDark">#000000</color>
<color name="colorAccent">#3F51B5</color> <color name="colorAccent">#3f51b5</color>
<color name="ic_launcher_background">#FFFFFF</color> <color name="ic_launcher_background">#ffffff</color>
<color name="themePrimary">#FFFFFF</color>
<color name="themeSecondary">#FFFFFF</color>
<color name="themeTertiary">#FAFAFA</color> <!-- TODO find a better color -->
<color name="themePrimary">#ffffff</color>
<color name="themeSecondary">#ffffff</color>
<color name="textPrimary">#000000</color> <color name="textPrimary">#000000</color>
<color name="textSecondary">#818181</color> <color name="textSecondary">#818181</color>
<!--theme color section, not the working colors--> <!--theme color section, not the working colors-->
<color name="textPrimaryDark">#FFFFFF</color>
<color name="textSecondaryDark">#E0E0E0</color>
<color name="textPrimaryLight">#000000</color>
<color name="textSecondaryLight">#818181</color>
<color name="themePrimaryDark">#000000</color> <color name="themePrimaryDark">#000000</color>
<color name="themeSecondaryDark">#303030</color> <color name="themeSecondaryDark">#303030</color>
<color name="themeTertiaryDark">#424242</color> <color name="themeTertiaryDark">#424242</color>
<color name="textPrimaryDark">#ffffff</color>
<color name="textSecondaryDark">#e0e0e0</color>
<color name="dividerDark">#232323</color>
<color name="themePrimaryLight">#ffffff</color>
<color name="themeSecondaryLight">#ffffff</color>
<color name="textPrimaryLight">#000000</color>
<color name="textSecondaryLight">#818181</color>
<color name="dividerLight">#e0e0e0</color>
<color name="themePrimaryLight">#FFFFFF</color>
<color name="themeSecondaryLight">#FFFFFF</color>
<color name="themeTertiaryLight">#424242</color> <!-- TODO find a better color -->
</resources> </resources>

View File

@ -4,45 +4,62 @@
<string name="navigation_drawer_close">Close navigation drawer</string> <string name="navigation_drawer_close">Close navigation drawer</string>
<string name="nav_header_title" translatable="false">Project Laogai</string> <string name="nav_header_title" translatable="false">Project Laogai</string>
<string name="nav_header_subtitle" translatable="false">seil0@mosad.xyz</string> <string name="nav_header_subtitle" translatable="false">seil0@mosad.xyz</string>
<string name="nav_header_desc" translatable="false">Project Laogai</string>
<!-- nav-view -->
<string name="home">Home</string> <string name="home">Home</string>
<string name="mensa">Mensa</string> <string name="mensa">Mensa</string>
<string name="timetable">Timetable</string> <string name="timetable">Timetable</string>
<string name="moodle">Moodle</string> <string name="moodle">Moodle</string>
<string name="settings">Settings</string> <string name="settings">Settings</string>
<!-- fragment_home -->
<string name="meal">Meal</string> <string name="meal">Meal</string>
<string name="today_date">Today, %1$s</string> <string name="today_date">Today, %1$s</string>
<string name="tomorrow_date">Tomorrow, %1$s</string> <string name="tomorrow_date">Tomorrow, %1$s</string>
<string name="mensa_closed">the Mensa is closed</string> <string name="mensa_closed">the Mensa is closed</string>
<string name="no_more_food">No more Food this week</string> <string name="no_more_food">No more Food this week</string>
<string name="no_lesson_today">"No lecture today!"</string> <string name="no_lesson_today">"No lecture today!"</string>
<string name="error">Error</string>
<string name="timetable_error">Could not load timetable!"</string>
<string name="sample_user" translatable="false">SampleUser@stud.hs-offenburg.de</string> <!-- fragment_settings -->
<string name="info">Info</string>
<string name="user">User</string>
<string name="course_desc">Tap to change course</string>
<string name="about_dialog_heading">About</string>
<string name="about_dialog_text" translatable="false">"This software is made by @Seil0 and is published under the terms and conditions of GPL 3. For further information visit \ngit.mosad.xyz/Seil0/ProjectLaogai \n\n© 2018-2019 seil0@mosad.xyz "</string>
<string name="about_txtView" translatable="false">hso App by @Seil0</string>
<string name="about_version" translatable="false">Version %1$s (%2$s)</string>
<string name="licenses">Licenses</string>
<string name="theme">Theme</string>
<string name="themeLight">Light</string>
<string name="themeDark">Dark</string>
<string name="themeBlack">Black</string>
<string name="primary_color">Primary color</string>
<string name="primary_color_desc">The primary color, default is black.</string>
<string name="accent_color">Accent color</string>
<string name="accent_color_desc">The accent color, default is indigo.</string>
<string name="show_buffet">Always show buffet</string>
<!-- dialogs -->
<string name="select_course">Select your course</string>
<string name="loading_timetable">loading timetable …</string>
<string name="select">select</string>
<string name="close">close</string>
<string name="mensa_credit">Mensa credit</string>
<string name="mensa_current">current: %1$s\n</string>
<string name="mensa_last">last: %1$s</string>
<!-- sample strings -->
<string name="sample_user" translatable="false">spinefield@stud.hs-offenburg.de</string>
<string name="sample_course" translatable="false">SampleCourse 3</string> <string name="sample_course" translatable="false">SampleCourse 3</string>
<string name="sample_date" translatable="false">Montag, 30.02</string> <string name="sample_date" translatable="false">Montag, 30.02</string>
<string name="a_time" translatable="false">0.00 23.59</string> <string name="a_time" translatable="false">0.00 23.59</string>
<string name="info">Info</string> <!-- errors -->
<string name="user">User</string> <string name="error">Error</string>
<string name="course_desc">Tap to change course</string> <string name="timetable_error">Could not load timetable!"</string>
<string name="select_course">Select your course</string>
<string name="primary_color">primary color</string>
<string name="primary_color_desc">The primary color, default is black.</string>
<string name="accent_color">accent color</string>
<string name="accent_color_desc">The accent color, default is indigo.</string>
<string name="show_buffet">always show buffet</string>
<string name="select">select</string>
<string name="about_txtView" translatable="false">hso App by @Seil0</string>
<string name="about_version" translatable="false">version %1$s (%2$s)</string>
<string name="about_dialog_heading">About</string>
<string name="about_dialog_text" translatable="false">"This software is made by @Seil0 and is published under the terms and conditions of GPL 3. For further information visit \ngit.mosad.xyz/Seil0/ProjectLaogai \n\n© 2018-2019 seil0@mosad.xyz "</string>
<string name="loading_timetable">loading timetable …</string>
<!-- save keys -->
<string name="save_key_course" translatable="false">org.mosad.seil0.projectlaogai.course</string> <string name="save_key_course" translatable="false">org.mosad.seil0.projectlaogai.course</string>
<string name="save_key_courseTTLink" translatable="false">org.mosad.seil0.projectlaogai.courseTTLink</string> <string name="save_key_courseTTLink" translatable="false">org.mosad.seil0.projectlaogai.courseTTLink</string>
<string name="save_key_colorPrimary" translatable="false">org.mosad.seil0.projectlaogai.colorPrimary</string> <string name="save_key_colorPrimary" translatable="false">org.mosad.seil0.projectlaogai.colorPrimary</string>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,5 +3,6 @@ ProjectLaogai is an app to access the timetable and the mensa menu of Hochschule
Features: Features:
* check out the mensa menu of this and next week * check out the mensa menu of this and next week
* access your timetable * access your timetable
* check the current balance of your mensa card
* open moodle * open moodle
* probably some funny bugs * probably some funny bugs

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

View File

@ -1,6 +1,5 @@
#Thu Apr 25 17:58:09 WEST 2019
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip

18
gradlew vendored
View File

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

18
gradlew.bat vendored
View File

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