Compare commits
164 Commits
Author | SHA1 | Date | |
---|---|---|---|
28520bee74 | |||
0c6a486dd9 | |||
ec15c79b63 | |||
4592d1984f | |||
aae74cf9db | |||
e19b0db39d | |||
23fd52b0c5 | |||
62a62049c3 | |||
849698779b | |||
a42a92a3c5 | |||
be30e2f968 | |||
c629b2aec2 | |||
99ba87c3f6 | |||
953b4825a9 | |||
2807843d25 | |||
638c321798 | |||
42d938b0bc | |||
9d7504bbaf | |||
8ecfe8f120 | |||
ff0c4ad1a7 | |||
ea0caea91e | |||
1f660bdd37 | |||
be43d87b1a | |||
dd5c7b3fb8 | |||
fb3dab6dc3 | |||
bcfdb83d14 | |||
8c55366ec0 | |||
6f68fbb73c | |||
297dd77e79 | |||
6a70f26807 | |||
f6b00a8d81 | |||
69ce9fef5a | |||
2d753851c0 | |||
6c0624c793 | |||
7779296345 | |||
3e2ef0521e | |||
bdfeb46faf | |||
18ca435764 | |||
948f330ebe | |||
72e9efb9e7 | |||
bea1b47396 | |||
34a68ff75d | |||
1ba3f1fa87 | |||
48544aef2f | |||
bc09e33147 | |||
6ec9b9f5e2 | |||
a19173b499 | |||
85dc3181fd | |||
e46c234b6c | |||
faa07966da | |||
ccc0f0f2bc | |||
e15bf95b85 | |||
01677c04ef | |||
6e9c63d3d4 | |||
c343735b57 | |||
9c5274dc06 | |||
b186a2e96e | |||
2cb4b72369 | |||
2d497d1a96 | |||
9d2de3fcb3 | |||
bed3f5d978 | |||
8f5a4dd1b3 | |||
9907d083e9 | |||
d4860b2a32 | |||
5ec2b53bce | |||
701a351e6e | |||
5cad924b26 | |||
605bf6248d | |||
d5adc4df51 | |||
c23454f081 | |||
b240beacc9 | |||
aa69d2242f | |||
6e6c9f71a0 | |||
889c673c5d | |||
522f31e077 | |||
b9ca18044c | |||
bd49e482e2 | |||
2f5b2a6579 | |||
4ce37b3dcf | |||
7550fcfd22 | |||
a23f7c9212 | |||
75168c6688 | |||
be916a74ab | |||
3e909ab68f | |||
5f2b3aa496 | |||
6cc1671a52 | |||
b4ed1ca927 | |||
e74c307566 | |||
733b675ffa | |||
3a0a31f781 | |||
f52151fbf1 | |||
e8cae7e807 | |||
c6ac19bfae | |||
a7abd48726 | |||
9a22d9b737 | |||
2cc2ecf952 | |||
ea70aedbd0 | |||
98b3adbf3b | |||
a055f59cc8 | |||
01db6bb451 | |||
4838c9406c | |||
23195f94e0 | |||
5e766ec126 | |||
9c1f95ca25 | |||
e99127a63a | |||
f7fa96b1ae | |||
ac37bce145 | |||
6fd82254e0 | |||
0dd8ba9475 | |||
770e9a255d | |||
4589badbfc | |||
74f75bfbde | |||
a00c651bfd | |||
77757ad9ca | |||
fe111ac56b | |||
4971d0b1b8 | |||
2bd86ff6bb | |||
dbaf496a79 | |||
77326a8ed6 | |||
9fc897e194 | |||
f9ac219ec6 | |||
a87e57c80e | |||
ff1d353cae | |||
6e5cf29eaa | |||
ee388bf5a5 | |||
8aaf8e3647 | |||
d645d75bbf | |||
f97491addd | |||
e08790aaa4 | |||
750a808fbe | |||
e51a80b78d | |||
a4eaea2918 | |||
cd3136715f | |||
15f1386b6e | |||
3bace6c155 | |||
61716f8eb4 | |||
a1410f7b80 | |||
953185425b | |||
a6d14044c2 | |||
f3b7ff066d | |||
040eb26dcf | |||
1a5d2b6561 | |||
92b60d660c | |||
a4eceeddc9 | |||
3e3a80442e | |||
5a1a07cd42 | |||
dbfdaffe99 | |||
58eb217ab7 | |||
24f920c05f | |||
6301308d76 | |||
8e205fa889 | |||
e9bdcee443 | |||
b214cfccb2 | |||
404ddd58b8 | |||
137ff7df0c | |||
b4071d7456 | |||
5e6e6cfde6 | |||
ffeb09a37f | |||
75a457312d | |||
87bf614d28 | |||
e69354af96 | |||
ec74a8e4f8 | |||
b49d16b1a1 | |||
70059b4b0c |
9
.drone.yml
Normal file
@ -0,0 +1,9 @@
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: assembleRelease
|
||||
image: nextcloudci/android10:android-56
|
||||
commands:
|
||||
- ./gradlew assembleRelease
|
||||
|
29
README.md
@ -1,12 +1,27 @@
|
||||
# ProjectLaogai "hso App"
|
||||
Some info about the app ...
|
||||
[![Build Status](https://drone.mosad.xyz/api/badges/Seil0/ProjectLaogai/status.svg)](https://drone.mosad.xyz/Seil0/ProjectLaogai)
|
||||
![fdroid version](https://img.shields.io/f-droid/v/org.mosad.seil0.projectlaogai.svg?style=flat-square)
|
||||
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg?style=flat-square)](https://www.gnu.org/licenses/gpl-3.0)
|
||||
|
||||
# Project Laogai
|
||||
Project Laogai is an app to access the timetable, grades (qispos) and the canteen menu of Hochschule Offenburg. Laogai uses the TCoR-API fot timetables and the canteen menu, wich makes acessing them super fast. To get the grades from Qispos, Laogai will ask for your login data, wich are stored encrypted on your device.
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="75">](https://f-droid.org/packages/org.mosad.seil0.projectlaogai/)
|
||||
[<img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" height="75">](https://play.google.com/store/apps/details?id=org.mosad.seil0.projectlaogai)
|
||||
|
||||
## Features
|
||||
* look up what you can eat in the mensa
|
||||
* check your timetable
|
||||
* probably many many bugs
|
||||
* have your grades displayed directly in the app
|
||||
* show the timetable of your course
|
||||
* take a look at the canteen menu for the current and next week
|
||||
* check the current balance of your mensa card
|
||||
* open moodle directly in the app
|
||||
|
||||
Please report bugs and issues to support@mosad.xyz
|
||||
|
||||
## Screenshots
|
||||
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.png)
|
||||
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.png)
|
||||
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.png)
|
||||
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.png)
|
||||
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa_dark.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa_dark.png)
|
||||
|
||||
|
||||
ProjectLaogai © 2018 mosad www.mosad.xyz, Project by [@Seil0](https://git.mosad.xyz/Seil0)
|
||||
ProjectLaogai © 2019-2020 [@Seil0](https://git.mosad.xyz/Seil0), a [mosad.xyz](http://www.mosad.xyz) Project
|
||||
|
@ -1,43 +1,75 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
android {
|
||||
signingConfigs {
|
||||
}
|
||||
compileSdkVersion 28
|
||||
compileSdkVersion 29
|
||||
defaultConfig {
|
||||
applicationId "org.mosad.seil0.projectlaogai"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 9
|
||||
versionName "0.3.1"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 29
|
||||
versionCode 6000 // 0006000
|
||||
versionName "0.6.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resValue "string", "build_time", buildTime()
|
||||
setProperty("archivesBaseName", "projectlaogai-$versionName")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
res.srcDirs =
|
||||
[
|
||||
'src/main/res/layouts/activities',
|
||||
'src/main/res/layouts/dialogs',
|
||||
'src/main/res/layouts/fragments',
|
||||
'src/main/res/layouts',
|
||||
'src/main/res'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'
|
||||
implementation 'androidx.core:core:1.3.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
|
||||
implementation 'org.jsoup:jsoup:1.11.3'
|
||||
implementation 'org.jetbrains.anko:anko-commons:0.10.8'
|
||||
implementation 'com.afollestad.material-dialogs:core:2.0.0-rc1'
|
||||
implementation 'com.afollestad.material-dialogs:color:2.0.0-rc1'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'androidx.security:security-crypto:1.1.0-alpha02'
|
||||
implementation 'com.google.android.material:material:1.2.0'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'com.afollestad:aesthetic:1.0.0-beta05'
|
||||
implementation 'com.afollestad.material-dialogs:core:3.3.0'
|
||||
implementation 'com.afollestad.material-dialogs:color:3.3.0'
|
||||
implementation 'com.afollestad.material-dialogs:bottomsheets:3.3.0'
|
||||
implementation 'de.psdev.licensesdialog:licensesdialog:2.1.0'
|
||||
|
||||
implementation 'org.apache.commons:commons-lang3:3.11'
|
||||
implementation 'org.jsoup:jsoup:1.13.1'
|
||||
|
||||
testImplementation 'junit:junit:4.13'
|
||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
}
|
||||
|
||||
static def buildTime() {
|
||||
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
|
||||
}
|
9
app/proguard-rules.pro
vendored
@ -15,7 +15,16 @@
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
-dontobfuscate
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
-keep class org.mosad.seil0.projectlaogai.util.** { <fields>; }
|
||||
|
||||
#Gson
|
||||
-keepattributes Signature
|
||||
-dontwarn sun.misc.**
|
||||
|
||||
#misc
|
||||
-dontwarn java.lang.instrument.ClassFileTransformer
|
||||
|
@ -1,7 +1,7 @@
|
||||
package org.mosad.seil0.projectlaogai
|
||||
|
||||
import androidx.test.InstrumentationRegistry
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@ -18,7 +18,7 @@ class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getTargetContext()
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().context
|
||||
assertEquals("org.mosad.seil0.projectlaogai", appContext.packageName)
|
||||
}
|
||||
}
|
||||
|
@ -2,31 +2,55 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.mosad.seil0.projectlaogai">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup_descriptor"
|
||||
android:icon="@mipmap/ic_laogai_icon"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_laogai_icon"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
android:theme="@style/AppTheme.Light">
|
||||
|
||||
<activity
|
||||
android:name=".SplashActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/SplashTheme"
|
||||
android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".OnboardingActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.Light"
|
||||
android:screenOrientation="portrait"
|
||||
android:launchMode="singleTop">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:screenOrientation="portrait">
|
||||
android:theme="@style/AppTheme.Light"
|
||||
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>
|
||||
</application>
|
||||
|
||||
|
BIN
app/src/main/ic_laogai_icon-playstore.png
Normal file
After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 39 KiB |
227
app/src/main/java/com/codebutler/farebot/Utils.kt
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2018 <seil0@mosad.xyz>
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -22,50 +22,63 @@
|
||||
|
||||
package org.mosad.seil0.projectlaogai
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.NfcManager
|
||||
import android.nfc.tech.NfcA
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.aesthetic.Aesthetic
|
||||
import com.afollestad.aesthetic.NavigationViewMode
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.android.synthetic.main.app_bar_main.*
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
import org.mosad.seil0.projectlaogai.controller.NFCMensaCard
|
||||
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorAccent
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorPrimary
|
||||
import org.mosad.seil0.projectlaogai.fragments.*
|
||||
import org.mosad.seil0.projectlaogai.hsoparser.*
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
/**
|
||||
* TODO save the current fragment to show it when the app is restarted
|
||||
*/
|
||||
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
|
||||
|
||||
private val mensaParser = MensaParser()
|
||||
private val timeTableParser = TimeTableParser()
|
||||
private var activeFragment: Fragment = HomeFragment() // the currently active fragment, home at the start
|
||||
private val className = "MainActivity"
|
||||
|
||||
private var weekMenus = ArrayList<Meal>()
|
||||
private var courseTTLinkList = ArrayList<CourseTTLink>()
|
||||
private var timeTableCurrentWeek = arrayOf<Array<Lesson>>()
|
||||
private var timeTableNextWeek = arrayOf<Array<Lesson>>()
|
||||
|
||||
private lateinit var course: CourseTTLink
|
||||
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?) {
|
||||
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
|
||||
|
||||
Aesthetic.attach(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
|
||||
// load mensa and timetable
|
||||
// load mensa, timetable and color
|
||||
load()
|
||||
|
||||
//init home fragment TODO make a abstract fragment class
|
||||
val homeFragment = HomeFragment()
|
||||
homeFragment.setMainActivity(this)
|
||||
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
|
||||
fragmentTransaction.replace(R.id.fragment_container, homeFragment)
|
||||
fragmentTransaction.commit()
|
||||
initAesthetic()
|
||||
initForegroundDispatch()
|
||||
|
||||
val toggle = ActionBarDrawerToggle(
|
||||
this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close
|
||||
@ -74,12 +87,48 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
toggle.syncState()
|
||||
|
||||
nav_view.setNavigationItemSelectedListener(this)
|
||||
|
||||
// based on the intent we get, call readBalance or open a Fragment
|
||||
when (intent.action) {
|
||||
NfcAdapter.ACTION_TECH_DISCOVERED -> NFCMensaCard.readBalance(intent, this)
|
||||
"org.mosad.seil0.projectlaogai.fragments.MensaFragment" -> activeFragment = MensaFragment()
|
||||
"org.mosad.seil0.projectlaogai.fragments.TimeTableFragment" -> activeFragment = TimeTableFragment()
|
||||
"org.mosad.seil0.projectlaogai.fragments.MoodleFragment" -> activeFragment = MoodleFragment()
|
||||
}
|
||||
|
||||
// open the activeFragment, default is the HomeFragment
|
||||
fragmentTransaction.replace(R.id.fragment_container, activeFragment)
|
||||
fragmentTransaction.commit()
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
|
||||
if (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action)
|
||||
NFCMensaCard.readBalance(intent, this)
|
||||
}
|
||||
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
Aesthetic.resume(this)
|
||||
if(useNFC)
|
||||
adapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray)
|
||||
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
Aesthetic.pause(this)
|
||||
if(useNFC)
|
||||
adapter.disableForegroundDispatch(this)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
|
||||
drawer_layout.closeDrawer(GravityCompat.START)
|
||||
} else {
|
||||
// TODO only call on double tap
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
@ -102,147 +151,82 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
|
||||
override fun onNavigationItemSelected(item: MenuItem): Boolean {
|
||||
// Handle navigation view item clicks here.
|
||||
when (item.itemId) {
|
||||
R.id.nav_home -> {
|
||||
val homeFragment = HomeFragment()
|
||||
homeFragment.setMainActivity(this)
|
||||
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
|
||||
fragmentTransaction.replace(R.id.fragment_container, homeFragment)
|
||||
fragmentTransaction.commit()
|
||||
}
|
||||
R.id.nav_mensa -> {
|
||||
val mensaFragment = MensaFragment()
|
||||
mensaFragment.setMainActivity(this)
|
||||
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
|
||||
fragmentTransaction.replace(R.id.fragment_container, mensaFragment)
|
||||
fragmentTransaction.commit()
|
||||
}
|
||||
R.id.nav_timetable -> {
|
||||
val timeTableFragment = TimeTableFragment()
|
||||
timeTableFragment.setMainActivity(this)
|
||||
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
|
||||
fragmentTransaction.replace(R.id.fragment_container, timeTableFragment)
|
||||
fragmentTransaction.commit()
|
||||
|
||||
}
|
||||
R.id.nav_moodle -> {
|
||||
// val moodleFragment = MoodleFragment()
|
||||
//
|
||||
// val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
|
||||
// fragmentTransaction.replace(R.id.fragment_container, moodleFragment)
|
||||
// fragmentTransaction.commit()
|
||||
}
|
||||
R.id.nav_settings -> {
|
||||
val settingsFragment = SettingsFragment()
|
||||
settingsFragment.setMainActivity(this)
|
||||
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
|
||||
fragmentTransaction.replace(R.id.fragment_container, settingsFragment)
|
||||
fragmentTransaction.commit()
|
||||
}
|
||||
activeFragment = when(item.itemId) {
|
||||
R.id.nav_home -> HomeFragment()
|
||||
R.id.nav_mensa -> MensaFragment()
|
||||
R.id.nav_timetable -> TimeTableFragment()
|
||||
R.id.nav_moodle -> MoodleFragment()
|
||||
R.id.nav_grades -> GradesFragment()
|
||||
R.id.nav_settings -> SettingsFragment()
|
||||
else -> HomeFragment()
|
||||
}
|
||||
|
||||
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
|
||||
fragmentTransaction.replace(R.id.fragment_container, activeFragment)
|
||||
fragmentTransaction.commit()
|
||||
|
||||
drawer_layout.closeDrawer(GravityCompat.START)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* update the gui with the data of the new selected course
|
||||
* save selected course and courseTTLink
|
||||
*/
|
||||
fun updateCourse(course: CourseTTLink) {
|
||||
println(course.course)
|
||||
println(course.courseTTLink)
|
||||
|
||||
this.course = course
|
||||
// save new selected course
|
||||
val sharedPref = getPreferences(MODE_PRIVATE) ?: return
|
||||
with (sharedPref.edit()) {
|
||||
putString(getString(R.string.save_key_course), course.course)
|
||||
putString(getString(R.string.save_key_courseTTLink), course.courseTTLink.replace("http", "https"))
|
||||
apply()
|
||||
}
|
||||
|
||||
timeTableCurrentWeek = timeTableParser.getTimeTable(course.courseTTLink.replace("http", "https"))
|
||||
}
|
||||
|
||||
/**
|
||||
* load the mensa menus of the current week
|
||||
*/
|
||||
private fun load() {
|
||||
|
||||
// load saved course
|
||||
val sharedPref = getPreferences(MODE_PRIVATE) ?: return
|
||||
course = CourseTTLink(
|
||||
sharedPref.getString(getString(R.string.save_key_courseTTLink),
|
||||
"https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0")!!,
|
||||
sharedPref.getString(getString(R.string.save_key_course), "AI3")!!
|
||||
)
|
||||
|
||||
/**
|
||||
* load mensa, course timetable and courselist from the swfr/hso website
|
||||
* TODO make an API see https://git.mosad.xyz/Seil0/TheCitadelofRicks
|
||||
*/
|
||||
val time = measureTimeMillis {
|
||||
/* getting the course list should be faster than the timetable,
|
||||
* we need have time until the user opens the dialog
|
||||
*/
|
||||
doAsync {
|
||||
courseTTLinkList = timeTableParser.getCourseTTLinks()
|
||||
}
|
||||
|
||||
doAsync {
|
||||
try {
|
||||
timeTableNextWeek = timeTableParser.getTimeTable(course.courseTTLink.replace("week=0","week=1"))
|
||||
} catch (e: Exception) {
|
||||
e.stackTrace
|
||||
}
|
||||
}
|
||||
|
||||
val t1 = doAsync {
|
||||
weekMenus = mensaParser.getMensaMenu()
|
||||
}
|
||||
|
||||
val t2 = doAsync {
|
||||
try {
|
||||
timeTableCurrentWeek = timeTableParser.getTimeTable(course.courseTTLink)
|
||||
} catch (e: Exception) {
|
||||
|
||||
uiThread {
|
||||
MaterialDialog(this@MainActivity)
|
||||
.title(R.string.error)
|
||||
.message(R.string.no_tt_error)
|
||||
.show()
|
||||
}
|
||||
|
||||
e.stackTrace
|
||||
}
|
||||
}
|
||||
|
||||
t1.get()
|
||||
t2.get()
|
||||
val startupTime = measureTimeMillis {
|
||||
Preferences.load(this) // load the settings, must be finished before doing anything else
|
||||
CacheController(this) // load the cache
|
||||
EncryptedPreferences.load(this)
|
||||
}
|
||||
println("Completed in $time ms")
|
||||
|
||||
Log.i(className, "startup completed in $startupTime ms")
|
||||
}
|
||||
|
||||
fun getCourseTTLinkList(): ArrayList<CourseTTLink>{
|
||||
return courseTTLinkList
|
||||
private fun initAesthetic() {
|
||||
// If we haven't set any defaults, do that now
|
||||
if (Aesthetic.isFirstTime) {
|
||||
// set the default theme at the first app start
|
||||
Aesthetic.config {
|
||||
activityTheme(R.style.AppTheme_Light)
|
||||
apply()
|
||||
}
|
||||
|
||||
// show the onboarding activity
|
||||
startActivity(Intent(this, OnboardingActivity::class.java))
|
||||
finish()
|
||||
}
|
||||
|
||||
Aesthetic.config {
|
||||
colorPrimary(cColorPrimary)
|
||||
colorPrimaryDark(cColorPrimary)
|
||||
colorAccent(cColorAccent)
|
||||
navigationViewMode(NavigationViewMode.SELECTED_ACCENT)
|
||||
apply()
|
||||
}
|
||||
|
||||
// set theme color values
|
||||
val out = TypedValue()
|
||||
this.theme.resolveAttribute(R.attr.themePrimary, out, true)
|
||||
Preferences.themePrimary = out.data
|
||||
|
||||
this.theme.resolveAttribute(R.attr.themeSecondary, out, true)
|
||||
Preferences.themeSecondary = out.data
|
||||
}
|
||||
|
||||
fun getTimeTableCurrentWeek(): Array<Array<Lesson>> {
|
||||
return timeTableCurrentWeek
|
||||
}
|
||||
private fun initForegroundDispatch() {
|
||||
val nfcManager = this.getSystemService(Context.NFC_SERVICE) as NfcManager
|
||||
val nfcAdapter = nfcManager.defaultAdapter
|
||||
|
||||
fun getTimeTableNextWeek(): Array<Array<Lesson>> {
|
||||
return timeTableNextWeek
|
||||
}
|
||||
|
||||
fun getWeekMenu(): ArrayList<Meal>{
|
||||
return weekMenus
|
||||
}
|
||||
|
||||
fun getCourse(): CourseTTLink {
|
||||
return course
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,145 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.afollestad.materialdialogs.callbacks.onDismiss
|
||||
import kotlinx.android.synthetic.main.activity_onboarding.*
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
|
||||
import org.mosad.seil0.projectlaogai.fragments.SettingsFragment
|
||||
import org.mosad.seil0.projectlaogai.onboarding.ViewPagerAdapter
|
||||
|
||||
class OnboardingActivity : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
val layouts = intArrayOf(R.layout.fragment_on_welcome, R.layout.fragment_on_course, R.layout.fragment_on_login)
|
||||
}
|
||||
private lateinit var viewPager: ViewPager
|
||||
private lateinit var viewPagerAdapter: ViewPagerAdapter
|
||||
private lateinit var linLayoutDots: LinearLayout
|
||||
private lateinit var dots: Array<TextView>
|
||||
|
||||
private lateinit var editTextEmail: EditText
|
||||
private lateinit var editTextPassword: EditText
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_onboarding)
|
||||
|
||||
viewPager = findViewById(R.id.viewPager)
|
||||
linLayoutDots = findViewById(R.id.linLayout_Dots)
|
||||
|
||||
addBottomDots(0)
|
||||
|
||||
viewPagerAdapter = ViewPagerAdapter(this)
|
||||
viewPager.adapter = viewPagerAdapter
|
||||
viewPager.addOnPageChangeListener(viewPagerPageChangeListener)
|
||||
|
||||
// we don't use the skip button, instead we use the start button to skip the last fragment
|
||||
btn_Skip.visibility = View.GONE
|
||||
}
|
||||
|
||||
fun btnNextClick(@Suppress("UNUSED_PARAMETER")v: View) {
|
||||
if (viewPager.currentItem < layouts.size - 1) {
|
||||
viewPager.currentItem++
|
||||
} else {
|
||||
launchHomeScreen()
|
||||
}
|
||||
}
|
||||
|
||||
fun btnSkipClick(@Suppress("UNUSED_PARAMETER")v: View) {
|
||||
launchHomeScreen()
|
||||
}
|
||||
|
||||
fun btnSelectCourseClick(@Suppress("UNUSED_PARAMETER")v: View) {
|
||||
SettingsFragment().selectCourse(this).show {
|
||||
onDismiss {
|
||||
btnNextClick(v) // show the next fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun btnLoginClick(@Suppress("UNUSED_PARAMETER")v: View) {
|
||||
editTextEmail = findViewById(R.id.editText_email)
|
||||
editTextPassword = findViewById(R.id.editText_password)
|
||||
|
||||
// get login credentials from gui
|
||||
val email = editTextEmail.text.toString()
|
||||
val password = editTextPassword.text.toString()
|
||||
|
||||
// save the credentials
|
||||
EncryptedPreferences.saveCredentials(email, password, this)
|
||||
|
||||
launchHomeScreen()
|
||||
}
|
||||
|
||||
private fun addBottomDots(currentPage: Int) {
|
||||
dots = Array(layouts.size) { TextView(this) }
|
||||
linLayoutDots.removeAllViews()
|
||||
|
||||
dots.forEach {
|
||||
it.text = HtmlCompat.fromHtml("•", HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
it.textSize = 35f
|
||||
it.setTextColor(Color.parseColor("#cccccc"))
|
||||
linLayoutDots.addView(it)
|
||||
}
|
||||
|
||||
if (dots.isNotEmpty())
|
||||
dots[currentPage].setTextColor(Color.parseColor("#000000"))
|
||||
}
|
||||
|
||||
private fun launchHomeScreen() {
|
||||
startActivity(Intent(this, MainActivity::class.java))
|
||||
finish()
|
||||
}
|
||||
|
||||
private var viewPagerPageChangeListener: ViewPager.OnPageChangeListener = object : ViewPager.OnPageChangeListener {
|
||||
|
||||
override fun onPageSelected(position: Int) {
|
||||
addBottomDots(position)
|
||||
// changing the next button text to skip for the login fragment
|
||||
if (position == layouts.size - 1) {
|
||||
btn_Next.text = getString(R.string.skip)
|
||||
btn_Next.visibility = View.VISIBLE
|
||||
btn_Skip.visibility = View.GONE
|
||||
} else {
|
||||
btn_Next.visibility = View.GONE
|
||||
btn_Skip.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageScrolled(arg0: Int, arg1: Float, arg2: Int) {}
|
||||
override fun onPageScrollStateChanged(arg0: Int) {}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.controller
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.nfc.tech.IsoDep
|
||||
import android.util.Log
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import com.codebutler.farebot.Utils
|
||||
import com.codebutler.farebot.card.desfire.DesfireFileSettings
|
||||
import com.codebutler.farebot.card.desfire.DesfireProtocol
|
||||
import kotlinx.android.synthetic.main.dialog_mensa_credit.*
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||
import java.lang.Exception
|
||||
|
||||
class NFCMensaCard {
|
||||
|
||||
companion object {
|
||||
private const val className = "NFCMensaCard"
|
||||
private const val appId = 0x5F8415
|
||||
private const val fileId = 1
|
||||
|
||||
/**
|
||||
* read the current balance and last payment from the mensa card
|
||||
* @param intent a nfc intent
|
||||
* @param context the context to show the dialog in
|
||||
*/
|
||||
fun readBalance(intent: Intent, context: Context) {
|
||||
val tag: Tag? = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
|
||||
val isoDep = IsoDep.get(tag)
|
||||
try {
|
||||
isoDep.connect()
|
||||
|
||||
val card = DesfireProtocol(isoDep)
|
||||
val settings = Utils.selectAppFile(card, appId, fileId)
|
||||
|
||||
if (settings is DesfireFileSettings.ValueDesfireFileSettings) {
|
||||
val data = try {
|
||||
card.readValue(fileId)
|
||||
} catch (ex: Exception) { 0 }
|
||||
|
||||
lookAtMe(context, data, settings.value).show()
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Log.w(className,"could not connect to tag", ex)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* generate the values for current balance and last payment
|
||||
* if the easter egg is active use schmeckles as currency
|
||||
* 0.0000075 = 1.11 / 148 / 1000 (dollar / shm / card multiplier)
|
||||
* @param context the context to access resources
|
||||
* @param currentRaw the raw card value of the current balance
|
||||
* @param lastRaw the raw card value of the last payment
|
||||
* @return the message containing all values
|
||||
*/
|
||||
private fun lookAtMe(context: Context, currentRaw: Int, lastRaw: Int): MaterialDialog {
|
||||
val dialog = MaterialDialog(context)
|
||||
.customView(R.layout.dialog_mensa_credit)
|
||||
|
||||
val current = if (!Preferences.oGiants) {
|
||||
String.format("%.2f €", (currentRaw.toFloat() / 1000))
|
||||
} else {
|
||||
String.format("%.4f shm", (currentRaw.toFloat() * 0.0000075))
|
||||
}
|
||||
|
||||
val last = if (!Preferences.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
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,236 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.controller
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jsoup.HttpStatusException
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
|
||||
import org.mosad.seil0.projectlaogai.util.GradeSubject
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
/**
|
||||
* Parse the qispos site the get all needed data for the grades fragment
|
||||
*/
|
||||
class QISPOSParser(val context: Context) {
|
||||
|
||||
private val className = this.javaClass.name
|
||||
private val baseURL = "https://notenverwaltung.hs-offenburg.de"
|
||||
private val loginPath = "/qispos/rds?state=user&type=1&category=auth.login&startpage=portal.vm&breadCrumbSource=portal"
|
||||
|
||||
/**
|
||||
* check if qispos is available
|
||||
* @return a http status code, 999 if no HttpStatusException supplied
|
||||
*/
|
||||
fun checkQISPOSStatus(): Int {
|
||||
return runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
val socketFactory = createSSLSocketFactory()
|
||||
|
||||
try {
|
||||
val res = Jsoup.connect(baseURL + loginPath)
|
||||
.sslSocketFactory(socketFactory)
|
||||
.followRedirects(true)
|
||||
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
|
||||
.execute()
|
||||
|
||||
res.statusCode()
|
||||
} catch (exHttp: HttpStatusException) {
|
||||
exHttp.statusCode
|
||||
} catch (ex: Exception) {
|
||||
Log.e(className, "Error while checking status", ex)
|
||||
999
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parse the html from readGrades()
|
||||
* @return a SortedMap, each entry is a semester, each semester has a ArrayList with subjects
|
||||
*/
|
||||
fun parseGrades(): SortedMap<String, ArrayList<GradeSubject>> {
|
||||
val gradesMap = HashMap<String, ArrayList<GradeSubject>>()
|
||||
val gradesListHtml = readGrades()
|
||||
|
||||
gradesListHtml?.select("table > tbody > tr")?.forEach {
|
||||
val row = it.select("td.tabelle1_alignleft,td.tabelle1_aligncenter,td.tabelle1_alignright")
|
||||
|
||||
// only real subjects will be selected
|
||||
if(row.size >= 6 && row[0].text().length >=7) {
|
||||
val subject = GradeSubject(
|
||||
id = row[0].text(),
|
||||
name = row[1].text(),
|
||||
semester = row[2].text(),
|
||||
grade = if (row[3].text().isNotEmpty()) row[3].text() else row[4].text(),
|
||||
credits = row[5].text()
|
||||
)
|
||||
|
||||
if (gradesMap.containsKey(subject.semester)) {
|
||||
gradesMap[subject.semester]!!.add(subject)
|
||||
} else {
|
||||
gradesMap[subject.semester] = arrayListOf(subject)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// return the sorted map
|
||||
return gradesMap.toSortedMap(compareBy<String>{
|
||||
val oText = it.substringAfter(" ")
|
||||
|
||||
if (oText.contains("/")) {
|
||||
oText.substringBefore("/").toInt() + 0.5
|
||||
} else {
|
||||
oText.toDouble()
|
||||
}
|
||||
}.thenBy { it })
|
||||
}
|
||||
|
||||
/**
|
||||
* read the grades html from qispos
|
||||
* @return the grades list as html element or null
|
||||
*/
|
||||
private fun readGrades(): Element?{
|
||||
|
||||
val credentials = EncryptedPreferences.readCredentials(context)
|
||||
val username = credentials.first.substringBefore("@")
|
||||
val password = credentials.second
|
||||
|
||||
return runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val socketFactory = createSSLSocketFactory()
|
||||
|
||||
// login, asdf = username, fdsa = password, wtf
|
||||
val list = mapOf(
|
||||
Pair("asdf", username),
|
||||
Pair("fdsa", password),
|
||||
Pair("submit", "Anmelden")
|
||||
)
|
||||
|
||||
// login and get session cookies
|
||||
val res = Jsoup.connect(baseURL + loginPath)
|
||||
.sslSocketFactory(socketFactory)
|
||||
.followRedirects(true)
|
||||
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
|
||||
.data(list)
|
||||
.postDataCharset("UTF-8")
|
||||
.execute()
|
||||
Log.i(className, "login status is: ${res.statusMessage()}: ${res.statusCode()}")
|
||||
|
||||
val loginCookies = res.cookies()
|
||||
|
||||
// grades root document and url
|
||||
val rootHtml =Jsoup.parse(res.body())
|
||||
val gradesRootLink = rootHtml.select("li.menueListStyle > a.auflistung").last().attr("abs:href")
|
||||
|
||||
// parse grades url
|
||||
val gradesHtml = Jsoup.connect(gradesRootLink)
|
||||
.sslSocketFactory(socketFactory)
|
||||
.followRedirects(true)
|
||||
.cookies(loginCookies)
|
||||
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
|
||||
.get()
|
||||
|
||||
val gradesNextLink = gradesHtml.select("li.treelist > a.regular").attr("abs:href")
|
||||
|
||||
val gradesNextHtml = Jsoup.connect(gradesNextLink)
|
||||
.sslSocketFactory(socketFactory)
|
||||
.followRedirects(true)
|
||||
.cookies(loginCookies)
|
||||
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
|
||||
.get()
|
||||
|
||||
val gradesListLink = gradesNextHtml.selectFirst("li.treelist > ul > li").selectFirst("a").attr("abs:href")
|
||||
|
||||
// get the grades list
|
||||
val gradesListHtml = Jsoup.connect(gradesListLink)
|
||||
.sslSocketFactory(socketFactory)
|
||||
.followRedirects(true)
|
||||
.cookies(loginCookies)
|
||||
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
|
||||
.get()
|
||||
|
||||
gradesListHtml.body()
|
||||
} catch (ex: Exception) {
|
||||
Log.e(className, "error while loading qispos", ex)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* since the HS has a fucked up tls setup we need to work around that
|
||||
*/
|
||||
private fun createSSLSocketFactory(): SSLSocketFactory {
|
||||
// Load CAs from an InputStream
|
||||
// (could be from a resource or ByteArrayInputStream or ...)
|
||||
val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
|
||||
val caInput = context.resources.openRawResource(R.raw.notenverwaltung_hs_offenburg_de)
|
||||
val ca = caInput.use {
|
||||
cf.generateCertificate(it) as X509Certificate
|
||||
}
|
||||
|
||||
// Create a KeyStore containing our trusted CAs
|
||||
val keyStoreType = KeyStore.getDefaultType()
|
||||
val keyStore = KeyStore.getInstance(keyStoreType).apply {
|
||||
load(null, null)
|
||||
setCertificateEntry("ca", ca)
|
||||
}
|
||||
|
||||
// Create a TrustManager that trusts the CAs inputStream our KeyStore
|
||||
val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
|
||||
val tmf = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
|
||||
init(keyStore)
|
||||
}
|
||||
|
||||
// Create an SSLContext that uses our TrustManager
|
||||
val sslContext = SSLContext.getInstance("TLS").apply {
|
||||
init(null, tmf.trustManagers, null)
|
||||
}
|
||||
|
||||
return sslContext.socketFactory
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.controller
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonParser
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import kotlinx.coroutines.*
|
||||
import org.mosad.seil0.projectlaogai.util.*
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* This Controller calls the tcor api,
|
||||
* all functions return tcor api objects
|
||||
*/
|
||||
class TCoRAPIController {
|
||||
|
||||
companion object {
|
||||
private const val tcorBaseURL = "https://tcor.mosad.xyz"
|
||||
|
||||
/**
|
||||
* Get a array of all currently available courses at the tcor API.
|
||||
* Read the json object from tcor api
|
||||
*/
|
||||
|
||||
fun getCourseListNEW(): CoursesList {
|
||||
val url = URL("$tcorBaseURL/courseList")
|
||||
|
||||
return Gson().fromJson(url.readText(), CoursesList().javaClass)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current and next weeks mensa menus from the tcor API.
|
||||
* Read the json object from tcor api
|
||||
*/
|
||||
fun getMensaMenu(): MensaMenu {
|
||||
val url = URL("$tcorBaseURL/mensamenu")
|
||||
|
||||
return Gson().fromJson(url.readText(), MensaMenu().javaClass)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timetable for "courseName" at week "week"
|
||||
* Read the json object from tcor api
|
||||
* @param courseName the course name (e.g AI1)
|
||||
* @param week the week to update (0 for the current and so on)
|
||||
*/
|
||||
fun getTimetable(courseName: String, week: Int): TimetableWeek {
|
||||
val url = URL("$tcorBaseURL/timetable?course=$courseName&week=$week")
|
||||
val timetableCW = Gson().fromJson(url.readText(), TimetableCourseWeek().javaClass)
|
||||
|
||||
return TimetableWeek(
|
||||
timetableCW.meta.weekIndex,
|
||||
timetableCW.meta.weekNumberYear,
|
||||
timetableCW.timetable.days
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all lessons for a course at one week (async)
|
||||
* @param courseName the course name
|
||||
* @param week the week to look up
|
||||
*/
|
||||
fun getSubjectListAsync(courseName: String, week: Int): Deferred<ArrayList<String>> {
|
||||
val url = URL("$tcorBaseURL/subjectList?course=$courseName&week=$week")
|
||||
|
||||
return GlobalScope.async {
|
||||
Gson().fromJson(url.readText(), ArrayList<String>()::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all occurrences of a lesson for a course at one week
|
||||
* @param courseName the course name
|
||||
* @param subject the subject to search for
|
||||
* @param week the week to look up
|
||||
*/
|
||||
fun getLessons(courseName: String, subject: String, week: Int): ArrayList<Lesson> {
|
||||
val url = URL("$tcorBaseURL/lessons?course=$courseName&subject=$subject&week=$week")
|
||||
var array: ArrayList<Lesson>
|
||||
|
||||
runBlocking {
|
||||
withContext(Dispatchers.Default) {
|
||||
array = Gson().fromJson(
|
||||
JsonParser.parseString(url.readText()).asJsonArray,
|
||||
object : TypeToken<ArrayList<Lesson>>() {}.type
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return array
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
375
app/src/main/java/org/mosad/seil0/projectlaogai/controller/cache/CacheController.kt
vendored
Normal file
@ -0,0 +1,375 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.controller.cache
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonParser
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import kotlinx.coroutines.*
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.coursesCacheTime
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.mensaCacheTime
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.timetableCacheTime
|
||||
import org.mosad.seil0.projectlaogai.util.*
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
/**
|
||||
* The cacheController reads and updates the cache files.
|
||||
* It contains the courseList and mensaMenu object, all timetable objects
|
||||
* are located in TimetableController.
|
||||
*/
|
||||
class CacheController(cont: Context) {
|
||||
|
||||
private val className = "CacheController"
|
||||
private val context = cont
|
||||
|
||||
init {
|
||||
val cal = Calendar.getInstance()
|
||||
val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
|
||||
val currentTime = System.currentTimeMillis() / 1000
|
||||
|
||||
// check if we need to update the mensa data before displaying it
|
||||
cal.time = Date(mensaCacheTime * 1000)
|
||||
|
||||
// if a) it's monday and the last cache update was on sunday or b) the cache is older than 24hr, update blocking
|
||||
if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - mensaMenu.meta.updateTime) > 86400) {
|
||||
Log.i(className, "update mensa blocking")
|
||||
GlobalScope.launch(Dispatchers.Default) { updateMensaMenu(context).join() }
|
||||
}
|
||||
|
||||
// check if we need to update the timetable before displaying it
|
||||
cal.time = Date(timetableCacheTime * 1000)
|
||||
|
||||
// if a) it`s monday and the last cache update was not on a sunday or b) the cache is older than 24hr, update blocking
|
||||
if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - timetableCacheTime) > 86400) {
|
||||
Log.i(className, "updating timetable after sunday!")
|
||||
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
val threads = listOf(
|
||||
updateTimetable(cCourse.courseName, 0, context),
|
||||
updateTimetable(cCourse.courseName, 1, context)
|
||||
)
|
||||
threads.joinAll()
|
||||
}
|
||||
}
|
||||
|
||||
updateCourseList(context)
|
||||
|
||||
readStartCache(cCourse.courseName) // initially read values from cache
|
||||
|
||||
// check if an update is necessary, not blocking
|
||||
if (currentTime - coursesCacheTime > 86400)
|
||||
updateCourseList(context)
|
||||
|
||||
if (currentTime - mensaCacheTime > 10800)
|
||||
updateMensaMenu(context)
|
||||
|
||||
if (currentTime - timetableCacheTime > 10800) {
|
||||
TimetableController.update(context)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val className = "CacheController"
|
||||
var coursesList = ArrayList<Course>()
|
||||
var mensaMenu = MensaMenu()
|
||||
|
||||
/**
|
||||
* update the course list, async
|
||||
*/
|
||||
fun updateCourseList(context: Context): Job {
|
||||
val file = File(context.filesDir, "courses.json")
|
||||
var courseListUp = CoursesList()
|
||||
|
||||
return GlobalScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
courseListUp = TCoRAPIController.getCourseListNEW()
|
||||
} catch (ex: Exception) {
|
||||
Log.e(className, "could not load course list from tcor", ex)
|
||||
}
|
||||
|
||||
// update coursesList array list
|
||||
coursesList = courseListUp.courses
|
||||
|
||||
// save cache file and update time
|
||||
save(file, Gson().toJson(courseListUp))
|
||||
coursesCacheTime = System.currentTimeMillis() / 1000
|
||||
Preferences.save(context)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update the mensa menu, async
|
||||
*/
|
||||
fun updateMensaMenu(context: Context): Job {
|
||||
val file = File(context.filesDir, "mensa.json")
|
||||
|
||||
return GlobalScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
mensaMenu = TCoRAPIController.getMensaMenu()
|
||||
} catch (ex: Exception) {
|
||||
Log.e(className, "could not load mensa menu from tcor", ex)
|
||||
}
|
||||
|
||||
// save cache file and update time
|
||||
save(file, Gson().toJson(mensaMenu))
|
||||
mensaCacheTime = System.currentTimeMillis() / 1000
|
||||
Preferences.save(context)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update the timetable for a week, async
|
||||
* @param courseName the course name (e.g AI1)
|
||||
* @param week the week to update (0 for the current and so on)
|
||||
*/
|
||||
fun updateTimetable(courseName: String, week: Int, context: Context): Job {
|
||||
val file = File(context.filesDir, "timetable-$courseName-$week.json")
|
||||
var timetable = TimetableWeek()
|
||||
|
||||
// try to update timetable from tcor, async
|
||||
return GlobalScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
timetable = TCoRAPIController.getTimetable(courseName, week)
|
||||
} catch (ex: Exception) {
|
||||
Log.e(className, "could not load timetable $courseName[$week] from tcor", ex)
|
||||
}
|
||||
|
||||
// update timetable in TTC
|
||||
if (TimetableController.timetable.size > week) {
|
||||
TimetableController.timetable[week] = timetable
|
||||
} else {
|
||||
TimetableController.timetable.add(timetable)
|
||||
}
|
||||
|
||||
// save cache file and update time
|
||||
save(file, Gson().toJson(timetable))
|
||||
timetableCacheTime = System.currentTimeMillis() / 1000
|
||||
Preferences.save(context)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update all additional subject lessons, async
|
||||
*/
|
||||
fun updateAdditionalLessons(context: Context): Job {
|
||||
val fileLessons = File(context.filesDir, "additional_lessons.json")
|
||||
|
||||
return GlobalScope.launch(Dispatchers.IO) {
|
||||
TimetableController.subjectMap.forEach { (courseName, subjects) ->
|
||||
// update all subjects for a course
|
||||
subjects.forEach {subject ->
|
||||
try {
|
||||
TCoRAPIController.getLessons(courseName, subject, 0).forEach { lesson ->
|
||||
TimetableController.addLesson(courseName, subject, lesson)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Log.e(className, "could not load $courseName: $subject", ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
save(fileLessons, Gson().toJson(TimetableController.lessonMap))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* save changes in lessonMap and subjectMap,
|
||||
* called on addSubject or removeSubject
|
||||
*/
|
||||
fun saveAdditionalSubjects(context: Context) {
|
||||
val fileLessons = File(context.filesDir, "additional_lessons.json")
|
||||
val fileSubjects = File(context.filesDir, "additional_subjects.json")
|
||||
|
||||
save(fileLessons, Gson().toJson(TimetableController.lessonMap))
|
||||
save(fileSubjects, Gson().toJson(TimetableController.subjectMap))
|
||||
}
|
||||
|
||||
private fun save(file: File, text: String) {
|
||||
try {
|
||||
val writer = BufferedWriter(FileWriter(file))
|
||||
writer.write(text)
|
||||
writer.close()
|
||||
} catch (ex: Exception) {
|
||||
Log.e(className, "failed to write file \"${file.absoluteFile}\"", ex)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* read coursesList, mensa (current and next week), timetable (current and next week)
|
||||
* @param courseName the course name (e.g AI1)
|
||||
*/
|
||||
private fun readStartCache(courseName: String) {
|
||||
try {
|
||||
readCoursesList()
|
||||
} catch (ex : Exception) {
|
||||
Log.e(className, "Error while reading the course list", ex)
|
||||
}
|
||||
|
||||
try {
|
||||
readMensa()
|
||||
} catch (ex : Exception) {
|
||||
Log.e(className, "Error while reading the mensa menu", ex)
|
||||
}
|
||||
|
||||
try {
|
||||
readTimetable(courseName, 0)
|
||||
} catch (ex : Exception) {
|
||||
Log.e(className, "Error while reading timetable week 0", ex)
|
||||
}
|
||||
|
||||
try {
|
||||
readTimetable(courseName, 1)
|
||||
} catch (ex : Exception) {
|
||||
Log.e(className, "Error while reading timetable week 1", ex)
|
||||
}
|
||||
|
||||
try {
|
||||
readAdditionalSubjects()
|
||||
} catch (ex : Exception) {
|
||||
Log.e(className, "Error while reading additional subjects", ex)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* read the courses list from the cached file
|
||||
* add them to the coursesList object
|
||||
*/
|
||||
private fun readCoursesList() {
|
||||
val file = File(context.filesDir, "courses.json")
|
||||
|
||||
// make sure the file exists
|
||||
if (!file.exists()) {
|
||||
runBlocking { updateCourseList(context).join() }
|
||||
return
|
||||
}
|
||||
|
||||
coursesList = FileReader(file).use {
|
||||
GsonBuilder().create().fromJson(BufferedReader(it).readLine(), CoursesList().javaClass).courses
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get the MensaMenu object from the cached json,
|
||||
* if cache is empty create the cache file
|
||||
*/
|
||||
private fun readMensa() {
|
||||
val file = File(context.filesDir, "mensa.json")
|
||||
|
||||
// make sure the file exists
|
||||
if (!file.exists()) {
|
||||
runBlocking { updateMensaMenu(context).join() }
|
||||
return
|
||||
}
|
||||
|
||||
mensaMenu = FileReader(file).use {
|
||||
GsonBuilder().create().fromJson(BufferedReader(it).readLine(), MensaMenu().javaClass)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* read the weeks timetable from the cached file
|
||||
* @param courseName the course name (e.g AI1)
|
||||
* @param week the week to read (0 for the current and so on)
|
||||
*/
|
||||
private fun readTimetable(courseName: String, week: Int) {
|
||||
val file = File(context.filesDir, "timetable-$courseName-$week.json")
|
||||
|
||||
// if the file does not exist, call updateTimetable blocking and return
|
||||
if (!file.exists()) {
|
||||
runBlocking { updateTimetable(courseName, week, context).join() }
|
||||
return
|
||||
}
|
||||
|
||||
val timetableObject = FileReader(file).use {
|
||||
JsonParser.parseString(BufferedReader(it).readLine()).asJsonObject
|
||||
}
|
||||
|
||||
// if its a TimetableCourseWeek object migrate to TimetableWeek TODO remove converting at version 0.8
|
||||
val timetable = if(timetableObject.has("meta")) {
|
||||
Log.i(Companion.className, "trying to migrate TimetableCourseWeek to TimetableWeek")
|
||||
val timetableWC = Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass)
|
||||
save(file, Gson().toJson(TimetableWeek(
|
||||
timetableWC.meta.weekIndex,
|
||||
timetableWC.meta.weekNumberYear,
|
||||
timetableWC.timetable.days
|
||||
)))
|
||||
TimetableWeek(timetableWC.meta.weekIndex, timetableWC.meta.weekNumberYear, timetableWC.timetable.days)
|
||||
} else {
|
||||
Gson().fromJson(timetableObject, TimetableWeek().javaClass)
|
||||
}
|
||||
|
||||
// update timetable in TTC
|
||||
if (TimetableController.timetable.size > week) {
|
||||
TimetableController.timetable[week] = timetable
|
||||
} else {
|
||||
TimetableController.timetable.add(timetable)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readAdditionalSubjects() {
|
||||
val fileLessons = File(context.filesDir, "additional_lessons.json")
|
||||
val fileSubjects = File(context.filesDir, "additional_subjects.json")
|
||||
|
||||
// make sure the file exists
|
||||
if (!fileLessons.exists() || !fileSubjects.exists()) {
|
||||
return
|
||||
}
|
||||
|
||||
// clear the maps before loading, just to be save
|
||||
TimetableController.lessonMap.clear()
|
||||
TimetableController.subjectMap.clear()
|
||||
|
||||
// read subjects and lessons from cache file
|
||||
FileReader(fileLessons).use {
|
||||
TimetableController.lessonMap.putAll(
|
||||
GsonBuilder().create()
|
||||
.fromJson(BufferedReader(it).readLine(), object : TypeToken<HashMap<String, Lesson>>() {}.type)
|
||||
)
|
||||
}
|
||||
|
||||
FileReader(fileSubjects).use {
|
||||
TimetableController.subjectMap.putAll(
|
||||
GsonBuilder().create()
|
||||
.fromJson(BufferedReader(it).readLine(), HashMap<String, ArrayList<String>>().javaClass)
|
||||
)
|
||||
}
|
||||
|
||||
// add lessons to timetable
|
||||
TimetableController.lessonMap.forEach { (_, lesson) ->
|
||||
TimetableController.addLessonToTimetable(lesson)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
131
app/src/main/java/org/mosad/seil0/projectlaogai/controller/cache/TimetableController.kt
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.controller.cache
|
||||
|
||||
import android.content.Context
|
||||
import kotlinx.coroutines.Job
|
||||
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
|
||||
import org.mosad.seil0.projectlaogai.util.Lesson
|
||||
import org.mosad.seil0.projectlaogai.util.TimetableWeek
|
||||
|
||||
/**
|
||||
* The TimetableController contains the timetable, subjectMap
|
||||
* and lessonMap objects. It also contains the additional subjects logic.
|
||||
* All functions ro read or update cache files are located in the CacheController.
|
||||
*
|
||||
* TODO
|
||||
* * add second week
|
||||
* * add configurable week to addSubject() and removeSubject(), updateAdditionalLessons()
|
||||
*/
|
||||
class TimetableController {
|
||||
|
||||
companion object {
|
||||
val timetable = ArrayList<TimetableWeek>()
|
||||
val lessonMap = HashMap<String, Lesson>() // the key is courseName-subject-lessonID
|
||||
val subjectMap = HashMap<String, ArrayList<String>>() // the key is courseName
|
||||
|
||||
/**
|
||||
* update the main timetable and all additional subjects, async
|
||||
*/
|
||||
fun update(context: Context): List<Job> {
|
||||
return listOf(
|
||||
CacheController.updateTimetable(cCourse.courseName, 0, context),
|
||||
CacheController.updateTimetable(cCourse.courseName, 1, context),
|
||||
CacheController.updateAdditionalLessons(context)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* add a subject to the subjectMap and all it's lessons
|
||||
* to the lessonMap
|
||||
* @param courseName course to which the subject belongs
|
||||
* @param subject the subjects name
|
||||
*/
|
||||
fun addSubject(courseName: String, subject: String, context: Context) {
|
||||
// add subject
|
||||
if (subjectMap.containsKey(courseName)) {
|
||||
subjectMap[courseName]?.add(subject)
|
||||
} else {
|
||||
subjectMap[courseName] = arrayListOf(subject)
|
||||
}
|
||||
|
||||
// add concrete lessons
|
||||
TCoRAPIController.getLessons(courseName, subject, 0).forEach { lesson ->
|
||||
addLesson(courseName, subject, lesson)
|
||||
}
|
||||
|
||||
CacheController.saveAdditionalSubjects(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* remove a subject from the subjectMap and all it's lessons
|
||||
* from the lessonMap
|
||||
* @param courseName course to which the subject belongs
|
||||
* @param subject the subjects name
|
||||
*/
|
||||
fun removeSubject(courseName: String, subject: String, context: Context) {
|
||||
// remove subject
|
||||
subjectMap[courseName]?.remove(subject)
|
||||
|
||||
// remove concrete lessons
|
||||
val iterator = lessonMap.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val it = iterator.next()
|
||||
if(it.key.contains("$courseName-$subject")) {
|
||||
// remove the lesson from the lessons list
|
||||
iterator.remove() // use iterator to remove, otherwise ConcurrentModificationException
|
||||
|
||||
// remove the lesson from the timetable
|
||||
val id = it.value.lessonID.split(".")
|
||||
if(id.size == 3)
|
||||
timetable[0].days[id[0].toInt()].timeslots[id[1].toInt()].remove(it.value)
|
||||
}
|
||||
}
|
||||
|
||||
CacheController.saveAdditionalSubjects(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* add a lesson to the lessonMap, also add it to the timetable
|
||||
*/
|
||||
fun addLesson(courseName: String, subject: String, lesson: Lesson) {
|
||||
//the courseName, subject and lessonID, separator: -
|
||||
val key = "$courseName-$subject-${lesson.lessonID}"
|
||||
lessonMap[key] = lesson
|
||||
|
||||
addLessonToTimetable(lesson)
|
||||
}
|
||||
|
||||
/**
|
||||
* add a lesson to the timetable
|
||||
*/
|
||||
fun addLessonToTimetable(lesson: Lesson) {
|
||||
val id = lesson.lessonID.split(".")
|
||||
if(id.size == 3)
|
||||
timetable[0].days[id[0].toInt()].timeslots[id[1].toInt()].add(lesson)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.controller.preferences
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.util.Log
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKey
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
|
||||
object EncryptedPreferences {
|
||||
|
||||
var email = ""
|
||||
internal set
|
||||
|
||||
/**
|
||||
* save user email and password to encrypted preference
|
||||
*/
|
||||
fun saveCredentials(email: String, password: String, context: Context) {
|
||||
this.email = email
|
||||
|
||||
with (getEncryptedPreferences(context)?.edit()) {
|
||||
this?.putString(context.getString(R.string.save_key_user_email), email)
|
||||
this?.putString(context.getString(R.string.save_key_user_password), password)
|
||||
this?.apply()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* read user email and password from encrypted preference
|
||||
* @return Pair(email, password)
|
||||
*/
|
||||
fun readCredentials(context: Context): Pair<String, String> {
|
||||
return with (getEncryptedPreferences(context)) {
|
||||
email = this?.getString(context.getString(R.string.save_key_user_email), "").toString()
|
||||
|
||||
Pair(
|
||||
this?.getString(context.getString(R.string.save_key_user_email), "").toString(),
|
||||
this?.getString(context.getString(R.string.save_key_user_password), "").toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* initially load the stored values
|
||||
*/
|
||||
fun load(context: Context) {
|
||||
with(getEncryptedPreferences(context)) {
|
||||
email = this?.getString(
|
||||
context.getString(R.string.save_key_user_email), ""
|
||||
).toString()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create a encrypted shared preference
|
||||
*/
|
||||
private fun getEncryptedPreferences(context: Context): SharedPreferences? {
|
||||
return try {
|
||||
val spec = KeyGenParameterSpec.Builder(MasterKey.DEFAULT_MASTER_KEY_ALIAS,
|
||||
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.setKeySize(MasterKey.DEFAULT_AES_GCM_MASTER_KEY_SIZE)
|
||||
.build()
|
||||
|
||||
val masterKey = MasterKey.Builder(context)
|
||||
.setKeyGenParameterSpec(spec)
|
||||
.build()
|
||||
|
||||
EncryptedSharedPreferences.create(
|
||||
context,
|
||||
context.getString(R.string.encrypted_preference_file_key),
|
||||
masterKey,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
} catch (ex: Exception) {
|
||||
Log.e(javaClass.name, "Could not create encrypted shared preference.", ex)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,192 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.controller.preferences
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.util.Course
|
||||
|
||||
/**
|
||||
* The PreferencesController class
|
||||
* contains all preferences and global variables that exist in this app
|
||||
*/
|
||||
object Preferences {
|
||||
|
||||
var coursesCacheTime: Long = 0
|
||||
var mensaCacheTime: Long = 0
|
||||
var timetableCacheTime: Long = 0
|
||||
var cColorPrimary: Int = Color.parseColor("#009688")
|
||||
var cColorAccent: Int = Color.parseColor("#0096ff")
|
||||
var cCourse = Course(
|
||||
"https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0",
|
||||
"AI3"
|
||||
)
|
||||
var cShowBuffet = true
|
||||
var oGiants = false
|
||||
|
||||
// TODO move!
|
||||
var themePrimary = 0
|
||||
var themeSecondary = 0
|
||||
|
||||
// the save function
|
||||
fun save(context: Context) {
|
||||
val sharedPref = context.getSharedPreferences(
|
||||
context.getString(R.string.preference_file_key),
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
|
||||
// save the update times (cache)
|
||||
with (sharedPref.edit()) {
|
||||
putLong(context.getString(R.string.save_key_coursesCacheTime),
|
||||
coursesCacheTime
|
||||
)
|
||||
putLong(context.getString(R.string.save_key_mensaCacheTime),
|
||||
mensaCacheTime
|
||||
)
|
||||
putLong(context.getString(R.string.save_key_timetableCacheTime),
|
||||
timetableCacheTime
|
||||
)
|
||||
apply()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* save the course locally
|
||||
*/
|
||||
fun saveCourse(context: Context, course: Course) {
|
||||
val sharedPref = context.getSharedPreferences(
|
||||
context.getString(R.string.preference_file_key),
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
|
||||
with (sharedPref.edit()) {
|
||||
putString(context.getString(R.string.save_key_course), course.courseName)
|
||||
putString(context.getString(R.string.save_key_courseTTLink), course.courseLink)
|
||||
apply()
|
||||
}
|
||||
|
||||
cCourse = course
|
||||
}
|
||||
|
||||
/**
|
||||
* save the primary color
|
||||
*/
|
||||
fun saveColorPrimary(context: Context, colorPrimary: Int) {
|
||||
val sharedPref = context.getSharedPreferences(
|
||||
context.getString(R.string.preference_file_key),
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
|
||||
with (sharedPref.edit()) {
|
||||
putInt(context.getString(R.string.save_key_colorPrimary),
|
||||
colorPrimary
|
||||
)
|
||||
apply()
|
||||
}
|
||||
|
||||
cColorPrimary = colorPrimary
|
||||
}
|
||||
|
||||
/**
|
||||
* save the accent color
|
||||
*/
|
||||
fun saveColorAccent(context: Context, colorAccent: Int) {
|
||||
val sharedPref = context.getSharedPreferences(
|
||||
context.getString(R.string.preference_file_key),
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
|
||||
with (sharedPref.edit()) {
|
||||
putInt(context.getString(R.string.save_key_colorAccent),
|
||||
colorAccent
|
||||
)
|
||||
apply()
|
||||
}
|
||||
|
||||
cColorAccent = colorAccent
|
||||
}
|
||||
|
||||
/**
|
||||
* save showBuffet
|
||||
*/
|
||||
fun saveShowBuffet(context: Context, showBuffet: Boolean) {
|
||||
val sharedPref = context.getSharedPreferences(
|
||||
context.getString(R.string.preference_file_key),
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
|
||||
with (sharedPref.edit()) {
|
||||
putBoolean(context.getString(R.string.save_key_showBuffet),
|
||||
showBuffet
|
||||
)
|
||||
apply()
|
||||
}
|
||||
|
||||
cShowBuffet = showBuffet
|
||||
}
|
||||
|
||||
/**
|
||||
* initially load the stored values
|
||||
*/
|
||||
fun load(context: Context) {
|
||||
val sharedPref = context.getSharedPreferences(
|
||||
context.getString(R.string.preference_file_key),
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
|
||||
// load the update times (cache)
|
||||
coursesCacheTime = sharedPref.getLong(context.getString(
|
||||
R.string.save_key_coursesCacheTime
|
||||
), 0)
|
||||
mensaCacheTime = sharedPref.getLong(context.getString(
|
||||
R.string.save_key_mensaCacheTime
|
||||
), 0)
|
||||
timetableCacheTime = sharedPref.getLong(context.getString(
|
||||
R.string.save_key_timetableCacheTime
|
||||
), 0)
|
||||
|
||||
// load saved course
|
||||
cCourse = Course(
|
||||
sharedPref.getString(context.getString(R.string.save_key_courseTTLink),
|
||||
"https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0"
|
||||
)!!,
|
||||
sharedPref.getString(context.getString(R.string.save_key_course), "AI3")!!
|
||||
)
|
||||
|
||||
// load saved colors
|
||||
cColorPrimary = sharedPref.getInt(context.getString(
|
||||
R.string.save_key_colorPrimary
|
||||
), cColorPrimary)
|
||||
cColorAccent = sharedPref.getInt(context.getString(
|
||||
R.string.save_key_colorAccent
|
||||
), cColorAccent)
|
||||
|
||||
// load showBuffet
|
||||
cShowBuffet = sharedPref.getBoolean(context.getString(
|
||||
R.string.save_key_showBuffet
|
||||
), true)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,200 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.fragments
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import kotlinx.android.synthetic.main.fragment_grades.*
|
||||
import kotlinx.coroutines.*
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.controller.QISPOSParser
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.GradeLinearLayout
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
|
||||
|
||||
/**
|
||||
* The grades fragment class
|
||||
* contains all needed parts to display and the grades screen
|
||||
*/
|
||||
class GradesFragment : Fragment() {
|
||||
|
||||
private lateinit var refreshLayoutGrades: SwipeRefreshLayout
|
||||
|
||||
private lateinit var parser: QISPOSParser
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_grades, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
refreshLayoutGrades = view.findViewById(R.id.refreshLayout_Grades)
|
||||
refreshLayoutGrades.isEnabled = false // disable swipe
|
||||
refreshLayoutGrades.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
|
||||
|
||||
parser = QISPOSParser(context!!)// init the parser
|
||||
|
||||
if (checkCredentials() && checkQisposStatus()) {
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
addGrades()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the credentials are set, if not show a login dialog
|
||||
*/
|
||||
private fun checkCredentials(): Boolean {
|
||||
val credentials = EncryptedPreferences.readCredentials(context!!)
|
||||
var credentialsPresent = false
|
||||
|
||||
// if there is no password set, show the login dialog
|
||||
if (credentials.first.isEmpty() || credentials.second.isEmpty()) {
|
||||
LoginDialog(this.context!!)
|
||||
.positiveButton {
|
||||
EncryptedPreferences.saveCredentials(email, password, context)
|
||||
addGrades()
|
||||
}
|
||||
.negativeButton {
|
||||
txtView_Loading.text = resources.getString(R.string.credentials_missing)
|
||||
}
|
||||
.show {
|
||||
email = EncryptedPreferences.email
|
||||
password = ""
|
||||
}
|
||||
} else {
|
||||
credentialsPresent = true
|
||||
}
|
||||
|
||||
return credentialsPresent
|
||||
}
|
||||
|
||||
/**
|
||||
* check if qispos is available, if not show an error
|
||||
*/
|
||||
private fun checkQisposStatus(): Boolean {
|
||||
val statusCode = parser.checkQISPOSStatus()
|
||||
|
||||
// show error if the status code is not 200
|
||||
if (statusCode != 200) {
|
||||
val infoText = resources.getString(when(statusCode) {
|
||||
503 -> R.string.qispos_unavailable
|
||||
else -> R.string.qispos_generic_error
|
||||
})
|
||||
|
||||
val img = ResourcesCompat.getDrawable(resources, R.drawable.ic_error_outline_black_24dp, null)?.apply {
|
||||
bounds = Rect(0, 0, 75, 75)
|
||||
}
|
||||
|
||||
txtView_Loading?.apply {
|
||||
text = infoText
|
||||
setCompoundDrawables(null, null, null, img)
|
||||
}
|
||||
}
|
||||
|
||||
return statusCode == 200
|
||||
}
|
||||
|
||||
// add the grades to the layout, async
|
||||
private fun addGrades() = GlobalScope.launch(Dispatchers.Default) {
|
||||
withContext(Dispatchers.Main) {
|
||||
refreshLayout_Grades.isRefreshing = true
|
||||
}
|
||||
|
||||
val grades = parser.parseGrades()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
linLayout_Grades.removeAllViews() // clear layout
|
||||
}
|
||||
|
||||
// for each semester add a new card
|
||||
grades.forEach { semester ->
|
||||
val usedSubjects = ArrayList<String>()
|
||||
val semesterCard = DayCardView(context!!)
|
||||
semesterCard.setDayHeading(semester.key)
|
||||
|
||||
// for each subject add a new linLayout
|
||||
semester.value.forEachIndexed { index, subject ->
|
||||
if (usedSubjects.contains(subject.name)) {
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
// get the first sub subjects
|
||||
val subSubject = semester.value.firstOrNull {
|
||||
it.name.contains(subject.name) && it.name != subject.name
|
||||
}
|
||||
|
||||
// if sub subject is not null, add it to used subjects
|
||||
subSubject?.let {
|
||||
usedSubjects.add(it.name)
|
||||
}
|
||||
|
||||
val subjectLayout = GradeLinearLayout(context).set {
|
||||
subjectName = subject.name
|
||||
grade = subject.grade
|
||||
subSubjectName = subSubject?.name.toString()
|
||||
subGrade = subSubject?.grade.toString()
|
||||
}
|
||||
|
||||
// disable sub-subject if not set
|
||||
if (subSubject == null)
|
||||
subjectLayout.disableSubSubject()
|
||||
|
||||
// disable divider if last element
|
||||
if (index == semester.value.lastIndex || semester.value.indexOf(subSubject) == semester.value.lastIndex)
|
||||
subjectLayout.disableDivider()
|
||||
|
||||
semesterCard.getLinLayoutDay().addView(subjectLayout)
|
||||
}
|
||||
|
||||
// without context we can't access the view
|
||||
withContext(Dispatchers.Main) {
|
||||
linLayout_Grades.addView(semesterCard)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
val txtViewLegal = TextView(context).apply {
|
||||
text = resources.getString(R.string.without_guarantee)
|
||||
textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||
}
|
||||
|
||||
// stop refreshing and show legal warning
|
||||
withContext(Dispatchers.Main) {
|
||||
linLayout_Grades.addView(txtViewLegal)
|
||||
refreshLayout_Grades.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2018 <seil0@mosad.xyz>
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -22,20 +22,25 @@
|
||||
|
||||
package org.mosad.seil0.projectlaogai.fragments
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import kotlinx.android.synthetic.main.fragment_home.*
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
import org.mosad.seil0.projectlaogai.MainActivity
|
||||
import kotlinx.coroutines.*
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.hsoparser.*
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.LessonCardView
|
||||
import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.mensaMenu
|
||||
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
|
||||
import org.mosad.seil0.projectlaogai.util.Meal
|
||||
import org.mosad.seil0.projectlaogai.util.TimetableDay
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
|
||||
import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@ -44,70 +49,66 @@ import java.util.*
|
||||
*/
|
||||
class HomeFragment : Fragment() {
|
||||
|
||||
private lateinit var linLayoutTimeTable: LinearLayout
|
||||
private var mainActivity = MainActivity()
|
||||
private val className = "HomeFragment"
|
||||
private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault())
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_home, container, false)
|
||||
}
|
||||
|
||||
val view: View = inflater.inflate(R.layout.fragment_home, container, false)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// init UI elements
|
||||
linLayoutTimeTable = view.findViewById(R.id.linLayoutTimeTable)
|
||||
|
||||
//setText()
|
||||
addCurrentMensaMenu()
|
||||
addCurrentTimeTable()
|
||||
|
||||
// Inflate the layout for this fragment
|
||||
return view
|
||||
addMensaMenu()
|
||||
addTimeTable()
|
||||
}
|
||||
|
||||
/**
|
||||
* add the current mensa meal to the home screens
|
||||
*/
|
||||
private fun addCurrentMensaMenu() {
|
||||
doAsync {
|
||||
val dayMenus: ArrayList<Meal>
|
||||
val cal = Calendar.getInstance()
|
||||
private fun addMensaMenu() = GlobalScope.launch(Dispatchers.Default) {
|
||||
|
||||
if (cal.get(Calendar.HOUR_OF_DAY) < 15) {
|
||||
dayMenus = MensaParser().getMensaMenuDay(mainActivity.getWeekMenu(), cal.get(Calendar.DAY_OF_WEEK))
|
||||
} else {
|
||||
dayMenus = MensaParser().getMensaMenuDay(mainActivity.getWeekMenu(), cal.get(Calendar.DAY_OF_WEEK) + 1)
|
||||
uiThread {
|
||||
// TODO Mensa closed today is showing
|
||||
txtView_Menu1Heading.text = resources.getString(R.string.meal_1_tomorrow)
|
||||
txtView_Menu2Heading.text = resources.getString(R.string.meal_2_tomorrow)
|
||||
var dayMeals: ArrayList<Meal>
|
||||
val cal = Calendar.getInstance()
|
||||
val mensaCardView = DayCardView(context!!)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
|
||||
if (isAdded) {
|
||||
if (cal.get(Calendar.HOUR_OF_DAY) < 15) {
|
||||
dayMeals = mensaMenu.currentWeek.days[NotRetardedCalendar.getDayOfWeekIndex()].meals
|
||||
mensaCardView.setDayHeading(activity!!.resources.getString(R.string.today_date, formatter.format(cal.time)))
|
||||
} else {
|
||||
dayMeals = mensaMenu.currentWeek.days[NotRetardedCalendar.getTomorrowWeekIndex()].meals
|
||||
cal.add(Calendar.DATE, 1)
|
||||
mensaCardView.setDayHeading(activity!!.resources.getString(R.string.tomorrow_date, formatter.format(cal.time)))
|
||||
}
|
||||
}
|
||||
|
||||
uiThread {
|
||||
|
||||
if (dayMenus.size >= 2) {
|
||||
if (dayMeals.size >= 2) {
|
||||
// get the index of the first meal, not a "Schneller Teller"
|
||||
loop@ for ((i, meal) in dayMenus.withIndex()) {
|
||||
if(meal.heading.contains("Essen")) {
|
||||
for ((j, part) in dayMenus[i].parts.withIndex()) {
|
||||
txtViewMenu1.append(part)
|
||||
if(j < (dayMenus[i].parts.size - 2))
|
||||
txtViewMenu1.append("\n")
|
||||
}
|
||||
loop@ for ((i, meal) in dayMeals.withIndex()) {
|
||||
if (meal.heading.contains("Essen")) {
|
||||
|
||||
for ((j, part) in dayMenus[i + 1].parts.withIndex()) {
|
||||
txtViewMenu2.append(part)
|
||||
if(j < (dayMenus[i + 1].parts.size - 2))
|
||||
txtViewMenu2.append("\n")
|
||||
}
|
||||
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 {
|
||||
txtViewMenu1.text = resources.getString(R.string.no_meal_today)
|
||||
txtViewMenu2.text = resources.getString(R.string.no_meal_today)
|
||||
mensaCardView.getLinLayoutDay().addView(getNoCard(resources.getString(R.string.mensa_closed)))
|
||||
}
|
||||
|
||||
linLayout_Home.addView(mensaCardView)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -115,51 +116,72 @@ class HomeFragment : Fragment() {
|
||||
/**
|
||||
* add the current timetable to the home screen
|
||||
*/
|
||||
private fun addCurrentTimeTable() {
|
||||
val dayIndex = NotRetardedCalendar().getDayOfWeekIndex()
|
||||
private fun addTimeTable() = GlobalScope.launch(Dispatchers.Default) {
|
||||
|
||||
if (mainActivity.getTimeTableCurrentWeek().isNotEmpty() && dayIndex < 6) {
|
||||
withContext(Dispatchers.Main) {
|
||||
|
||||
val timeTableDay = mainActivity.getTimeTableCurrentWeek()[dayIndex]
|
||||
|
||||
for (i in 0..5) {
|
||||
val lessonCardView = LessonCardView(context!!, null)
|
||||
|
||||
lessonCardView.getTxtViewLesson().text = resources.getString(R.string.string_new_line, timeTableDay[i].lessonSubject)
|
||||
lessonCardView.getTxtViewLesson().append(timeTableDay[i].lessonTeacher + "\n")
|
||||
lessonCardView.getTxtViewLesson().append(timeTableDay[i].lessonRoom)
|
||||
lessonCardView.getTxtViewTime().text = DataTypes().getTime()[i]
|
||||
|
||||
if(lessonCardView.getTxtViewLesson().text.length > 2)
|
||||
linLayoutTimeTable.addView(lessonCardView)
|
||||
if (isAdded && TimetableController.timetable.isNotEmpty()) {
|
||||
try {
|
||||
val dayCardView = findNextDay(NotRetardedCalendar.getDayOfWeekIndex())
|
||||
linLayout_Home.addView(dayCardView)
|
||||
} catch (ex: Exception) {
|
||||
Log.e(className, "could not load timetable", ex) // TODO send feedback
|
||||
}
|
||||
}
|
||||
|
||||
// add a card if there is no lesson today
|
||||
if (linLayoutTimeTable.childCount == 0) {
|
||||
// TODO we could display the next day with a lecture
|
||||
val noLessonCardView = LessonCardView(context!!, null)
|
||||
noLessonCardView.getTxtViewLesson().text = resources.getString(R.string.no_lesson_today)
|
||||
linLayoutTimeTable.addView(noLessonCardView)
|
||||
}
|
||||
} else {
|
||||
if (dayIndex == 6) {
|
||||
// if that's the case it's sunday
|
||||
val noLessonCardView = LessonCardView(context!!, null)
|
||||
noLessonCardView.getTxtViewLesson().text = resources.getString(R.string.no_lesson_today)
|
||||
linLayoutTimeTable.addView(noLessonCardView)
|
||||
} else {
|
||||
MaterialDialog(context!!)
|
||||
.title(R.string.error)
|
||||
.message(R.string.gen_tt_error)
|
||||
.show()
|
||||
// TODO log the error and send feedback
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun setMainActivity(mainActivity: MainActivity) {
|
||||
this.mainActivity = mainActivity
|
||||
/**
|
||||
* find the next day with a lesson
|
||||
* start at week 0, startDayIndex and search every cached week until we find a) a day with a timetable
|
||||
* or b) we find no timetable and add a no lesson card
|
||||
* @param startDayIndex the day index you want to start searching
|
||||
* @return a DayCardView with all lessons added
|
||||
*/
|
||||
private fun findNextDay(startDayIndex: Int): DayCardView {
|
||||
val dayCardView = DayCardView(context!!)
|
||||
var dayTimetable: TimetableDay? = null
|
||||
var dayIndexSearch = startDayIndex
|
||||
var weekIndexSearch = 0
|
||||
|
||||
while (dayTimetable == null && weekIndexSearch < TimetableController.timetable.size) {
|
||||
for (dayIndex in dayIndexSearch..5) {
|
||||
dayTimetable = TimetableController.timetable[weekIndexSearch].days[dayIndex]
|
||||
|
||||
// some wired calendar magic, calculate the correct date to be shown ((timetable week - current week * 7) + (dayIndex - dayIndex of current week)
|
||||
val daysToAdd = ((TimetableController.timetable[weekIndexSearch].weekNumberYear - NotRetardedCalendar.getWeekOfYear())
|
||||
* 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex()))
|
||||
dayCardView.addTimetableDay(dayTimetable, daysToAdd)
|
||||
|
||||
// if there are no lessons don't show the dayCardView
|
||||
if (dayCardView.getLinLayoutDay().childCount > 1)
|
||||
return dayCardView
|
||||
}
|
||||
|
||||
weekIndexSearch++
|
||||
dayIndexSearch = 0
|
||||
}
|
||||
|
||||
// there was no day found in the cached weeks, add no lesson card
|
||||
dayCardView.setDayHeading(formatter.format(Calendar.getInstance().time))
|
||||
dayCardView.getLinLayoutDay().addView(getNoCard(resources.getString(R.string.no_lesson_today))) // if there is no lecture at all show the no lesson card
|
||||
return dayCardView
|
||||
}
|
||||
|
||||
/**
|
||||
* @param text the text to show on the card
|
||||
* @return a TextView with the text and all needed parameters
|
||||
*/
|
||||
private fun getNoCard(text: String): TextView {
|
||||
val noLesson = TextView(context)
|
||||
noLesson.text = text
|
||||
noLesson.textSize = 18.0F
|
||||
noLesson.setTypeface(null, Typeface.BOLD)
|
||||
noLesson.textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||
noLesson.setPadding(7, 7, 7, 7)
|
||||
|
||||
return noLesson
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2018 <seil0@mosad.xyz>
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -23,18 +23,25 @@
|
||||
package org.mosad.seil0.projectlaogai.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
import org.mosad.seil0.projectlaogai.MainActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import kotlinx.android.synthetic.main.fragment_mensa.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.MensaDayCardView
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.MenuCardView
|
||||
import java.util.*
|
||||
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
|
||||
import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.mensaMenu
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cShowBuffet
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.TextViewInfo
|
||||
import org.mosad.seil0.projectlaogai.util.MensaWeek
|
||||
import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
|
||||
|
||||
/**
|
||||
* The mensa controller class
|
||||
@ -42,78 +49,109 @@ import java.util.*
|
||||
*/
|
||||
class MensaFragment : Fragment() {
|
||||
|
||||
private lateinit var linLayoutMensaFragment: LinearLayout
|
||||
private var mainActivity = MainActivity()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
|
||||
val view: View = inflater.inflate(R.layout.fragment_mensa, container, false)
|
||||
|
||||
linLayoutMensaFragment = view.findViewById(R.id.linLayout_MensaFragment)
|
||||
|
||||
addCurrentWeek()
|
||||
|
||||
return view
|
||||
return inflater.inflate(R.layout.fragment_mensa, container, false)
|
||||
}
|
||||
|
||||
private fun addCurrentWeek() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
doAsync {
|
||||
refreshLayout_Mensa.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
|
||||
|
||||
uiThread {
|
||||
refreshAction() // init actions
|
||||
|
||||
for(day in Calendar.getInstance().get(Calendar.DAY_OF_WEEK)..7) {
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
val dayCurrent = if(NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
|
||||
addWeek(mensaMenu.currentWeek, dayCurrent).join()
|
||||
|
||||
val strDay: String = when(day) {
|
||||
Calendar.MONDAY -> "Mon"
|
||||
Calendar.TUESDAY -> "Die"
|
||||
Calendar.WEDNESDAY -> "Mit"
|
||||
Calendar.THURSDAY -> "Don"
|
||||
Calendar.FRIDAY -> "Fre"
|
||||
Calendar.SATURDAY -> "Sam"
|
||||
else -> "TODAY" // the app will likely crash here
|
||||
// add the next week
|
||||
addWeek(mensaMenu.nextWeek, 0)
|
||||
}
|
||||
|
||||
// show a info if there are no more menus
|
||||
if (linLayout_Mensa.childCount == 0) {
|
||||
val txtViewInfo = TextViewInfo(context!!).set {
|
||||
txt = resources.getString(R.string.no_more_meals)
|
||||
}
|
||||
linLayout_Mensa.addView(txtViewInfo)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add all menus from dayStart to Friday for a given week
|
||||
* @param menusWeek menu of type MensaWeek you want to add
|
||||
* @param dayStart the first day of the week to add
|
||||
*/
|
||||
private fun addWeek(menusWeek: MensaWeek, dayStart: Int) = GlobalScope.launch(Dispatchers.Default) {
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
// only add the days dayStart to Fri since the mensa is closed on Sat/Sun
|
||||
for (dayIndex in dayStart..4) {
|
||||
var helpMeal = MealLinearLayout(context)
|
||||
val dayCardView = DayCardView(context!!)
|
||||
|
||||
if(menusWeek.days[dayIndex].meals.isNotEmpty())
|
||||
dayCardView.setDayHeading(menusWeek.days[dayIndex].meals[0].day)
|
||||
|
||||
for (meal in menusWeek.days[dayIndex].meals) {
|
||||
val mealLayout = MealLinearLayout(context)
|
||||
mealLayout.setMeal(meal)
|
||||
|
||||
if(meal.heading != "Buffet" || cShowBuffet) {
|
||||
dayCardView.getLinLayoutDay().addView(mealLayout)
|
||||
helpMeal = mealLayout
|
||||
}
|
||||
|
||||
val cardViewMensaDay = MensaDayCardView(context!!, null)
|
||||
var add = false
|
||||
|
||||
for (meal in mainActivity.getWeekMenu()) {
|
||||
//println("Day: " + meal.day)
|
||||
if (meal.day.contains(strDay)) {
|
||||
|
||||
val menuViewMenu = MenuCardView(context!!, null)
|
||||
menuViewMenu.setMenuHeading(meal.heading)
|
||||
|
||||
for ((i, part) in meal.parts.withIndex()) {
|
||||
menuViewMenu.getTxtViewMenu().append(part)
|
||||
if(i < (meal.parts.size - 2))
|
||||
menuViewMenu.getTxtViewMenu().append("\n")
|
||||
}
|
||||
|
||||
cardViewMensaDay.setDayHeading(meal.day) //TODO move this out of the first for loop, performance!!
|
||||
cardViewMensaDay.getLinLayoutMensaDay().addView(menuViewMenu)
|
||||
add = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(add)
|
||||
linLayoutMensaFragment.addView(cardViewMensaDay)
|
||||
}
|
||||
|
||||
// add a card if there are no more meals in this week
|
||||
if(linLayoutMensaFragment.childCount == 0) {
|
||||
val cardViewNoMoreFood = MensaDayCardView(context!!, null)
|
||||
cardViewNoMoreFood.setDayHeading(resources.getString(R.string.no_more_food))
|
||||
linLayoutMensaFragment.addView(cardViewNoMoreFood)
|
||||
}
|
||||
helpMeal.disableDivider()
|
||||
|
||||
if(dayCardView.getLinLayoutDay().childCount > 2)
|
||||
linLayout_Mensa.addView(dayCardView)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* initialize the refresh action
|
||||
*/
|
||||
private fun refreshAction() = GlobalScope.launch(Dispatchers.Default) {
|
||||
withContext(Dispatchers.Main) {
|
||||
// set the refresh listener
|
||||
refreshLayout_Mensa.setOnRefreshListener {
|
||||
updateMensaScreen()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun setMainActivity(mainActivity: MainActivity) {
|
||||
this.mainActivity = mainActivity
|
||||
/**
|
||||
* refresh the mensa cache and update the mensa screen
|
||||
*/
|
||||
private fun updateMensaScreen() = GlobalScope.launch(Dispatchers.Default) {
|
||||
CacheController.updateMensaMenu(context!!).join() // blocking since we want the new data
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
// remove all menus from the layout
|
||||
linLayout_Mensa.removeAllViews()
|
||||
|
||||
// add the refreshed menus
|
||||
val dayCurrent = if (NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
|
||||
addWeek(mensaMenu.currentWeek, dayCurrent).join()
|
||||
|
||||
// add the next week
|
||||
addWeek(mensaMenu.nextWeek, 0)
|
||||
|
||||
refreshLayout_Mensa.isRefreshing = false
|
||||
|
||||
// show a info if there are no more menus
|
||||
if (linLayout_Mensa.childCount == 0) {
|
||||
val txtViewInfo = TextViewInfo(context!!).set {
|
||||
txt = resources.getString(R.string.no_more_meals)
|
||||
}
|
||||
linLayout_Mensa.addView(txtViewInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,3 +1,25 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
@ -5,8 +27,10 @@ import androidx.fragment.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.fragment_moodle.*
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import android.webkit.WebViewClient
|
||||
|
||||
/**
|
||||
* The moodle screen controller class
|
||||
@ -14,18 +38,22 @@ import org.mosad.seil0.projectlaogai.R
|
||||
*/
|
||||
class MoodleFragment : Fragment() {
|
||||
|
||||
private lateinit var webView: WebView
|
||||
private lateinit var webSettings: WebSettings
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val view: View = inflater.inflate(R.layout.fragment_settings, container, false)
|
||||
|
||||
//webView.loadUrl("www.google.de")
|
||||
|
||||
return view
|
||||
return inflater.inflate(R.layout.fragment_moodle, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
//webView.loadUrl("www.google.de")
|
||||
}
|
||||
webView = view.findViewById(R.id.webView)
|
||||
webView.loadUrl("https://elearning.hs-offenburg.de/moodle/")
|
||||
|
||||
webSettings = webView.settings
|
||||
//webSettings.setJavaScriptEnabled(true) // Enable Javascript
|
||||
|
||||
webView.webViewClient = WebViewClient() // Force links and redirects to open in the WebView instead of in a browser
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2018 <seil0@mosad.xyz>
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -22,24 +22,43 @@
|
||||
|
||||
package org.mosad.seil0.projectlaogai.fragments
|
||||
|
||||
import android.graphics.Color
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.afollestad.aesthetic.Aesthetic
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.WhichButton
|
||||
import com.afollestad.materialdialogs.actions.getActionButton
|
||||
import com.afollestad.materialdialogs.callbacks.onDismiss
|
||||
import com.afollestad.materialdialogs.color.colorChooser
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import com.afollestad.materialdialogs.list.listItems
|
||||
import com.afollestad.materialdialogs.list.listItemsMultiChoice
|
||||
import com.afollestad.materialdialogs.list.listItemsSingleChoice
|
||||
import de.psdev.licensesdialog.LicensesDialog
|
||||
import kotlinx.android.synthetic.main.fragment_settings.*
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
import org.mosad.seil0.projectlaogai.MainActivity
|
||||
import kotlinx.coroutines.*
|
||||
import org.mosad.seil0.projectlaogai.BuildConfig
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.hsoparser.CourseTTLink
|
||||
import java.util.ArrayList
|
||||
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
|
||||
import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.coursesList
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorAccent
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorPrimary
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cShowBuffet
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
|
||||
import org.mosad.seil0.projectlaogai.util.DataTypes
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* The settings controller class
|
||||
@ -49,33 +68,68 @@ class SettingsFragment : Fragment() {
|
||||
|
||||
private lateinit var linLayoutUser: LinearLayout
|
||||
private lateinit var linLayoutCourse: LinearLayout
|
||||
private lateinit var linLayoutInfo: LinearLayout
|
||||
private lateinit var linLayoutMainColor: LinearLayout
|
||||
private lateinit var viewPrimaryColor: View
|
||||
private lateinit var courseTTLinkList: ArrayList<CourseTTLink>
|
||||
private var mainActivity = MainActivity()
|
||||
private lateinit var linLayoutManageLessons: LinearLayout
|
||||
private lateinit var linLayoutAbout: LinearLayout
|
||||
private lateinit var linLayoutLicence: LinearLayout
|
||||
private lateinit var linLayoutTheme: LinearLayout
|
||||
private lateinit var linLayoutPrimaryColor: LinearLayout
|
||||
private lateinit var linLayoutAccentColor: LinearLayout
|
||||
private lateinit var switchBuffet: SwitchCompat
|
||||
private lateinit var txtViewCourse: TextView
|
||||
|
||||
private var selectedTheme = DataTypes.Theme.Light
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
|
||||
val view: View = inflater.inflate(R.layout.fragment_settings, container, false)
|
||||
|
||||
linLayoutUser = view.findViewById(R.id.linLayout_User)
|
||||
linLayoutCourse = view.findViewById(R.id.linLayout_Course)
|
||||
linLayoutInfo = view.findViewById(R.id.linLayout_Info)
|
||||
linLayoutMainColor = view.findViewById(R.id.linLayout_MainColor)
|
||||
viewPrimaryColor = view.findViewById(R.id.view_PrimaryColor)
|
||||
|
||||
initActions()
|
||||
|
||||
// Inflate the layout for this fragment
|
||||
return view
|
||||
return inflater.inflate(R.layout.fragment_settings, container, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* initialize the settings gui
|
||||
*/
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
txtView_Course.text = mainActivity.getCourse().course
|
||||
linLayoutUser = view.findViewById(R.id.linLayout_User)
|
||||
linLayoutCourse = view.findViewById(R.id.linLayout_Course)
|
||||
linLayoutManageLessons = view.findViewById(R.id.linLayout_ManageLessons)
|
||||
linLayoutAbout = view.findViewById(R.id.linLayout_About)
|
||||
linLayoutLicence = view.findViewById(R.id.linLayout_Licence)
|
||||
linLayoutTheme = view.findViewById(R.id.linLayout_Theme)
|
||||
linLayoutPrimaryColor = view.findViewById(R.id.linLayout_PrimaryColor)
|
||||
linLayoutAccentColor = view.findViewById(R.id.linLayout_AccentColor)
|
||||
switchBuffet = view.findViewById(R.id.switch_buffet)
|
||||
|
||||
// if we call txtView_Course via KAE view binding it'll result in a NPE in the onDismissed call
|
||||
txtViewCourse = view.findViewById(R.id.txtView_Course)
|
||||
|
||||
initActions()
|
||||
|
||||
|
||||
txtView_User.text = EncryptedPreferences.email.ifEmpty { resources.getString(R.string.sample_user) }
|
||||
txtView_Course.text = cCourse.courseName
|
||||
txtView_AboutDesc.text = resources.getString(R.string.about_version, BuildConfig.VERSION_NAME, getString(R.string.build_time))
|
||||
switch_buffet.isChecked = cShowBuffet // init switch
|
||||
|
||||
val outValue = TypedValue()
|
||||
activity!!.theme.resolveAttribute(R.attr.themeName, outValue, true)
|
||||
when(outValue.string) {
|
||||
"light" -> {
|
||||
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryLight, activity!!.theme))
|
||||
selectedTheme = DataTypes.Theme.Light
|
||||
selectedTheme.string = resources.getString(R.string.themeLight)
|
||||
}
|
||||
"dark" -> {
|
||||
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryDark, activity!!.theme))
|
||||
selectedTheme = DataTypes.Theme.Dark
|
||||
selectedTheme.string = resources.getString(R.string.themeDark)
|
||||
}
|
||||
"black" -> {
|
||||
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryDark, activity!!.theme))
|
||||
selectedTheme = DataTypes.Theme.Black
|
||||
selectedTheme.string = resources.getString(R.string.themeBlack)
|
||||
}
|
||||
}
|
||||
txtView_SelectedTheme.text = selectedTheme.string
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,61 +138,187 @@ class SettingsFragment : Fragment() {
|
||||
private fun initActions() {
|
||||
linLayoutUser.setOnClickListener {
|
||||
// open a new dialog
|
||||
LoginDialog(context!!)
|
||||
.positiveButton {
|
||||
EncryptedPreferences.saveCredentials(email, password, context)
|
||||
}
|
||||
.show {
|
||||
email = EncryptedPreferences.email
|
||||
password = ""
|
||||
}
|
||||
}
|
||||
|
||||
linLayoutUser.setOnLongClickListener {
|
||||
Preferences.oGiants = true // enable easter egg
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
|
||||
linLayoutCourse.setOnClickListener {
|
||||
// open a new dialog
|
||||
val courseList = ArrayList<String>()
|
||||
selectCourse(context!!).show {
|
||||
onDismiss {
|
||||
txtViewCourse.text = cCourse.courseName // update txtView after the dialog is dismissed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
courseTTLinkList = mainActivity.getCourseTTLinkList()
|
||||
courseTTLinkList.forEach { (_, course) ->
|
||||
courseList.add(course)
|
||||
linLayoutManageLessons.setOnClickListener {
|
||||
val lessons = ArrayList<String>()
|
||||
TimetableController.subjectMap.forEach { pair ->
|
||||
pair.value.forEach {
|
||||
lessons.add("${pair.key} - $it")
|
||||
}
|
||||
}
|
||||
|
||||
MaterialDialog(context!!).listItems(items = courseList){ _, index, text ->
|
||||
txtView_Course.text = text // update txtView
|
||||
MaterialDialog(context!!).show {
|
||||
title(R.string.manage_lessons)
|
||||
positiveButton(R.string.delete)
|
||||
negativeButton(R.string.cancel)
|
||||
getActionButton(WhichButton.POSITIVE).updateTextColor(cColorAccent)
|
||||
getActionButton(WhichButton.NEGATIVE).updateTextColor(cColorAccent)
|
||||
|
||||
val dialog = MaterialDialog(context!!).cancelable(false)
|
||||
listItemsMultiChoice(items = lessons) { _, _, items ->
|
||||
items.forEach {
|
||||
val list = it.split(" - ")
|
||||
TimetableController.removeSubject(list[0], list[1], context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
linLayoutAbout.setOnClickListener {
|
||||
// open a new info dialog
|
||||
MaterialDialog(context!!)
|
||||
.title(R.string.about_dialog_heading)
|
||||
.message(R.string.about_dialog_text)
|
||||
.show()
|
||||
}
|
||||
|
||||
linLayoutLicence.setOnClickListener {
|
||||
// do the theme magic, as the lib's theme support is broken
|
||||
val outValue = TypedValue()
|
||||
context!!.theme.resolveAttribute(R.attr.themeName, outValue, true)
|
||||
|
||||
val dialogCss = when (outValue.string) {
|
||||
"light" -> R.string.license_dialog_style_light
|
||||
else -> R.string.license_dialog_style_dark
|
||||
}
|
||||
|
||||
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 {
|
||||
title(R.string.theme)
|
||||
listItemsSingleChoice(items = themes, initialSelection = selectedTheme.ordinal) { _, index, _ ->
|
||||
Aesthetic.config {
|
||||
when (index) {
|
||||
0 -> activityTheme(R.style.AppTheme_Light)
|
||||
1 -> activityTheme(R.style.AppTheme_Dark)
|
||||
2 -> activityTheme(R.style.AppTheme_Black)
|
||||
else -> activityTheme(R.style.AppTheme_Light)
|
||||
}
|
||||
apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
linLayoutPrimaryColor.setOnClickListener {
|
||||
// open a new color chooser dialog
|
||||
MaterialDialog(context!!)
|
||||
.colorChooser(DataTypes().primaryColors, allowCustomArgb = true, initialSelection = cColorPrimary) { _, color ->
|
||||
view_PrimaryColor.setBackgroundColor(color)
|
||||
Aesthetic.config {
|
||||
colorPrimary(color)
|
||||
colorPrimaryDark(color)
|
||||
apply()
|
||||
}
|
||||
Preferences.saveColorPrimary(context!!, color)
|
||||
}
|
||||
.show {
|
||||
title(R.string.primary_color)
|
||||
positiveButton(R.string.select)
|
||||
getActionButton(WhichButton.POSITIVE).updateTextColor(cColorAccent)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
linLayoutAccentColor.setOnClickListener {
|
||||
// open a new color chooser dialog
|
||||
MaterialDialog(context!!)
|
||||
.colorChooser(DataTypes().accentColors, allowCustomArgb = true, initialSelection = cColorAccent) { _, color ->
|
||||
view_AccentColor.setBackgroundColor(color)
|
||||
Aesthetic.config {
|
||||
colorAccent(color)
|
||||
apply()
|
||||
}
|
||||
|
||||
Preferences.saveColorAccent(context!!, color)
|
||||
}
|
||||
.show{
|
||||
title(R.string.accent_color)
|
||||
positiveButton(R.string.select)
|
||||
getActionButton(WhichButton.POSITIVE).updateTextColor(cColorAccent)
|
||||
}
|
||||
}
|
||||
|
||||
switchBuffet.setOnClickListener {
|
||||
Preferences.saveShowBuffet(context!!, switchBuffet.isChecked)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun selectCourse(context: Context) : MaterialDialog {
|
||||
val courseNameList = ArrayList<String>()
|
||||
coursesList.forEach { (_, courseName) ->
|
||||
courseNameList.add(courseName)
|
||||
}
|
||||
|
||||
// return a new course selection dialog
|
||||
return MaterialDialog(context)
|
||||
.title(R.string.select_course)
|
||||
.listItems(items = courseNameList) { _, index, _ ->
|
||||
|
||||
val loadingDialog = MaterialDialog(context).cancelable(false)
|
||||
.cancelOnTouchOutside(false)
|
||||
.customView(R.layout.dialog_loading)
|
||||
dialog.show()
|
||||
loadingDialog.show()
|
||||
|
||||
doAsync {
|
||||
mainActivity.updateCourse(courseTTLinkList[index])
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
Preferences.saveCourse(context, coursesList[index]) // save the course
|
||||
|
||||
uiThread {
|
||||
dialog.dismiss()
|
||||
// update current & next weeks timetable
|
||||
val threads = listOf(
|
||||
CacheController.updateTimetable(cCourse.courseName, 0, context),
|
||||
CacheController.updateTimetable(cCourse.courseName, 1, context)
|
||||
)
|
||||
threads.joinAll() // blocking since we want the new data
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
linLayoutInfo.setOnClickListener {
|
||||
// open a new info dialog
|
||||
MaterialDialog(context!!)
|
||||
.title(R.string.about)
|
||||
.message(R.string.about_text)
|
||||
.show()
|
||||
}
|
||||
|
||||
linLayoutMainColor.setOnClickListener {
|
||||
// open a new color chooser dialog
|
||||
val colors = intArrayOf(Color.BLACK, Color.RED, Color.GREEN, Color.BLUE)
|
||||
|
||||
MaterialDialog(context!!)
|
||||
.title(R.string.primary_color)
|
||||
.colorChooser(colors, initialSelection = Color.BLACK) { _, color ->
|
||||
viewPrimaryColor.setBackgroundColor(color)
|
||||
}
|
||||
.positiveButton(R.string.select)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun setMainActivity(mainActivity: MainActivity) {
|
||||
this.mainActivity = mainActivity
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2018 <seil0@mosad.xyz>
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -22,23 +22,23 @@
|
||||
|
||||
package org.mosad.seil0.projectlaogai.fragments
|
||||
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
import org.mosad.seil0.projectlaogai.MainActivity
|
||||
import android.widget.ScrollView
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import kotlinx.android.synthetic.main.fragment_timetable.*
|
||||
import kotlinx.coroutines.*
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.hsoparser.DataTypes
|
||||
import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.LessonCardView
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.MensaDayCardView
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
|
||||
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController.Companion.timetable
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.AddSubjectDialog
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
||||
import org.mosad.seil0.projectlaogai.uicomponents.TextViewInfo
|
||||
import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
|
||||
|
||||
/**
|
||||
* The timetable controller class
|
||||
@ -46,95 +46,105 @@ import java.util.*
|
||||
*/
|
||||
class TimeTableFragment : Fragment() {
|
||||
|
||||
private lateinit var linLayoutTTFragment: LinearLayout
|
||||
private var mainActivity = MainActivity()
|
||||
private lateinit var scrollViewTimetable: ScrollView
|
||||
private lateinit var faBtnAddSubject: FloatingActionButton
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_timetable, container, false)
|
||||
}
|
||||
|
||||
val view: View = inflater.inflate(R.layout.fragment_time_table, container, false)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
linLayoutTTFragment = view.findViewById(R.id.linLayout_TTFragment)
|
||||
scrollViewTimetable = view.findViewById(R.id.scrollView_Timetable)
|
||||
faBtnAddSubject = view.findViewById(R.id.faBtnAddSubject)
|
||||
refreshLayout_Timetable.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
|
||||
|
||||
if (mainActivity.getTimeTableCurrentWeek().isNotEmpty()) {
|
||||
addCurrentWeek()
|
||||
initActions() // init actions
|
||||
|
||||
if (timetable.size > 1 && timetable[0].days.isNotEmpty() && timetable[1].days.isNotEmpty()) {
|
||||
initTimetable()
|
||||
} else {
|
||||
// TODO show card with error msg
|
||||
val txtViewInfo = TextViewInfo(context!!).set {
|
||||
txt = resources.getString(R.string.timetable_generic_error)
|
||||
}.showImage()
|
||||
linLayout_Timetable.addView(txtViewInfo)
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
/**
|
||||
* add the remaining days of the current week to the timetable screen
|
||||
* initialize the actions
|
||||
*/
|
||||
private fun addCurrentWeek() {
|
||||
val dayIndex = NotRetardedCalendar().getDayOfWeekIndex()
|
||||
val formatter = SimpleDateFormat("E dd.MM", Locale.GERMANY) // TODO change to android call when min api is 24
|
||||
val calendar = Calendar.getInstance()
|
||||
private fun initActions() = GlobalScope.launch(Dispatchers.Main) {
|
||||
|
||||
doAsync {
|
||||
refreshLayout_Timetable.setOnRefreshListener {
|
||||
runBlocking { TimetableController.update(context!!).joinAll() }
|
||||
reloadTimetableUI()
|
||||
}
|
||||
|
||||
uiThread {
|
||||
// show the AddLessonDialog if the ftaBtn is clicked
|
||||
faBtnAddSubject.setOnClickListener {
|
||||
AddSubjectDialog(context!!)
|
||||
.positiveButton {
|
||||
TimetableController.addSubject(selectedCourse, selectedSubject, context)
|
||||
runBlocking { reloadTimetableUI() }
|
||||
}.show()
|
||||
}
|
||||
|
||||
// add current weeks days
|
||||
for(day in dayIndex..5) {
|
||||
val cardViewTimeTableDay = MensaDayCardView(context!!, null)
|
||||
cardViewTimeTableDay.setDayHeading(formatter.format(calendar.time))
|
||||
|
||||
// for each lessen of the day
|
||||
for((i, lesson) in mainActivity.getTimeTableCurrentWeek()[day].withIndex()) {
|
||||
val lessonCardView = LessonCardView(context!!, null)
|
||||
lessonCardView.setBackgroundColor(Color.TRANSPARENT)
|
||||
|
||||
lessonCardView.getTxtViewLesson().text = resources.getString(R.string.string_new_line, lesson.lessonSubject)
|
||||
lessonCardView.getTxtViewLesson().append(lesson.lessonTeacher + "\n")
|
||||
lessonCardView.getTxtViewLesson().append(lesson.lessonRoom)
|
||||
lessonCardView.getTxtViewTime().text = DataTypes().getTime()[i]
|
||||
|
||||
if(lessonCardView.getTxtViewLesson().text.length > 2)
|
||||
cardViewTimeTableDay.getLinLayoutMensaDay().addView(lessonCardView)
|
||||
}
|
||||
|
||||
calendar.add(Calendar.DATE,1)
|
||||
linLayoutTTFragment.addView(cardViewTimeTableDay)
|
||||
}
|
||||
|
||||
// add next weeks days, max number = dayIndex, if timetable was loaded
|
||||
if (mainActivity.getTimeTableNextWeek().isNotEmpty()) {
|
||||
calendar.add(Calendar.DATE,1) // before this we are at a sunday (no lecture on sundays!)
|
||||
|
||||
for(day in 0..(dayIndex - 1)) {
|
||||
|
||||
val cardViewTimeTableDay = MensaDayCardView(context!!, null)
|
||||
cardViewTimeTableDay.setDayHeading(formatter.format(calendar.time))
|
||||
|
||||
// for each lessen of the day
|
||||
for((i, lesson) in mainActivity.getTimeTableNextWeek()[day].withIndex()) {
|
||||
val lessonCardView = LessonCardView(context!!, null)
|
||||
lessonCardView.setBackgroundColor(Color.TRANSPARENT)
|
||||
|
||||
lessonCardView.getTxtViewLesson().text = resources.getString(R.string.string_new_line, lesson.lessonSubject)
|
||||
lessonCardView.getTxtViewLesson().append(lesson.lessonTeacher + "\n")
|
||||
lessonCardView.getTxtViewLesson().append(lesson.lessonRoom)
|
||||
lessonCardView.getTxtViewTime().text = DataTypes().getTime()[i]
|
||||
|
||||
if(lessonCardView.getTxtViewLesson().text.length > 2)
|
||||
cardViewTimeTableDay.getLinLayoutMensaDay().addView(lessonCardView)
|
||||
}
|
||||
|
||||
calendar.add(Calendar.DATE,1)
|
||||
linLayoutTTFragment.addView(cardViewTimeTableDay)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO if there is no lesson at one day , show a no lesson card
|
||||
// hide the btnCardValue if the user is scrolling down
|
||||
scrollViewTimetable.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||
if (scrollY > oldScrollY) {
|
||||
faBtnAddSubject.hide()
|
||||
} else {
|
||||
faBtnAddSubject.show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* add the current and next weeks lessons
|
||||
*/
|
||||
private fun initTimetable() = GlobalScope.launch(Dispatchers.Default) {
|
||||
val currentDayIndex = NotRetardedCalendar.getDayOfWeekIndex()
|
||||
|
||||
addTimetableWeek(currentDayIndex, 5, 0).join() // add current week
|
||||
addTimetableWeek(0, currentDayIndex - 1, 1) // add next week
|
||||
}
|
||||
|
||||
private fun addTimetableWeek(dayBegin: Int, dayEnd: Int, week: Int) = GlobalScope.launch(Dispatchers.Main) {
|
||||
for (dayIndex in dayBegin..dayEnd) {
|
||||
val dayCardView = DayCardView(context!!)
|
||||
|
||||
// some wired calendar magic, calculate the correct date to be shown
|
||||
// ((timetable week - current week * 7) + (dayIndex - dayIndex of current week)
|
||||
val daysToAdd = ((timetable[week].weekNumberYear - NotRetardedCalendar.getWeekOfYear())
|
||||
* 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex()))
|
||||
dayCardView.addTimetableDay(timetable[week].days[dayIndex], daysToAdd)
|
||||
|
||||
// if there are no lessons don't show the dayCardView
|
||||
if (dayCardView.getLinLayoutDay().childCount > 1)
|
||||
linLayout_Timetable.addView(dayCardView)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun setMainActivity(mainActivity: MainActivity) {
|
||||
this.mainActivity = mainActivity
|
||||
/**
|
||||
* clear linLayout_Timetable, add the updated timetable
|
||||
*/
|
||||
private fun reloadTimetableUI() = GlobalScope.launch(Dispatchers.Default) {
|
||||
withContext(Dispatchers.Main) {
|
||||
// remove all lessons from the layout
|
||||
linLayout_Timetable.removeAllViews()
|
||||
|
||||
// add the refreshed timetables
|
||||
val dayIndex = NotRetardedCalendar.getDayOfWeekIndex()
|
||||
|
||||
addTimetableWeek(dayIndex, 5, 0).join() // add current week
|
||||
addTimetableWeek(0, dayIndex - 1, 1) // add next week
|
||||
|
||||
refreshLayout_Timetable.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,63 +0,0 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2018 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.hsoparser
|
||||
|
||||
import java.util.*
|
||||
|
||||
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")
|
||||
|
||||
init {
|
||||
// do something
|
||||
}
|
||||
|
||||
fun getTime(): Array<String> {
|
||||
return times
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class NotRetardedCalendar {
|
||||
private val calendar = Calendar.getInstance()!!
|
||||
|
||||
fun getDayOfWeekIndex(): Int {
|
||||
return when(calendar.get(Calendar.DAY_OF_WEEK)) {
|
||||
Calendar.MONDAY -> 0
|
||||
Calendar.TUESDAY -> 1
|
||||
Calendar.WEDNESDAY -> 2
|
||||
Calendar.THURSDAY -> 3
|
||||
Calendar.FRIDAY -> 4
|
||||
Calendar.SATURDAY -> 5
|
||||
Calendar.SUNDAY -> 6
|
||||
else -> 7
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
data class Lesson(val lessonSubject: String, val lessonTeacher: String, val lessonRoom:String, val lessonRemark: String)
|
||||
|
||||
data class CourseTTLink(val courseTTLink: String, val course: String)
|
||||
|
||||
data class Meal(val day: String, val heading: String, val parts: ArrayList<String>, val additives: String)
|
@ -1,80 +0,0 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2018 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.hsoparser
|
||||
|
||||
import org.jsoup.Jsoup
|
||||
import java.util.*
|
||||
|
||||
class MensaParser {
|
||||
private var mealList = ArrayList<Meal>()
|
||||
|
||||
init {
|
||||
// do something
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the mensa menu for the whole week
|
||||
*/
|
||||
fun getMensaMenu(): ArrayList<Meal> {
|
||||
val menuHTML = Jsoup.connect("https://www.swfr.de/de/essen-trinken/speiseplaene/mensa-offenburg/").get()
|
||||
|
||||
menuHTML.select("#speiseplan-tabs").select("div.tab-content").select("div.menu-tagesplan").forEachIndexed { _, element ->
|
||||
val day = element.select("h3").text()
|
||||
for (i in 0 .. (element.select("div.row h4").size - 1)) {
|
||||
try {
|
||||
val heading = element.select("div.row h4")[i].text()
|
||||
val parts = ArrayList<String>(element.select("div.row").select("div.menu-info")[i].html().substringBefore("<span").replace("<br>", " ").split("\n"))
|
||||
val additives = element.select("div.row").select("div.menu-info")[i].select("span.show-with-allergenes").text()
|
||||
|
||||
mealList.add(Meal(day, heading, parts, additives))
|
||||
} catch (e: Exception) {
|
||||
// catch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mealList
|
||||
}
|
||||
|
||||
/**
|
||||
* return the mensa menu of a given day (Mon - Sat)
|
||||
*/
|
||||
fun getMensaMenuDay(mealList: ArrayList<Meal>, day: Int): ArrayList<Meal> {
|
||||
val dayMenus = ArrayList<Meal>()
|
||||
val strDay: String = when (day) {
|
||||
Calendar.MONDAY -> "Mon"
|
||||
Calendar.TUESDAY -> "Die"
|
||||
Calendar.WEDNESDAY -> "Mit"
|
||||
Calendar.THURSDAY -> "Don"
|
||||
Calendar.FRIDAY -> "Fre"
|
||||
Calendar.SATURDAY -> "Sam"
|
||||
else -> "TODAY"
|
||||
}
|
||||
|
||||
for (meal in mealList) {
|
||||
if (meal.day.contains(strDay))
|
||||
dayMenus.add(meal)
|
||||
}
|
||||
return dayMenus
|
||||
}
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2018 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.hsoparser
|
||||
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
class TimeTableParser {
|
||||
private val days = arrayOf("Monday", "Tuesday" ,"Wednesday", "Thursday", "Friday", "Saturday")
|
||||
private var courseTTLinkList = ArrayList<CourseTTLink>()
|
||||
private var timeTableWeek = arrayOf<Array<Lesson>>()
|
||||
|
||||
init {
|
||||
// create the timetable array
|
||||
for (i in 0..5) {
|
||||
var timeTableDay = arrayOf<Lesson>()
|
||||
for (j in 0..5) {
|
||||
timeTableDay += Lesson("", "","","")
|
||||
}
|
||||
timeTableWeek += timeTableDay
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get the timetable from the given url
|
||||
* the timetable is organised per row not per column;
|
||||
* Mon 1, Tue 1, Wed 1, Thur 1, Fri 1, Sat 1, Mon 2 and so on
|
||||
*/
|
||||
fun getTimeTable(courseTTURL: String): Array<Array<Lesson>> {
|
||||
val scheduleHTML = Jsoup.connect(courseTTURL).get()
|
||||
|
||||
//val week = scheduleHTML.select("h1.timetable-caption").text()
|
||||
//println("$week successful!\n")
|
||||
|
||||
scheduleHTML.select("table.timetable").select("td.lastcol").forEachIndexed { index, element ->
|
||||
timeTableWeek[index % 6][index / 6] = Lesson(element.select("div.lesson-subject").text(), element.select("div.lesson-teacher").text(), element.select("div.lesson-room").text(), element.select("div.lesson-remark").text())
|
||||
}
|
||||
|
||||
return timeTableWeek
|
||||
}
|
||||
|
||||
/**
|
||||
* parse all courses from the courses site at https://www.hs-offenburg.de/studium/vorlesungsplaene/
|
||||
*/
|
||||
fun getCourseTTLinks(): ArrayList<CourseTTLink> {
|
||||
val courseHTML = Jsoup.connect("https://www.hs-offenburg.de/studium/vorlesungsplaene/").get()
|
||||
|
||||
courseHTML.select("ul.index-group").select("li.Class").select("a[href]").forEachIndexed { _, element ->
|
||||
courseTTLinkList.add(CourseTTLink(element.attr("href"),element.text()))
|
||||
}
|
||||
return courseTTLinkList
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun printTimeTableWeek (timeTableWeek: Array<Array<Lesson>>) {
|
||||
|
||||
for (j in 0..5) print(days[j].padEnd(25 ,' ') + " | ")
|
||||
println()
|
||||
for (j in 0..5) print("-".padEnd(26 + (j.toFloat().div(j).toInt()), '-') + "+")
|
||||
println()
|
||||
|
||||
for (i in 0..5) {
|
||||
for (j in 0..5) print(timeTableWeek[j][i].lessonSubject.padEnd(25 ,' ').substring(0,25) + " | ")
|
||||
println()
|
||||
for (j in 0..5) print(timeTableWeek[j][i].lessonTeacher.padEnd(25 ,' ').substring(0,25) + " | ")
|
||||
println()
|
||||
for (j in 0..5) print(timeTableWeek[j][i].lessonRoom.padEnd(25 ,' ').substring(0,25) + " | ")
|
||||
println()
|
||||
for (j in 0..5) print("-".padEnd(26 + (j.toFloat().div(j).toInt()), '-') + "+")
|
||||
println()
|
||||
}
|
||||
println()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.onboarding
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.viewpager.widget.PagerAdapter
|
||||
import org.mosad.seil0.projectlaogai.OnboardingActivity.Companion.layouts
|
||||
|
||||
/**
|
||||
* a PagerAdapter
|
||||
*/
|
||||
class ViewPagerAdapter(cont: Context) : PagerAdapter() {
|
||||
|
||||
private val context = cont
|
||||
|
||||
override fun isViewFromObject(view: View, `object`: Any): Boolean {
|
||||
return view === `object`
|
||||
}
|
||||
|
||||
override fun getCount(): Int {
|
||||
return layouts.size
|
||||
}
|
||||
|
||||
override fun instantiateItem(container: ViewGroup, position: Int): Any {
|
||||
val view = LayoutInflater.from(context).inflate(layouts[position], container, false)
|
||||
container.addView(view)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
|
||||
val view = `object` as View
|
||||
container.removeView(view)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.uicomponents
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.widget.LinearLayout
|
||||
import androidx.cardview.widget.CardView
|
||||
import kotlinx.android.synthetic.main.cardview_day.view.*
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.util.DataTypes
|
||||
import org.mosad.seil0.projectlaogai.util.TimetableDay
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class DayCardView(context: Context) : CardView(context) {
|
||||
|
||||
private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault())
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.cardview_day,this)
|
||||
|
||||
// workaround to prevent a white border
|
||||
this.setBackgroundColor(Color.TRANSPARENT)
|
||||
}
|
||||
|
||||
fun getLinLayoutDay() : LinearLayout {
|
||||
return linLayout_Day
|
||||
}
|
||||
|
||||
fun setDayHeading(heading: String) {
|
||||
txtView_DayHeading.text = heading
|
||||
}
|
||||
|
||||
/**
|
||||
* add the lessons of one day to the dayCardView
|
||||
* @param timetable a timetable containing the day (and it's lessons) to be added
|
||||
*/
|
||||
fun addTimetableDay(timetable: TimetableDay, daysToAdd: Int) {
|
||||
var lastLesson = LessonLinearLayout(context)
|
||||
|
||||
// set the heading
|
||||
val cal = Calendar.getInstance()
|
||||
cal.add(Calendar.DATE, daysToAdd)
|
||||
txtView_DayHeading.text = formatter.format(cal.time)
|
||||
|
||||
// for every timeslot of that timetable
|
||||
timetable.timeslots.forEachIndexed { tsIndex, timeslot ->
|
||||
timeslot.forEach { lesson ->
|
||||
if (lesson.lessonSubject.isNotEmpty()) {
|
||||
|
||||
val lessonLayout = LessonLinearLayout(context)
|
||||
lessonLayout.setLesson(lesson, DataTypes().times[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
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2018 <seil0@mosad.xyz>
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -23,32 +23,36 @@
|
||||
package org.mosad.seil0.projectlaogai.uicomponents
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import kotlinx.android.synthetic.main.linearlayout_grade.view.*
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
|
||||
class MensaDayCardView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : androidx.cardview.widget.CardView(context, attrs){
|
||||
class GradeLinearLayout(context: Context?): LinearLayout(context) {
|
||||
|
||||
private var linLayoutMensaDay: LinearLayout
|
||||
private var txtViewDayHeading: TextView
|
||||
var subjectName = ""
|
||||
var grade = ""
|
||||
var subSubjectName = ""
|
||||
var subGrade = ""
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.mensaday_cardview,this)
|
||||
|
||||
linLayoutMensaDay = findViewById(R.id.linLayout_MensaDay)
|
||||
txtViewDayHeading = findViewById(R.id.txtView_DayHeading)
|
||||
|
||||
// workaround to prevent a white border
|
||||
//this.setBackgroundColor(Color.TRANSPARENT)
|
||||
inflate(context, R.layout.linearlayout_grade, this)
|
||||
}
|
||||
|
||||
fun getLinLayoutMensaDay(): LinearLayout {
|
||||
return linLayoutMensaDay
|
||||
fun set(func: GradeLinearLayout.() -> Unit): GradeLinearLayout = apply {
|
||||
func()
|
||||
|
||||
txtView_subject.text = subjectName
|
||||
txtView_grade.text = grade
|
||||
txtView_sub_subject.text = subSubjectName
|
||||
txtView_sub_grade.text = subGrade
|
||||
}
|
||||
|
||||
fun setDayHeading(heading: String) {
|
||||
txtViewDayHeading.text = heading
|
||||
fun disableDivider() {
|
||||
divider_grade.visibility = View.GONE
|
||||
}
|
||||
|
||||
fun disableSubSubject() {
|
||||
linLayout_sub_subject.visibility = View.GONE
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2018 <seil0@mosad.xyz>
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -23,31 +23,27 @@
|
||||
package org.mosad.seil0.projectlaogai.uicomponents
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import android.widget.TextView
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import kotlinx.android.synthetic.main.linearlayout_lesson.view.*
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.util.Lesson
|
||||
|
||||
class LessonCardView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : androidx.cardview.widget.CardView(context, attrs){
|
||||
|
||||
private var txtViewLesson: TextView
|
||||
private var txtViewTime: TextView
|
||||
class LessonLinearLayout(context: Context?) : LinearLayout(context) {
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.lesson_cardview,this)
|
||||
|
||||
txtViewLesson = findViewById(R.id.txtView_Lesson)
|
||||
txtViewTime = findViewById(R.id.txtView_Time)
|
||||
|
||||
// workaround to prevent a white border
|
||||
this.setBackgroundColor(Color.TRANSPARENT)
|
||||
inflate(context, R.layout.linearlayout_lesson, this)
|
||||
}
|
||||
|
||||
fun getTxtViewLesson(): TextView {
|
||||
return txtViewLesson
|
||||
fun setLesson(lesson: Lesson, time: String) {
|
||||
txtView_lessonTime.text = time
|
||||
txtView_lessonSubject.text = lesson.lessonSubject
|
||||
txtView_lessonTeacher.text = lesson.lessonTeacher
|
||||
txtView_lessonRoom.text = lesson.lessonRoom
|
||||
}
|
||||
|
||||
fun getTxtViewTime(): TextView {
|
||||
return txtViewTime
|
||||
fun disableDivider() {
|
||||
divider_lesson.visibility = View.GONE
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.uicomponents
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import kotlinx.android.synthetic.main.linearlayout_meal.view.*
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.util.Meal
|
||||
|
||||
class MealLinearLayout(context: Context?): LinearLayout(context) {
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.linearlayout_meal, this)
|
||||
}
|
||||
|
||||
fun setMeal(meal: Meal) {
|
||||
txtView_MealHeading.text = meal.heading
|
||||
|
||||
meal.parts.forEachIndexed { partIndex, part ->
|
||||
txtView_Meal.append(part)
|
||||
if(partIndex < (meal.parts.size - 1))
|
||||
txtView_Meal.append("\n")
|
||||
}
|
||||
}
|
||||
|
||||
fun disableDivider() {
|
||||
divider_meal.visibility = View.GONE
|
||||
}
|
||||
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package org.mosad.seil0.projectlaogai.uicomponents
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import android.widget.TextView
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
|
||||
class MenuCardView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : androidx.cardview.widget.CardView(context, attrs){
|
||||
|
||||
private var txtViewMenuHeading: TextView
|
||||
private var txtViewMenu: TextView
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.menu_cardview,this)
|
||||
|
||||
txtViewMenuHeading = findViewById(R.id.txtView_MenuHeading)
|
||||
txtViewMenu = findViewById(R.id.txtView_Menu)
|
||||
|
||||
// workaround to prevent a white border
|
||||
this.setBackgroundColor(Color.TRANSPARENT)
|
||||
}
|
||||
|
||||
fun setMenuHeading(heading: String) {
|
||||
txtViewMenuHeading.text = heading
|
||||
}
|
||||
|
||||
fun getTxtViewMenu(): TextView {
|
||||
return txtViewMenu
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.uicomponents
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
|
||||
|
||||
class TextViewInfo(context: Context?): AppCompatTextView(context!!) {
|
||||
|
||||
var txt = ""
|
||||
var txtSize = 15F
|
||||
var txtAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||
var drawable = R.drawable.ic_error_outline_black_24dp
|
||||
var params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
|
||||
|
||||
init {
|
||||
params.setMargins(0,200,0,0)
|
||||
|
||||
}
|
||||
|
||||
fun set(func: TextViewInfo.() -> Unit): TextViewInfo = apply {
|
||||
func()
|
||||
|
||||
text = txt
|
||||
layoutParams = params
|
||||
textSize = txtSize
|
||||
textAlignment = txtAlignment
|
||||
}
|
||||
|
||||
fun showImage(): TextViewInfo = apply {
|
||||
val img = ResourcesCompat.getDrawable(resources, drawable, context.theme)?.apply {
|
||||
bounds = Rect(0, 0, 75, 75)
|
||||
}
|
||||
setCompoundDrawables(null, null, null, img)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.uicomponents.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Spinner
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.WhichButton
|
||||
import com.afollestad.materialdialogs.actions.getActionButton
|
||||
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
|
||||
import com.afollestad.materialdialogs.bottomsheets.setPeekHeight
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import com.afollestad.materialdialogs.customview.getCustomView
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
|
||||
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
|
||||
import org.mosad.seil0.projectlaogai.util.Course
|
||||
import java.util.stream.Collectors
|
||||
|
||||
/**
|
||||
* This class creates a new AddLessonDialog.
|
||||
*/
|
||||
class AddSubjectDialog(val context: Context) {
|
||||
|
||||
private val dialog = MaterialDialog(context, BottomSheet())
|
||||
|
||||
private val spinnerCourses: Spinner
|
||||
private val spinnerSubjects: Spinner
|
||||
|
||||
private val subjectsList = ArrayList<String>()
|
||||
private val courseNamesList = getCourseNames()
|
||||
|
||||
var selectedCourse = ""
|
||||
var selectedSubject = ""
|
||||
|
||||
init {
|
||||
dialog.title(R.string.add_lesson)
|
||||
.message(R.string.add_lesson_desc)
|
||||
.customView(R.layout.dialog_add_lesson)
|
||||
.positiveButton(R.string.add)
|
||||
.negativeButton(R.string.cancel)
|
||||
.setPeekHeight(900)
|
||||
|
||||
spinnerCourses = dialog.getCustomView().findViewById(R.id.spinner_Courses)
|
||||
spinnerSubjects = dialog.getCustomView().findViewById(R.id.spinner_Lessons)
|
||||
|
||||
// fix not working accent color
|
||||
dialog.getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.cColorAccent)
|
||||
dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.cColorAccent)
|
||||
|
||||
initSpinners()
|
||||
}
|
||||
|
||||
fun positiveButton(func: AddSubjectDialog.() -> Unit): AddSubjectDialog = apply {
|
||||
dialog.positiveButton {
|
||||
func()
|
||||
}
|
||||
}
|
||||
|
||||
fun show() {
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
fun show(func: AddSubjectDialog.() -> Unit): AddSubjectDialog = apply {
|
||||
func()
|
||||
this.show()
|
||||
}
|
||||
|
||||
private fun initSpinners() {
|
||||
setArrayAdapter(spinnerCourses, courseNamesList)
|
||||
val lessonsAdapter = setArrayAdapter(spinnerSubjects, subjectsList)
|
||||
|
||||
// don't call onItemSelected() on spinnerCourses.onItemSelectedListener
|
||||
spinnerCourses.setSelection(0,false)
|
||||
spinnerCourses.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>, view: View, pos: Int, id: Long) {
|
||||
selectedCourse = parent.getItemAtPosition(pos).toString()
|
||||
|
||||
// TODO show loading dialog while loading
|
||||
val lessonSubjects = runBlocking {
|
||||
TCoRAPIController.getSubjectListAsync(parent.getItemAtPosition(pos).toString(), 0).await()
|
||||
}
|
||||
|
||||
lessonsAdapter.clear()
|
||||
lessonsAdapter.addAll(lessonSubjects)
|
||||
lessonsAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>) {
|
||||
// Another interface callback
|
||||
}
|
||||
}
|
||||
|
||||
// don't call onItemSelected() on spinnerCourses.onItemSelectedListener
|
||||
spinnerSubjects.setSelection(0,false)
|
||||
spinnerSubjects.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>, view: View, pos: Int, id: Long) {
|
||||
selectedSubject = parent.getItemAtPosition(pos).toString()
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>) {
|
||||
// Another interface callback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set a new ArrayAdapter for a spinner with a list
|
||||
* @param spinner the spinner you wish to set the adapter for
|
||||
* @param list the list to set the adapter to
|
||||
*/
|
||||
private fun setArrayAdapter(spinner: Spinner, list: List<String>): ArrayAdapter<String> {
|
||||
return ArrayAdapter(context, android.R.layout.simple_spinner_item, list)
|
||||
.also { adapter ->
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
spinner.adapter = adapter
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get all course names from coursesList
|
||||
* @return a list, containing all course names
|
||||
*/
|
||||
private fun getCourseNames(): List<String> {
|
||||
val coursesNameList: List<String>
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
coursesNameList = CacheController.coursesList.stream().map(Course::courseName).collect(
|
||||
Collectors.toList())
|
||||
} else {
|
||||
coursesNameList = ArrayList()
|
||||
CacheController.coursesList.forEach { course ->
|
||||
coursesNameList.add(course.courseName)
|
||||
}
|
||||
}
|
||||
|
||||
return coursesNameList
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.uicomponents.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.EditText
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.WhichButton
|
||||
import com.afollestad.materialdialogs.actions.getActionButton
|
||||
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
|
||||
import com.afollestad.materialdialogs.bottomsheets.setPeekHeight
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import com.afollestad.materialdialogs.customview.getCustomView
|
||||
import org.mosad.seil0.projectlaogai.R
|
||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||
|
||||
class LoginDialog(val context: Context) {
|
||||
|
||||
private val dialog = MaterialDialog(context, BottomSheet())
|
||||
|
||||
private val editTextEmail: EditText
|
||||
private val editTextPassword: EditText
|
||||
|
||||
var email = ""
|
||||
var password = ""
|
||||
|
||||
init {
|
||||
dialog.title(R.string.login_heading)
|
||||
.message(R.string.login_desc_on)
|
||||
.customView(R.layout.dialog_login)
|
||||
.positiveButton(R.string.save)
|
||||
.negativeButton(R.string.cancel)
|
||||
.setPeekHeight(900)
|
||||
|
||||
editTextEmail = dialog.getCustomView().findViewById(R.id.editText_email)
|
||||
editTextPassword = dialog.getCustomView().findViewById(R.id.editText_password)
|
||||
|
||||
// fix not working accent color
|
||||
dialog.getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.cColorAccent)
|
||||
dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.cColorAccent)
|
||||
}
|
||||
|
||||
fun positiveButton(func: LoginDialog.() -> Unit): LoginDialog = apply {
|
||||
dialog.positiveButton {
|
||||
email = editTextEmail.text.toString()
|
||||
password = editTextPassword.text.toString()
|
||||
|
||||
func()
|
||||
}
|
||||
}
|
||||
|
||||
fun negativeButton(func: LoginDialog.() -> Unit): LoginDialog = apply {
|
||||
dialog.negativeButton {
|
||||
func()
|
||||
}
|
||||
}
|
||||
|
||||
fun show(func: LoginDialog.() -> Unit): LoginDialog = apply {
|
||||
func()
|
||||
|
||||
editTextEmail.setText(email)
|
||||
editTextPassword.setText(password)
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.util
|
||||
|
||||
import android.graphics.Color
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class DataTypes {
|
||||
val times = arrayOf("8.00 - 9.30", "9.45 - 11.15", "11.35 - 13.05", "14.00 -15.30", "15.45 - 17.15", "17.30 - 19.00")
|
||||
|
||||
val primaryColors = intArrayOf(
|
||||
Color.parseColor("#E53935"),
|
||||
Color.parseColor("#E91E63"),
|
||||
Color.parseColor("#9C27B0"),
|
||||
Color.parseColor("#673AB7"),
|
||||
Color.parseColor("#3F51B5"),
|
||||
Color.parseColor("#2196F3"),
|
||||
Color.parseColor("#03A9F4"),
|
||||
Color.parseColor("#00BCD4"),
|
||||
Color.parseColor("#009688"),
|
||||
Color.parseColor("#4CAF50"),
|
||||
Color.parseColor("#8BC34A"),
|
||||
Color.parseColor("#CDDC39"),
|
||||
Color.parseColor("#FDD835"),
|
||||
Color.parseColor("#FFB300"),
|
||||
Color.parseColor("#FB8C00"),
|
||||
Color.parseColor("#FF5722"),
|
||||
Color.parseColor("#795548"),
|
||||
Color.parseColor("#9E9E9E"),
|
||||
Color.parseColor("#607B8B"),
|
||||
Color.parseColor("#000000")
|
||||
)
|
||||
|
||||
val accentColors = intArrayOf(
|
||||
Color.parseColor("#FF1744"),
|
||||
Color.parseColor("#F50057"),
|
||||
Color.parseColor("#D500F9"),
|
||||
Color.parseColor("#3F51B5"),
|
||||
Color.parseColor("#3D5AFE"),
|
||||
Color.parseColor("#2979FF"),
|
||||
Color.parseColor("#0096FF"),
|
||||
Color.parseColor("#00E5FF"),
|
||||
Color.parseColor("#1DE9B6"),
|
||||
Color.parseColor("#00E676"),
|
||||
Color.parseColor("#C6FF00"),
|
||||
Color.parseColor("#FFD600"),
|
||||
Color.parseColor("#FFC400"),
|
||||
Color.parseColor("#FF9100"),
|
||||
Color.parseColor("#FF3D00"),
|
||||
Color.parseColor("#000000")
|
||||
)
|
||||
|
||||
enum class Theme(var string: String) {
|
||||
Light("Light"),
|
||||
Dark("Dark"),
|
||||
Black("Black")
|
||||
}
|
||||
}
|
||||
|
||||
// data classes for the course part
|
||||
data class Course(val courseLink: String, val courseName: String)
|
||||
|
||||
data class CoursesMeta(val updateTime: Long = 0, val totalCourses: Int = 0)
|
||||
|
||||
data class CoursesList(val meta: CoursesMeta = CoursesMeta(), val courses: ArrayList<Course> = ArrayList())
|
||||
|
||||
// data classes for the Mensa part
|
||||
data class Meal(val day: String, val heading: String, val parts: ArrayList<String>, val additives: String)
|
||||
|
||||
data class Meals(val meals: ArrayList<Meal>)
|
||||
|
||||
data class MensaWeek(val days: Array<Meals> = Array(7) { Meals(ArrayList()) })
|
||||
|
||||
data class MensaMeta(val updateTime: Long = 0, val mensaName: String = "")
|
||||
|
||||
data class MensaMenu(val meta: MensaMeta = MensaMeta(), val currentWeek: MensaWeek = MensaWeek(), val nextWeek: MensaWeek = MensaWeek())
|
||||
|
||||
// data classes for the timetable part
|
||||
data class Lesson(
|
||||
val lessonID: String,
|
||||
val lessonSubject: String,
|
||||
val lessonTeacher: String,
|
||||
val lessonRoom: String,
|
||||
val lessonRemark: String
|
||||
)
|
||||
|
||||
data class TimetableDay(val timeslots: Array<ArrayList<Lesson>> = Array(6) { ArrayList() })
|
||||
|
||||
data class TimetableWeek(val weekIndex: Int = 0, val weekNumberYear: Int = 0, val days: Array<TimetableDay> = Array(6) { TimetableDay() })
|
||||
|
||||
// data classes for the qispos part
|
||||
data class GradeSubject(val id: String = "", val name: String = "", val semester: String = "", val grade: String = "", val credits: String = "")
|
||||
|
||||
// TCoR
|
||||
data class TimetableWeekTCoR(val days: Array<TimetableDay> = Array(6) { TimetableDay() })
|
||||
|
||||
data class TimetableCourseMeta(val updateTime: Long = 0, val courseName: String = "", val weekIndex: Int = 0, val weekNumberYear: Int = 0, val link: String = "")
|
||||
|
||||
data class TimetableCourseWeek(val meta: TimetableCourseMeta = TimetableCourseMeta(), var timetable: TimetableWeekTCoR = TimetableWeekTCoR())
|
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* ProjectLaogai
|
||||
*
|
||||
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mosad.seil0.projectlaogai.util
|
||||
|
||||
import java.util.*
|
||||
|
||||
class NotRetardedCalendar {
|
||||
companion object {
|
||||
private val calendar = Calendar.getInstance()
|
||||
|
||||
fun getDayOfWeekIndex(): Int {
|
||||
return when (calendar.get(Calendar.DAY_OF_WEEK)) {
|
||||
Calendar.MONDAY -> 0
|
||||
Calendar.TUESDAY -> 1
|
||||
Calendar.WEDNESDAY -> 2
|
||||
Calendar.THURSDAY -> 3
|
||||
Calendar.FRIDAY -> 4
|
||||
Calendar.SATURDAY -> 5
|
||||
Calendar.SUNDAY -> 6
|
||||
else -> 7
|
||||
}
|
||||
}
|
||||
|
||||
fun getTomorrowWeekIndex(): Int {
|
||||
return when (calendar.get(Calendar.DAY_OF_WEEK)) {
|
||||
Calendar.MONDAY -> 1
|
||||
Calendar.TUESDAY -> 2
|
||||
Calendar.WEDNESDAY -> 3
|
||||
Calendar.THURSDAY -> 4
|
||||
Calendar.FRIDAY -> 5
|
||||
Calendar.SATURDAY -> 6
|
||||
Calendar.SUNDAY -> 0
|
||||
else -> 7
|
||||
}
|
||||
}
|
||||
|
||||
fun getWeekOfYear(): Int {
|
||||
return when (calendar.get(Calendar.DAY_OF_WEEK)) {
|
||||
Calendar.SUNDAY -> Calendar.getInstance().get(Calendar.WEEK_OF_YEAR) - 1
|
||||
else -> Calendar.getInstance().get(Calendar.WEEK_OF_YEAR)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:drawable="@color/colorPrimary"/>
|
||||
<item android:drawable="@android:color/black"/>
|
||||
|
||||
<!--TODO when minAPI is 23 use this-->
|
||||
<!--<item android:gravity="center" android:width="144dp" android:height="144dp">-->
|
||||
<item android:gravity="center">
|
||||
<item android:gravity="center" android:width="144dp" android:height="144dp">
|
||||
<bitmap
|
||||
android:gravity="fill_horizontal|fill_vertical"
|
||||
android:src="@mipmap/ic_laogai_icon_splash"/>
|
||||
android:src="@drawable/ic_splash_logo"/>
|
||||
</item>
|
||||
|
||||
</layer-list>
|
9
app/src/main/res/drawable/ic_add_black_24dp.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_grading_black_24dp.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M4,7h16v2H4V7zM4,13h16v-2H4V13zM4,17h7v-2H4V17zM4,21h7v-2H4V21zM15.41,18.17L14,16.75l-1.41,1.41L15.41,21L20,16.42L18.58,15L15.41,18.17zM4,3v2h16V3H4z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
|
||||
</vector>
|
@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
|
||||
</vector>
|
@ -1,9 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:autoMirrored="true"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z" />
|
||||
</vector>
|
||||
|
BIN
app/src/main/res/drawable/ic_splash_logo.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
@ -1,9 +0,0 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="135"
|
||||
android:centerColor="@color/colorPrimary"
|
||||
android:endColor="@color/colorPrimaryDark"
|
||||
android:startColor="@color/colorPrimary"
|
||||
android:type="linear"/>
|
||||
</shape>
|
@ -1,109 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".fragments.HomeFragment">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:clickable="false"
|
||||
android:maxHeight="125dp"
|
||||
app:cardUseCompatPadding="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" android:elevation="5dp">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:layout_marginBottom="2dp">
|
||||
<TextView
|
||||
android:text="@string/meal_1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/txtView_Menu1Heading"
|
||||
android:textStyle="bold" android:textAlignment="center" android:textSize="16sp"
|
||||
android:typeface="sans" android:fontFamily="sans-serif" android:paddingBottom="5dp"/>
|
||||
<TextView
|
||||
android:id="@+id/txtViewMenu1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textAlignment="center"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:typeface="sans"
|
||||
android:textIsSelectable="true"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardView2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardUseCompatPadding="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/cardView" android:elevation="5dp"
|
||||
android:layout_marginEnd="5dp" android:layout_marginStart="5dp">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:layout_marginBottom="2dp">
|
||||
<TextView
|
||||
android:text="@string/meal_2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/txtView_Menu2Heading"
|
||||
android:textAlignment="center" android:textStyle="bold" android:textSize="16sp"
|
||||
android:typeface="sans" android:fontFamily="sans-serif" android:paddingBottom="5dp"/>
|
||||
<TextView
|
||||
android:id="@+id/txtViewMenu2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textAlignment="center"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:typeface="sans"
|
||||
android:textIsSelectable="true"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
<ScrollView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/cardView2" android:id="@+id/scrollViewTimeTable"
|
||||
android:background="@color/md_divider_dark_theme" android:paddingTop="6dp"
|
||||
android:layout_marginTop="5dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayoutTimeTable"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/cardView2">
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</FrameLayout>
|
@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".fragments.MensaFragment">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/linLayout_MensaFragment"
|
||||
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</FrameLayout>
|
@ -1,147 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".fragments.SettingsFragment">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
|
||||
>
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="9dp" android:id="@+id/cardView_Info" app:cardElevation="5dp"
|
||||
app:cardUseCompatPadding="true">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<TextView
|
||||
android:text="@string/info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/txtView_Info"
|
||||
android:typeface="sans"
|
||||
android:textSize="18sp" android:textStyle="bold" android:fontFamily="sans-serif"
|
||||
android:layout_margin="7dp" android:textColor="@color/colorPrimary"/>
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:padding="7dp" android:id="@+id/linLayout_User">
|
||||
<TextView
|
||||
android:text="@string/sample_user"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/txtView_User"
|
||||
android:textSize="16sp" android:textStyle="bold"
|
||||
android:textColor="@android:color/primary_text_light"/>
|
||||
<TextView
|
||||
android:text="@string/user"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/txtView_UserDesc"/>
|
||||
</LinearLayout>
|
||||
<View
|
||||
android:id="@+id/divider2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?android:attr/listDivider"
|
||||
/>
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:layout_margin="7dp"
|
||||
android:id="@+id/linLayout_Course">
|
||||
<TextView
|
||||
android:text="@string/sample_course"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/txtView_Course"
|
||||
android:textSize="16sp" android:textColor="@android:color/primary_text_light"
|
||||
android:textStyle="bold"/>
|
||||
<TextView
|
||||
android:text="@string/course_desc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/txtView_CourseDesc"/>
|
||||
</LinearLayout>
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?android:attr/listDivider"
|
||||
/>
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:layout_margin="7dp"
|
||||
android:id="@+id/linLayout_Info">
|
||||
<TextView
|
||||
android:text="@string/about_txtView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/txtView_About"
|
||||
android:textStyle="bold" android:textSize="16sp"
|
||||
android:textColor="@android:color/primary_text_light"/>
|
||||
<TextView
|
||||
android:text="@string/version"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/txtView_Version"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/cardView_Settings" app:cardElevation="5dp"
|
||||
app:cardUseCompatPadding="true">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/linLayout_Settings">
|
||||
<TextView
|
||||
android:text="@string/settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/txtView_Settings"
|
||||
android:typeface="sans"
|
||||
android:textSize="18sp" android:textColor="@color/design_default_color_primary"
|
||||
android:textStyle="bold" android:layout_margin="7dp" android:paddingTop="3dp"/>
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:layout_margin="7dp"
|
||||
android:gravity="center_vertical|end"
|
||||
android:clickable="true" android:id="@+id/linLayout_MainColor" android:focusable="true">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<TextView
|
||||
android:text="@string/primary_color"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/txtView_PrimaryColor"
|
||||
android:textSize="16sp" android:textStyle="bold"
|
||||
android:textColor="@android:color/primary_text_light"/>
|
||||
<TextView
|
||||
android:text="@string/main_color_desc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/txtView_PrimaryColorDesc"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:layout_margin="7dp"
|
||||
android:gravity="end">
|
||||
<View
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp" android:id="@+id/view_PrimaryColor"
|
||||
android:background="@color/colorPrimary"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</FrameLayout>
|
@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="@android:color/background_light"
|
||||
app:cardElevation="5dp"
|
||||
app:cardUseCompatPadding="true" app:cardPreventCornerOverlap="false" app:contentPadding="5dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_Lesson"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/a_lesson"
|
||||
android:textColor="@android:color/primary_text_light"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:typeface="sans"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_Time"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:text="@string/a_time"/>
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="9dp"
|
||||
app:cardElevation="5dp"
|
||||
app:cardBackgroundColor="@color/colorMensaDay"
|
||||
app:cardUseCompatPadding="true">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/linLayout_MensaDay" android:paddingBottom="5dp">
|
||||
<TextView
|
||||
android:text="@string/sample_date"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/txtView_DayHeading" android:textAlignment="center"
|
||||
android:textSize="20sp" android:textStyle="bold" android:typeface="sans" android:paddingBottom="5dp"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/cardView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:clickable="false"
|
||||
android:maxHeight="125dp"
|
||||
app:cardUseCompatPadding="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<TextView
|
||||
android:text="@string/meal_1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/txtView_MenuHeading"
|
||||
android:textStyle="bold" android:textAlignment="center" android:textSize="16sp"
|
||||
android:typeface="sans" android:fontFamily="sans-serif" android:paddingBottom="5dp"/>
|
||||
<TextView
|
||||
android:id="@+id/txtView_Menu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textAlignment="center"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:typeface="sans"
|
||||
android:textIsSelectable="true"/>
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
@ -20,7 +20,9 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:fitsSystemWindows="true"
|
||||
android:background="?themeSecondary"
|
||||
app:headerLayout="@layout/nav_header_main"
|
||||
app:itemTextColor="?colorAccent"
|
||||
app:menu="@menu/activity_main_drawer"/>
|
||||
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
android:id="@+id/viewPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
</androidx.viewpager.widget.ViewPager>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_Dots"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="30dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal" />
|
||||
|
||||
<View
|
||||
android:id="@+id/view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="1dp"
|
||||
android:layout_above="@id/linLayout_Dots"
|
||||
android:alpha="0.5"
|
||||
android:background="@android:color/white" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_Next"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="@null"
|
||||
android:onClick="btnNextClick"
|
||||
android:text="@string/next"
|
||||
android:visibility="gone" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_Skip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="@null"
|
||||
android:onClick="btnSkipClick"
|
||||
android:text="@string/skip"
|
||||
tools:visibility="gone" />
|
||||
|
||||
</RelativeLayout>
|
@ -20,7 +20,7 @@
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/colorPrimary"
|
||||
android:background="?colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay"/>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
21
app/src/main/res/layouts/activities/layout/cardview_day.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardUseCompatPadding="true"
|
||||
app:cardElevation="5dp"
|
||||
app:cardBackgroundColor="?themeSecondary"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:id="@+id/linLayout_Day">
|
||||
<TextView
|
||||
android:text="@string/sample_date"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/txtView_DayHeading" android:textSize="18sp"
|
||||
android:textAlignment="center" android:textStyle="bold" android:textColor="?colorAccent"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
30
app/src/main/res/layouts/activities/layout/dialog_login.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/linLayout_login"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editText_email"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:ems="10"
|
||||
android:hint="@string/email"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textEmailAddress" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editText_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:ems="10"
|
||||
android:hint="@string/password"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
</LinearLayout>
|
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/linLayout_grade"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="7dp"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingRight="7dp"
|
||||
android:paddingBottom="3dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_subject"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_subject"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/sample_subject"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_grade"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/sample_grade"
|
||||
android:textAlignment="textEnd"
|
||||
android:textSize="15sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_sub_subject"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="7dp"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingEnd="0dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_sub_subject"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/sample_sub_subject" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_sub_grade"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/sample_grade_state"
|
||||
android:textAlignment="textEnd" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider_grade"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:background="?dividerColor" />
|
||||
</LinearLayout>
|
@ -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/textSecondaryLight" />
|
||||
</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>
|
@ -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>
|
@ -1,37 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/nav_header_main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/nav_header_height"
|
||||
android:background="@drawable/side_nav_bar"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark"
|
||||
android:background="@android:color/black"
|
||||
android:gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
android:gravity="bottom">
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:paddingTop="@dimen/nav_header_vertical_spacing"
|
||||
app:srcCompat="@mipmap/ic_laogai_icon"
|
||||
android:contentDescription="@string/nav_header_desc"
|
||||
android:id="@+id/imageView"/>
|
||||
app:srcCompat="@mipmap/ic_laogai_icon" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_nav_header_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/nav_header_vertical_spacing"
|
||||
android:text="@string/nav_header_title"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"/>
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
android:textColor="#FFFFFF" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/nav_header_subtitle"
|
||||
android:id="@+id/textView"/>
|
||||
android:textColor="#ffffff" />
|
||||
|
||||
</LinearLayout>
|
33
app/src/main/res/layouts/activities/values/css_styles.xml
Normal 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>
|
43
app/src/main/res/layouts/activities/xml/shortcuts.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<shortcut
|
||||
android:shortcutId="mensa"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/ic_local_dining_black_24dp"
|
||||
android:shortcutShortLabel="@string/shortcut_mensa_short"
|
||||
android:shortcutLongLabel="@string/shortcut_mensa_long"
|
||||
android:shortcutDisabledMessage="@string/shortcut_mensa_disabled">
|
||||
<intent
|
||||
android:action="org.mosad.seil0.projectlaogai.fragments.MensaFragment"
|
||||
android:targetPackage="org.mosad.seil0.projectlaogai"
|
||||
android:targetClass="org.mosad.seil0.projectlaogai.MainActivity" />
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>
|
||||
|
||||
<shortcut
|
||||
android:shortcutId="timetable"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/ic_baseline_calendar_today_24dp"
|
||||
android:shortcutShortLabel="@string/shortcut_timetable_short"
|
||||
android:shortcutLongLabel="@string/shortcut_timetable_long"
|
||||
android:shortcutDisabledMessage="@string/shortcut_timetable_disabled">
|
||||
<intent
|
||||
android:action="org.mosad.seil0.projectlaogai.fragments.TimeTableFragment"
|
||||
android:targetPackage="org.mosad.seil0.projectlaogai"
|
||||
android:targetClass="org.mosad.seil0.projectlaogai.MainActivity" />
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>
|
||||
|
||||
<shortcut
|
||||
android:shortcutId="moodle"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/ic_school_black_24dp"
|
||||
android:shortcutShortLabel="@string/shortcut_moodle_short"
|
||||
android:shortcutLongLabel="@string/shortcut_moodle_long"
|
||||
android:shortcutDisabledMessage="@string/shortcut_moodle_disabled">
|
||||
<intent
|
||||
android:action="org.mosad.seil0.projectlaogai.fragments.MoodleFragment"
|
||||
android:targetPackage="org.mosad.seil0.projectlaogai"
|
||||
android:targetClass="org.mosad.seil0.projectlaogai.MainActivity" />
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/linLayout_addlesson"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_Course"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="7dp"
|
||||
android:text="@string/courses"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_Courses"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/courses"
|
||||
android:paddingBottom="10dp"
|
||||
android:popupBackground="?themeSecondary"
|
||||
android:spinnerMode="dropdown" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_Lesson"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="7dp"
|
||||
android:text="@string/lessons"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_Lessons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/lessons"
|
||||
android:paddingBottom="10dp"
|
||||
android:popupBackground="?themeSecondary" />
|
||||
</LinearLayout>
|
@ -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="@string/mensa_current"
|
||||
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>
|
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".fragments.GradesFragment">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/refreshLayout_Grades"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?themePrimary">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView_Grades"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_Grades"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_Loading"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="100dp"
|
||||
android:padding="7dp"
|
||||
android:text="@string/loading_from_hs"
|
||||
android:textAlignment="center"
|
||||
android:textSize="15sp" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</FrameLayout>
|
@ -3,7 +3,8 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".fragments.TimeTableFragment">
|
||||
tools:context=".fragments.HomeFragment"
|
||||
android:background="?themePrimary">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
@ -15,9 +16,11 @@
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/linLayout_TTFragment">
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/linLayout_Home">
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</FrameLayout>
|
29
app/src/main/res/layouts/fragments/layout/fragment_mensa.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".fragments.MensaFragment"
|
||||
android:background="?themePrimary">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/refreshLayout_Mensa"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView_Mensa"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_Mensa"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="false"
|
||||
android:orientation="vertical" />
|
||||
</ScrollView>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
|
||||
</FrameLayout>
|
@ -8,6 +8,7 @@
|
||||
<!-- TODO: Update blank fragment layout -->
|
||||
<WebView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:id="@+id/webView"/>
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/webView"/>
|
||||
|
||||
</FrameLayout>
|
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgCourse"
|
||||
android:layout_width="256dp"
|
||||
android:layout_height="256dp"
|
||||
android:contentDescription="@string/timetable"
|
||||
android:scaleType="fitCenter"
|
||||
app:layout_constraintBottom_toTopOf="@+id/linearLayout2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_baseline_calendar_today_24dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="60dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_Course"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/course_heading"
|
||||
android:textAlignment="center"
|
||||
android:textSize="26sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_CourseDesc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:text="@string/course_desc_on"
|
||||
android:textAlignment="center"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSelectCourse"
|
||||
style="@style/Widget.AppCompat.Button.Colored"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="20dp"
|
||||
android:onClick="btnSelectCourseClick"
|
||||
android:text="@string/select_course"
|
||||
android:textColor="#FFFFFFFF"
|
||||
android:textStyle="bold" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgGrades"
|
||||
android:layout_width="256dp"
|
||||
android:layout_height="256dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:scaleType="fitCenter"
|
||||
app:layout_constraintBottom_toTopOf="@+id/linearLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_school_black_24dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="60dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_Grades"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_heading"
|
||||
android:textAlignment="center"
|
||||
android:textSize="26sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_GradesDesc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:text="@string/login_desc_on"
|
||||
android:textAlignment="center"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editText_email"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:ems="10"
|
||||
android:hint="@string/email"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textEmailAddress" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editText_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:ems="10"
|
||||
android:hint="@string/password"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnLogin"
|
||||
style="@style/Widget.AppCompat.Button.Colored"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="20dp"
|
||||
android:onClick="btnLoginClick"
|
||||
android:text="@string/login"
|
||||
android:textColor="#FFFFFFFF"
|
||||
android:textStyle="bold" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/logo"
|
||||
android:layout_width="256dp"
|
||||
android:layout_height="256dp"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:scaleType="fitCenter"
|
||||
app:layout_constraintBottom_toTopOf="@+id/linearLayout3"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@mipmap/ic_laogai_icon_foreground" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="60dp"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtAppName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name"
|
||||
android:textAlignment="center"
|
||||
android:textSize="26sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtWelcome"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:text="@string/welcome"
|
||||
android:textAlignment="center"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnGetStarted"
|
||||
style="@style/Widget.AppCompat.Button.Colored"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="20dp"
|
||||
android:onClick="btnNextClick"
|
||||
android:text="@string/get_started"
|
||||
android:textColor="#FFFFFFFF"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
348
app/src/main/res/layouts/fragments/layout/fragment_settings.xml
Normal file
@ -0,0 +1,348 @@
|
||||
<?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"
|
||||
android:background="?themePrimary"
|
||||
tools:context=".fragments.SettingsFragment">
|
||||
|
||||
<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:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardView_Info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="9dp"
|
||||
app:cardBackgroundColor="?themeSecondary"
|
||||
app:cardElevation="5dp"
|
||||
app:cardUseCompatPadding="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_Info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:fontFamily="sans-serif"
|
||||
android:text="@string/info"
|
||||
android:textColor="?colorAccent"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:typeface="sans" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_User"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="7dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_User"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sample_user"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_UserDesc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/user_desc" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?dividerColor" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_Course"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_Course"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sample_course"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_CourseDesc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/course_desc" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?dividerColor" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_ManageLessons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_ManageLessons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/manage_lessons"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_ManageLessonsDesc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/manage_lessons_desc" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider7"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?dividerColor" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_About"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="7dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_About"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/about_txtView"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_AboutDesc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/about_version" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider6"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?dividerColor" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_Licence"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="7dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/licenses"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardView_Settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="?themeSecondary"
|
||||
app:cardElevation="5dp"
|
||||
app:cardUseCompatPadding="true">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_Settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_Settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:paddingTop="3dp"
|
||||
android:text="@string/settings"
|
||||
android:textColor="?colorAccent"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:typeface="sans" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_Theme"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="7dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_Theme"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/theme"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_SelectedTheme"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/themeLight" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?dividerColor" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_PrimaryColor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical|end"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_PrimaryColor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/primary_color"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_PrimaryColorDesc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/primary_color_desc" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:gravity="end"
|
||||
android:orientation="vertical">
|
||||
|
||||
<View
|
||||
android:id="@+id/view_PrimaryColor"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="?colorPrimary" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider4"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?dividerColor" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linLayout_AccentColor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical|end"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_AccentColor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/accent_color"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtView_AccentColorDesc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/accent_color_desc" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:gravity="end"
|
||||
android:orientation="vertical">
|
||||
|
||||
<View
|
||||
android:id="@+id/view_AccentColor"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="?colorAccent" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider5"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/switch_buffet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="7dp"
|
||||
android:text="@string/show_buffet"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</FrameLayout>
|
@ -0,0 +1,41 @@
|
||||
<?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.TimeTableFragment"
|
||||
android:background="?themePrimary">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/refreshLayout_Timetable">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:id="@+id/scrollView_Timetable">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="false"
|
||||
android:id="@+id/linLayout_Timetable"/>
|
||||
</ScrollView>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/faBtnAddSubject"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center|end"
|
||||
android:layout_marginEnd="11dp"
|
||||
android:layout_marginBottom="11dp"
|
||||
android:clickable="true"
|
||||
android:elevation="7dp"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_add_black_24dp"
|
||||
android:visibility="visible"
|
||||
app:backgroundTint="?colorAccent"
|
||||
app:fabSize="auto" />
|
||||
</FrameLayout>
|
@ -4,7 +4,10 @@
|
||||
tools:showIn="navigation_view">
|
||||
|
||||
<group android:checkableBehavior="single">
|
||||
<item android:id="@+id/nav_home" android:title="@string/home" android:icon="@drawable/ic_baseline_home_24dp"/>
|
||||
<item
|
||||
android:id="@+id/nav_home"
|
||||
android:title="@string/home"
|
||||
android:icon="@drawable/ic_baseline_home_24dp"/>
|
||||
<item
|
||||
android:id="@+id/nav_mensa"
|
||||
android:icon="@drawable/ic_local_dining_black_24dp"
|
||||
@ -17,6 +20,10 @@
|
||||
android:id="@+id/nav_moodle"
|
||||
android:icon="@drawable/ic_school_black_24dp"
|
||||
android:title="@string/moodle"/>
|
||||
<item
|
||||
android:id="@+id/nav_grades"
|
||||
android:icon="@drawable/ic_grading_black_24dp"
|
||||
android:title="@string/grades" />
|
||||
<item
|
||||
android:id="@+id/nav_settings"
|
||||
android:icon="@drawable/ic_settings_black_24dp"
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_laogai_icon_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_laogai_icon_foreground"/>
|
||||
</adaptive-icon>
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_laogai_icon_foreground.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_laogai_icon_foreground.png
Normal file
After Width: | Height: | Size: 755 B |
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.5 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_laogai_icon_foreground.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.0 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_laogai_icon_foreground.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_laogai_icon_foreground.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 8.2 KiB |