Compare commits
149 Commits
Author | SHA1 | Date | |
---|---|---|---|
e7d733fc5b
|
|||
521045e0dd
|
|||
3e52061a20
|
|||
b76f869ef0
|
|||
175d3be882 | |||
2687a8124d | |||
5491971959
|
|||
b29392c6f6
|
|||
4b1fb47abf
|
|||
0724502831
|
|||
1162ec73e5
|
|||
f40c0503c0
|
|||
451f6082ff
|
|||
36568e9682
|
|||
6388dfe54a
|
|||
c509381ec4
|
|||
390728bfb7
|
|||
a3102bc3f2
|
|||
95ce9e14bd
|
|||
d803d4dae1
|
|||
45d37f25de
|
|||
95362d5ab8
|
|||
f12873fe00
|
|||
cb1f43ec66
|
|||
d6eb1ff1e5
|
|||
d94f38de93
|
|||
313ff4741a
|
|||
07e348989a | |||
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 | |||
2bd86ff6bb | |||
dbaf496a79 | |||
77326a8ed6 | |||
9fc897e194 | |||
f9ac219ec6 | |||
ff1d353cae | |||
6e5cf29eaa |
8
.drone.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
kind: pipeline
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: nextcloudci/android10:android-56
|
||||||
|
commands:
|
||||||
|
- ./gradlew build
|
35
README.md
@ -1,16 +1,27 @@
|
|||||||
# ProjectLaogai "hso App"
|
[](https://drone.mosad.xyz/Seil0/ProjectLaogai)
|
||||||
ProjectLaogai is a app to access the timetable and the mensa menu of Hochschule Offenburg.
|

|
||||||
|
[](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 for 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
|
## Features
|
||||||
* check out the mensa menu of this and next week
|
* have your grades displayed directly in the app
|
||||||
* access your timetable
|
* show the timetable of your course
|
||||||
* open moodle
|
* take a look at the canteen menu for the current and next week
|
||||||
* probably some funny bugs
|
* check the current balance of your mensa card
|
||||||
|
* open moodle directly in the app
|
||||||
|
|
||||||
|
Please report bugs and issues to support@mosad.xyz
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
[<img src="https://raw.githubusercontent.com/Seil0/Seil0.github.io/master/images/Project_Laogai/ProjectLaogai_HomeScreen.png" width=180>](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project_Laogai/ProjectLaogai_HomeScreen.png)
|
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.webp" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.webp)
|
||||||
[<img src="https://raw.githubusercontent.com/Seil0/Seil0.github.io/master/images/Project_Laogai/ProjectLaogai_Mensa.png" width=180>](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project_Laogai/ProjectLaogai_Mensa.png)
|
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.webp" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.webp)
|
||||||
[<img src="https://raw.githubusercontent.com/Seil0/Seil0.github.io/master/images/Project_Laogai/ProjectLaogai_Timetable.png" width=180>](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project_Laogai/ProjectLaogai_Timetable.png)
|
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.webp" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.webp)
|
||||||
[<img src="https://raw.githubusercontent.com/Seil0/Seil0.github.io/master/images/Project_Laogai/ProjectLaogai_Settings.png" width=180>](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project_Laogai/ProjectLaogai_Settings.png)
|
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.webp" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.webp)
|
||||||
[<img src="https://raw.githubusercontent.com/Seil0/Seil0.github.io/master/images/Project_Laogai/ProjectLaogai_NavDrawer.png" width=180>](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project_Laogai/ProjectLaogai_NavDrawer.png)
|
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_dark_Mensa.webp" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_dark_Mensa.webp)
|
||||||
|
|
||||||
ProjectLaogai © 2019 mosad [www.mosad.xyz](http://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,53 +1,93 @@
|
|||||||
apply plugin: 'com.android.application'
|
plugins {
|
||||||
|
id 'com.android.application'
|
||||||
|
id 'kotlin-android'
|
||||||
|
}
|
||||||
|
|
||||||
apply plugin: 'kotlin-android'
|
kotlin {
|
||||||
|
jvmToolchain 11
|
||||||
apply plugin: 'kotlin-android-extensions'
|
sourceSets.configureEach {
|
||||||
|
languageSettings.optIn("kotlin.RequiresOptIn")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
signingConfigs {
|
|
||||||
}
|
compileSdk 34
|
||||||
compileSdkVersion 28
|
buildToolsVersion = '34.0.0'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "org.mosad.seil0.projectlaogai"
|
applicationId "org.mosad.seil0.projectlaogai"
|
||||||
minSdkVersion 21
|
minSdkVersion 23
|
||||||
targetSdkVersion 28
|
targetSdkVersion 33
|
||||||
versionCode 12
|
versionCode 6100 // 00.06.100
|
||||||
versionName "0.4.0"
|
versionName "0.6.1"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
resValue "string", "build_time", buildTime()
|
resValue "string", "build_time", buildTime()
|
||||||
setProperty("archivesBaseName", "projectlaogai-$versionName")
|
setProperty("archivesBaseName", "projectlaogai-$versionName")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding true
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled false
|
minifyEnabled true
|
||||||
shrinkResources false
|
shrinkResources true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
|
||||||
|
testOptions.unitTests.all {
|
||||||
|
useJUnitPlatform()
|
||||||
|
|
||||||
|
testLogging {
|
||||||
|
events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
namespace 'org.mosad.seil0.projectlaogai'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
implementation 'org.jetbrains.anko:anko-commons:0.10.8'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
implementation 'androidx.core:core-ktx:1.12.0'
|
||||||
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
|
||||||
|
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'com.google.android.material:material:1.0.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
implementation 'com.google.code.gson:gson:2.8.5'
|
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
|
||||||
implementation 'com.afollestad:aesthetic:1.0.0-beta05'
|
implementation "androidx.work:work-runtime-ktx:2.9.0"
|
||||||
implementation 'com.afollestad.material-dialogs:core:2.6.0'
|
implementation 'com.google.android.material:material:1.11.0'
|
||||||
implementation 'com.afollestad.material-dialogs:color:2.6.0'
|
implementation 'com.google.code.gson:gson:2.10'
|
||||||
|
// 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.15.3'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
|
||||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
androidTestImplementation 'androidx.test:core:1.5.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
}
|
}
|
||||||
|
|
||||||
static def buildTime() {
|
static def buildTime() {
|
||||||
|
13
app/proguard-rules.pro
vendored
@ -15,7 +15,20 @@
|
|||||||
# Uncomment this to preserve the line number information for
|
# Uncomment this to preserve the line number information for
|
||||||
# debugging stack traces.
|
# debugging stack traces.
|
||||||
#-keepattributes SourceFile,LineNumberTable
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
-dontobfuscate
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
# If you keep the line number information, uncomment this to
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
-keep class org.mosad.seil0.projectlaogai.util.** { <fields>; }
|
||||||
|
|
||||||
|
# Gson
|
||||||
|
-keepattributes Signature
|
||||||
|
-dontwarn sun.misc.**
|
||||||
|
|
||||||
|
# com.google.crypto
|
||||||
|
-dontwarn javax.annotation.Nullable
|
||||||
|
-dontwarn javax.annotation.concurrent.GuardedBy
|
||||||
|
|
||||||
|
# misc
|
||||||
|
-dontwarn java.lang.instrument.ClassFileTransformer
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package org.mosad.seil0.projectlaogai
|
package org.mosad.seil0.projectlaogai
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.runner.AndroidJUnit4
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@ -18,7 +18,7 @@ class ExampleInstrumentedTest {
|
|||||||
@Test
|
@Test
|
||||||
fun useAppContext() {
|
fun useAppContext() {
|
||||||
// Context of the app under test.
|
// Context of the app under test.
|
||||||
val appContext = InstrumentationRegistry.getTargetContext()
|
val appContext = InstrumentationRegistry.getInstrumentation().context
|
||||||
assertEquals("org.mosad.seil0.projectlaogai", appContext.packageName)
|
assertEquals("org.mosad.seil0.projectlaogai", appContext.packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,56 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="org.mosad.seil0.projectlaogai">
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_laogai_icon"
|
android:fullBackupContent="@xml/backup_descriptor"
|
||||||
android:label="@string/app_name"
|
android:icon="@mipmap/ic_laogai_icon"
|
||||||
android:roundIcon="@mipmap/ic_laogai_icon"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:roundIcon="@mipmap/ic_laogai_icon"
|
||||||
android:theme="@style/AppTheme">
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/AppTheme.Light">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".SplashActivity"
|
android:name=".SplashActivity"
|
||||||
android:label="@string/app_name"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/SplashTheme"
|
android:theme="@style/SplashTheme"
|
||||||
android:screenOrientation="portrait">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.app.shortcuts"
|
||||||
|
android:resource="@xml/shortcuts" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".OnboardingActivity"
|
||||||
android:label="@string/app_name"
|
android:launchMode="singleTop"
|
||||||
android:theme="@style/AppTheme.NoActionBar"
|
android:screenOrientation="portrait"
|
||||||
android:screenOrientation="portrait">
|
android:theme="@style/AppTheme.Light" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:theme="@style/AppTheme.Light">
|
||||||
|
|
||||||
|
<!-- nfc stuff -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.nfc.action.TECH_DISCOVERED" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.nfc.action.TECH_DISCOVERED"
|
||||||
|
android:resource="@xml/nfc_tech_filter" />
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
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
|
* ProjectLaogai
|
||||||
*
|
*
|
||||||
* Copyright 2019 <seil0@mosad.xyz>
|
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -22,96 +22,113 @@
|
|||||||
|
|
||||||
package org.mosad.seil0.projectlaogai
|
package org.mosad.seil0.projectlaogai
|
||||||
|
|
||||||
import android.graphics.Color
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.nfc.NfcAdapter
|
||||||
|
import android.nfc.NfcManager
|
||||||
|
import android.nfc.tech.NfcA
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.util.TypedValue
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.activity.addCallback
|
||||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentTransaction
|
import androidx.fragment.app.FragmentTransaction
|
||||||
import com.afollestad.aesthetic.Aesthetic
|
import androidx.fragment.app.commit
|
||||||
|
//import com.afollestad.aesthetic.Aesthetic
|
||||||
|
//import com.afollestad.aesthetic.NavigationViewMode
|
||||||
import com.google.android.material.navigation.NavigationView
|
import com.google.android.material.navigation.NavigationView
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import org.mosad.seil0.projectlaogai.controller.NFCMensaCard
|
||||||
import kotlinx.android.synthetic.main.app_bar_main.*
|
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
|
||||||
import org.mosad.seil0.projectlaogai.controller.CacheController
|
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorAccent
|
import org.mosad.seil0.projectlaogai.databinding.ActivityMainBinding
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorPrimary
|
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
|
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.coursesCacheTime
|
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.mensaCacheTime
|
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.timetableCacheTime
|
|
||||||
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
|
|
||||||
import org.mosad.seil0.projectlaogai.fragments.*
|
import org.mosad.seil0.projectlaogai.fragments.*
|
||||||
import java.sql.Date
|
import org.mosad.seil0.projectlaogai.util.NotificationUtils
|
||||||
import java.util.*
|
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO save the current fragment to show it when the app is restarted
|
||||||
|
*/
|
||||||
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
|
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityMainBinding
|
||||||
private var activeFragment: Fragment = HomeFragment() // the currently active fragment, home at the start
|
private var activeFragment: Fragment = HomeFragment() // the currently active fragment, home at the start
|
||||||
|
|
||||||
|
private lateinit var adapter: NfcAdapter
|
||||||
|
private lateinit var pendingIntent: PendingIntent
|
||||||
|
private lateinit var intentFiltersArray: Array<IntentFilter>
|
||||||
|
private lateinit var techListsArray: Array<Array<String>>
|
||||||
|
private var useNFC = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
Aesthetic.attach(this)
|
// Aesthetic.attach(this)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
|
||||||
setSupportActionBar(toolbar)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
setSupportActionBar(binding.appBar.toolbar)
|
||||||
|
|
||||||
// load mensa, timetable and color
|
// load mensa, timetable and color
|
||||||
load()
|
load()
|
||||||
|
initAesthetic()
|
||||||
// If we haven't set any defaults, do that now
|
initForegroundDispatch()
|
||||||
if (Aesthetic.isFirstTime) {
|
|
||||||
// this is executed on the first app start, use this to show tutorial etc.
|
|
||||||
Aesthetic.config {
|
|
||||||
colorPrimary(Color.BLACK)
|
|
||||||
colorPrimaryDark(Color.BLACK)
|
|
||||||
colorAccent(Color.parseColor("#FF1744"))
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsFragment().selectCourse(this)
|
|
||||||
} else {
|
|
||||||
Aesthetic.config {
|
|
||||||
colorPrimary(cColorPrimary)
|
|
||||||
colorPrimaryDark(cColorPrimary)
|
|
||||||
colorAccent(cColorAccent)
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//init home fragment
|
|
||||||
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
|
|
||||||
fragmentTransaction.replace(R.id.fragment_container, activeFragment)
|
|
||||||
fragmentTransaction.commit()
|
|
||||||
|
|
||||||
val toggle = ActionBarDrawerToggle(
|
val toggle = ActionBarDrawerToggle(
|
||||||
this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close
|
this, binding.drawerLayout, binding.appBar.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close
|
||||||
)
|
)
|
||||||
drawer_layout.addDrawerListener(toggle)
|
binding.drawerLayout.addDrawerListener(toggle)
|
||||||
toggle.syncState()
|
toggle.syncState()
|
||||||
|
|
||||||
nav_view.setNavigationItemSelectedListener(this)
|
binding.navView.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)
|
||||||
|
getString(R.string.intent_action_mensaFragment) -> activeFragment = MensaFragment()
|
||||||
|
getString(R.string.intent_action_timetableFragment) -> activeFragment = TimetableFragment()
|
||||||
|
getString(R.string.intent_action_moodleFragment) -> activeFragment = MoodleFragment()
|
||||||
|
else -> activeFragment = HomeFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
supportFragmentManager.commit {
|
||||||
|
replace(R.id.fragment_container, activeFragment, activeFragment.javaClass.simpleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
onBackPressedDispatcher.addCallback {
|
||||||
|
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
|
||||||
|
binding.drawerLayout.closeDrawer(GravityCompat.START)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
|
||||||
|
if (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action)
|
||||||
|
NFCMensaCard.readBalance(intent, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
Aesthetic.resume(this)
|
// Aesthetic.resume(this)
|
||||||
|
if(useNFC)
|
||||||
|
adapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
Aesthetic.pause(this)
|
// Aesthetic.pause(this)
|
||||||
}
|
if(useNFC)
|
||||||
|
adapter.disableForegroundDispatch(this)
|
||||||
override fun onBackPressed() {
|
|
||||||
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
|
|
||||||
drawer_layout.closeDrawer(GravityCompat.START)
|
|
||||||
} else {
|
|
||||||
super.onBackPressed()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
@ -132,29 +149,21 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
|||||||
|
|
||||||
override fun onNavigationItemSelected(item: MenuItem): Boolean {
|
override fun onNavigationItemSelected(item: MenuItem): Boolean {
|
||||||
// Handle navigation view item clicks here.
|
// Handle navigation view item clicks here.
|
||||||
when (item.itemId) {
|
activeFragment = when(item.itemId) {
|
||||||
R.id.nav_home -> {
|
R.id.nav_home -> HomeFragment()
|
||||||
activeFragment = HomeFragment()
|
R.id.nav_mensa -> MensaFragment()
|
||||||
}
|
R.id.nav_timetable -> TimetableFragment()
|
||||||
R.id.nav_mensa -> {
|
R.id.nav_moodle -> MoodleFragment()
|
||||||
activeFragment = MensaFragment()
|
R.id.nav_settings -> SettingsFragment()
|
||||||
}
|
else -> HomeFragment()
|
||||||
R.id.nav_timetable -> {
|
|
||||||
activeFragment = TimeTableFragment()
|
|
||||||
}
|
|
||||||
R.id.nav_moodle -> {
|
|
||||||
activeFragment = MoodleFragment()
|
|
||||||
}
|
|
||||||
R.id.nav_settings -> {
|
|
||||||
activeFragment = SettingsFragment()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
|
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
|
||||||
fragmentTransaction.replace(R.id.fragment_container, activeFragment)
|
fragmentTransaction.replace(R.id.fragment_container, activeFragment)
|
||||||
fragmentTransaction.commit()
|
fragmentTransaction.commit()
|
||||||
|
|
||||||
drawer_layout.closeDrawer(GravityCompat.START)
|
binding.drawerLayout.closeDrawer(GravityCompat.START)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,57 +171,61 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
|||||||
* load the mensa menus of the current week
|
* load the mensa menus of the current week
|
||||||
*/
|
*/
|
||||||
private fun load() {
|
private fun load() {
|
||||||
// load the settings
|
|
||||||
PreferencesController.load(this) // this must be finished before doing anything else
|
|
||||||
|
|
||||||
val startupTime = measureTimeMillis {
|
val startupTime = measureTimeMillis {
|
||||||
val tcor = TCoRAPIController(this)
|
Preferences.load(this) // load the settings, must be finished before doing anything else
|
||||||
val currentTime = System.currentTimeMillis() / 1000
|
CacheController(this) // load the cache
|
||||||
val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
|
EncryptedPreferences.load(this)
|
||||||
val cal = Calendar.getInstance()
|
NotificationUtils(this)
|
||||||
|
}
|
||||||
// timetable sunday workaround
|
Log.i(javaClass.simpleName, "Startup completed in $startupTime ms")
|
||||||
cal.time = Date(timetableCacheTime * 1000)
|
}
|
||||||
val timetableCacheDay = cal.get(Calendar.DAY_OF_WEEK)
|
|
||||||
|
private fun initAesthetic() {
|
||||||
// TODO this sill backfire if someone has to update before the server finished updating the timetable at 0001/0101
|
// If we haven't set any defaults, do that now
|
||||||
// update blocking if a) it`s monday and the last cache was not on a monday or b) the cache is older than 6 days
|
// if (Aesthetic.isFirstTime) {
|
||||||
if((currentDay == Calendar.MONDAY && timetableCacheDay != Calendar.MONDAY) || (System.currentTimeMillis() / 1000) - timetableCacheTime > 518400) {
|
// // set the default theme at the first app start
|
||||||
println("updating timetable after sunday!")
|
// Aesthetic.config {
|
||||||
val jobA = tcor.getTimetable(cCourse.courseName, 0)
|
// activityTheme(R.style.AppTheme_Light)
|
||||||
val jobB = tcor.getTimetable(cCourse.courseName, 1)
|
// apply()
|
||||||
|
// }
|
||||||
jobA.get()
|
// // TODO needs to be shown on first start
|
||||||
jobB.get()
|
// // show the onboarding activity
|
||||||
}
|
// startActivity(Intent(this, OnboardingActivity::class.java))
|
||||||
|
// finish()
|
||||||
// mensa sunday workaround
|
// }
|
||||||
cal.time = Date(System.currentTimeMillis()) // reset to current time
|
//
|
||||||
|
// Aesthetic.config {
|
||||||
// update blocking if it's sunday after 1500
|
// colorPrimary(Preferences.colorPrimary)
|
||||||
// TODO and the last update was before 1500
|
// colorPrimaryDark(Preferences.colorPrimary)
|
||||||
if(currentDay == Calendar.SUNDAY && cal.get(Calendar.HOUR_OF_DAY) >= 15) {
|
// colorAccent(Preferences.colorAccent)
|
||||||
val jobA = tcor.getMensa()
|
// navigationViewMode(NavigationViewMode.SELECTED_ACCENT)
|
||||||
jobA.get()
|
// apply()
|
||||||
}
|
// }
|
||||||
|
|
||||||
// get the cached files
|
// set theme color values
|
||||||
val cache = CacheController(this)
|
val out = TypedValue()
|
||||||
cache.readStartCache(cCourse.courseName)
|
this.theme.resolveAttribute(R.attr.themePrimary, out, true)
|
||||||
|
Preferences.themePrimary = out.data
|
||||||
// check if an update is necessary
|
|
||||||
if(currentTime - coursesCacheTime > 86400)
|
this.theme.resolveAttribute(R.attr.themeSecondary, out, true)
|
||||||
tcor.getCoursesList()
|
Preferences.themeSecondary = out.data
|
||||||
|
}
|
||||||
if(currentTime - mensaCacheTime > 10800)
|
|
||||||
tcor.getMensa()
|
private fun initForegroundDispatch() {
|
||||||
|
val nfcManager = this.getSystemService(Context.NFC_SERVICE) as NfcManager
|
||||||
if(currentTime - timetableCacheTime > 10800) {
|
val nfcAdapter = nfcManager.defaultAdapter
|
||||||
tcor.getTimetable(cCourse.courseName, 0)
|
|
||||||
tcor.getTimetable(cCourse.courseName, 1)
|
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),
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
}
|
}
|
||||||
println("Completed in $startupTime ms")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,172 @@
|
|||||||
|
/**
|
||||||
|
* 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.lifecycle.lifecycleScope
|
||||||
|
import androidx.viewpager.widget.ViewPager
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
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.databinding.ActivityOnboardingBinding
|
||||||
|
import org.mosad.seil0.projectlaogai.onboarding.ViewPagerAdapter
|
||||||
|
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.CourseSelectionDialog
|
||||||
|
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoadingDialog
|
||||||
|
|
||||||
|
class OnboardingActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityOnboardingBinding
|
||||||
|
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)
|
||||||
|
|
||||||
|
binding = ActivityOnboardingBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
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
|
||||||
|
binding.btnSkip.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(v: View) {
|
||||||
|
CourseSelectionDialog(this).show {
|
||||||
|
list = CacheController.coursesList.map { it.courseName }
|
||||||
|
listItems {
|
||||||
|
val loadingDialog = LoadingDialog(context)
|
||||||
|
loadingDialog.show()
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.Default) {
|
||||||
|
Preferences.saveCourse(context, CacheController.coursesList[selectedIndex]) // save the course
|
||||||
|
|
||||||
|
// update current & next weeks timetable
|
||||||
|
val threads = listOf(
|
||||||
|
CacheController.updateTimetable(Preferences.course.courseName, 0, context),
|
||||||
|
CacheController.updateTimetable(Preferences.course.courseName, 1, context)
|
||||||
|
)
|
||||||
|
threads.joinAll() // blocking since we want the new data
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
loadingDialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
binding.btnNext.text = getString(R.string.skip)
|
||||||
|
binding.btnNext.visibility = View.VISIBLE
|
||||||
|
binding.btnSkip.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
binding.btnNext.visibility = View.GONE
|
||||||
|
binding.btnSkip.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageScrolled(arg0: Int, arg1: Float, arg2: Int) {}
|
||||||
|
override fun onPageScrollStateChanged(arg0: Int) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,122 +0,0 @@
|
|||||||
/**
|
|
||||||
* ProjectLaogai
|
|
||||||
*
|
|
||||||
* Copyright 2019 <seil0@mosad.xyz>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
||||||
* MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.mosad.seil0.projectlaogai.controller
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.google.gson.JsonParser
|
|
||||||
import org.mosad.seil0.projectlaogai.hsoparser.Course
|
|
||||||
import org.mosad.seil0.projectlaogai.hsoparser.MensaWeek
|
|
||||||
import org.mosad.seil0.projectlaogai.hsoparser.TimetableWeek
|
|
||||||
import java.io.BufferedReader
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileReader
|
|
||||||
import com.google.gson.reflect.TypeToken
|
|
||||||
|
|
||||||
class CacheController(cont: Context) {
|
|
||||||
|
|
||||||
private val context = cont
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
var coursesList = ArrayList<Course>()
|
|
||||||
var mensaCurrentWeek = MensaWeek()
|
|
||||||
var mensaNextWeek = MensaWeek()
|
|
||||||
var timetables = ArrayList<TimetableWeek>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* read coursesList, mensa (current and next week), timetable (current and next week)
|
|
||||||
* @param courseName the course name (e.g AI1)
|
|
||||||
*/
|
|
||||||
fun readStartCache(courseName: String) {
|
|
||||||
readCoursesList()
|
|
||||||
readMensa()
|
|
||||||
readTimetable(courseName, 0)
|
|
||||||
readTimetable(courseName, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* read the courses list from the cached file
|
|
||||||
* add them to the coursesList object
|
|
||||||
*/
|
|
||||||
fun readCoursesList() {
|
|
||||||
val file = File(context.filesDir, "courses.json")
|
|
||||||
|
|
||||||
// make sure the file exists
|
|
||||||
if (!file.exists())
|
|
||||||
TCoRAPIController(context).getCoursesList().get()
|
|
||||||
|
|
||||||
val fileReader = FileReader(file)
|
|
||||||
val bufferedReader = BufferedReader(fileReader)
|
|
||||||
val coursesObject = JsonParser().parse(bufferedReader.readLine()).asJsonObject
|
|
||||||
|
|
||||||
coursesList = Gson().fromJson(coursesObject.getAsJsonArray("courses"), object : TypeToken<List<Course>>() {}.type)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* read current and next weeks mensa menus from the cached file
|
|
||||||
*/
|
|
||||||
fun readMensa() {
|
|
||||||
val file = File(context.filesDir, "mensa.json")
|
|
||||||
|
|
||||||
// make sure the file exists
|
|
||||||
if (!file.exists()) {
|
|
||||||
TCoRAPIController(context).getMensa().get()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val fileReader = FileReader(file)
|
|
||||||
val bufferedReader = BufferedReader(fileReader)
|
|
||||||
val mensaObject = JsonParser().parse(bufferedReader.readLine()).asJsonObject
|
|
||||||
|
|
||||||
val currentWeek = mensaObject.getAsJsonObject("currentWeek")
|
|
||||||
val nextWeek = mensaObject.getAsJsonObject("nextWeek")
|
|
||||||
|
|
||||||
mensaCurrentWeek = Gson().fromJson(currentWeek, MensaWeek().javaClass)
|
|
||||||
mensaNextWeek = Gson().fromJson(nextWeek, MensaWeek().javaClass)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* read the weeks timetable from the cached file
|
|
||||||
* @param courseName the course name (e.g AI1)
|
|
||||||
* @param week the week to read (0 for the current and so on)
|
|
||||||
*/
|
|
||||||
fun readTimetable(courseName: String, week: Int) {
|
|
||||||
val file = File(context.filesDir, "timetable-$courseName-$week.json")
|
|
||||||
|
|
||||||
// make sure the file exists
|
|
||||||
if (!file.exists())
|
|
||||||
TCoRAPIController(context).getTimetable(courseName, week).get()
|
|
||||||
|
|
||||||
val fileReader = FileReader(file)
|
|
||||||
val bufferedReader = BufferedReader(fileReader)
|
|
||||||
val timetableObject = JsonParser().parse(bufferedReader.readLine()).asJsonObject
|
|
||||||
|
|
||||||
// make sure you add the single weeks in the exact order!
|
|
||||||
if (timetables.size == week) {
|
|
||||||
timetables.add(Gson().fromJson(timetableObject.getAsJsonObject("timetable"), TimetableWeek().javaClass))
|
|
||||||
} else if (timetables.size >= week) {
|
|
||||||
timetables[week] = Gson().fromJson(timetableObject.getAsJsonObject("timetable"), TimetableWeek().javaClass)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 android.widget.TextView
|
||||||
|
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 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.findViewById<TextView>(R.id.txtView_current).text = current
|
||||||
|
dialog.findViewById<TextView>(R.id.txtView_last).text = context.resources.getString(R.string.mensa_last, last)
|
||||||
|
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,133 +0,0 @@
|
|||||||
/**
|
|
||||||
* ProjectLaogai
|
|
||||||
*
|
|
||||||
* Copyright 2019 <seil0@mosad.xyz>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
||||||
* MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.mosad.seil0.projectlaogai.controller
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Color
|
|
||||||
import org.jetbrains.anko.defaultSharedPreferences
|
|
||||||
import org.mosad.seil0.projectlaogai.R
|
|
||||||
import org.mosad.seil0.projectlaogai.hsoparser.Course
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The PreferencesController class
|
|
||||||
* contains all preferences and global variables that exist in this app
|
|
||||||
*/
|
|
||||||
class PreferencesController {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
var coursesCacheTime: Long = 0
|
|
||||||
var mensaCacheTime: Long = 0
|
|
||||||
var timetableCacheTime: Long = 0
|
|
||||||
var cColorPrimary: Int = Color.BLACK
|
|
||||||
var cColorAccent: Int = Color.parseColor("#3F51B5")
|
|
||||||
var cCourse = Course("https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0", "AI3")
|
|
||||||
var cShowBuffet = true
|
|
||||||
|
|
||||||
// the save function
|
|
||||||
fun save(context: Context) {
|
|
||||||
val sharedPref = context.defaultSharedPreferences
|
|
||||||
|
|
||||||
// save the update times (cache)
|
|
||||||
with (sharedPref.edit()) {
|
|
||||||
putLong(context.getString(R.string.save_key_coursesCacheTime),
|
|
||||||
coursesCacheTime
|
|
||||||
)
|
|
||||||
putLong(context.getString(R.string.save_key_mensaCacheTime),
|
|
||||||
mensaCacheTime
|
|
||||||
)
|
|
||||||
putLong(context.getString(R.string.save_key_timetableCacheTime),
|
|
||||||
timetableCacheTime
|
|
||||||
)
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
// save the course
|
|
||||||
with (sharedPref.edit()) {
|
|
||||||
putString(context.getString(R.string.save_key_course), cCourse.courseName)
|
|
||||||
putString(context.getString(R.string.save_key_courseTTLink), cCourse.courseLink)
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
// save the primary color
|
|
||||||
with (sharedPref.edit()) {
|
|
||||||
putInt(context.getString(R.string.save_key_colorPrimary),
|
|
||||||
cColorPrimary
|
|
||||||
)
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
// save the accent color
|
|
||||||
with (sharedPref.edit()) {
|
|
||||||
putInt(context.getString(R.string.save_key_colorAccent),
|
|
||||||
cColorAccent
|
|
||||||
)
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
// save showBuffet
|
|
||||||
with (sharedPref.edit()) {
|
|
||||||
putBoolean(context.getString(R.string.save_key_showBuffet),
|
|
||||||
cShowBuffet
|
|
||||||
)
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// the load function
|
|
||||||
fun load(context: Context) {
|
|
||||||
val sharedPref = context.defaultSharedPreferences
|
|
||||||
|
|
||||||
// load the update times (cache)
|
|
||||||
coursesCacheTime = sharedPref.getLong(context.getString(
|
|
||||||
R.string.save_key_coursesCacheTime
|
|
||||||
), 0)
|
|
||||||
mensaCacheTime = sharedPref.getLong(context.getString(
|
|
||||||
R.string.save_key_mensaCacheTime
|
|
||||||
), 0)
|
|
||||||
timetableCacheTime = sharedPref.getLong(context.getString(
|
|
||||||
R.string.save_key_timetableCacheTime
|
|
||||||
), 0)
|
|
||||||
|
|
||||||
// load saved course
|
|
||||||
cCourse = Course(
|
|
||||||
sharedPref.getString(context.getString(R.string.save_key_courseTTLink), "https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0")!!,
|
|
||||||
sharedPref.getString(context.getString(R.string.save_key_course), "AI3")!!
|
|
||||||
)
|
|
||||||
|
|
||||||
// load saved colors
|
|
||||||
cColorPrimary = sharedPref.getInt(context.getString(
|
|
||||||
R.string.save_key_colorPrimary
|
|
||||||
), Color.BLACK)
|
|
||||||
cColorAccent = sharedPref.getInt(context.getString(
|
|
||||||
R.string.save_key_colorAccent
|
|
||||||
), Color.parseColor("#3F51B5"))
|
|
||||||
|
|
||||||
// load showBuffet
|
|
||||||
cShowBuffet = sharedPref.getBoolean(context.getString(
|
|
||||||
R.string.save_key_showBuffet
|
|
||||||
), true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* ProjectLaogai
|
* ProjectLaogai
|
||||||
*
|
*
|
||||||
* Copyright 2019 <seil0@mosad.xyz>
|
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -22,75 +22,95 @@
|
|||||||
|
|
||||||
package org.mosad.seil0.projectlaogai.controller
|
package org.mosad.seil0.projectlaogai.controller
|
||||||
|
|
||||||
import android.content.Context
|
import com.google.gson.Gson
|
||||||
import org.jetbrains.anko.doAsync
|
import com.google.gson.JsonParser
|
||||||
import org.json.JSONObject
|
import com.google.gson.reflect.TypeToken
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.coursesCacheTime
|
import kotlinx.coroutines.*
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.mensaCacheTime
|
import org.mosad.seil0.projectlaogai.util.*
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.timetableCacheTime
|
|
||||||
import java.io.*
|
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
class TCoRAPIController(cont: Context) {
|
/**
|
||||||
private val context = cont
|
* This Controller calls the tcor api,
|
||||||
|
* all functions return tcor api objects
|
||||||
|
*/
|
||||||
|
class TCoRAPIController {
|
||||||
|
|
||||||
/**
|
companion object {
|
||||||
* get the json object from tcor api and write it as file (cache)
|
private const val tcorBaseURL = "https://tcor.mosad.xyz"
|
||||||
*/
|
|
||||||
fun getCoursesList() = doAsync {
|
|
||||||
val url = URL("https://tcor.mosad.xyz/courses")
|
|
||||||
val file = File(context.filesDir, "courses.json")
|
|
||||||
|
|
||||||
// read data from the API
|
/**
|
||||||
val coursesObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
|
* Get a array of all currently available courses at the tcor API.
|
||||||
|
* Read the json object from tcor api
|
||||||
|
*/
|
||||||
|
|
||||||
// write the json object to a file
|
fun getCourseListNEW(): CoursesList {
|
||||||
val writer = BufferedWriter(FileWriter(file))
|
val url = URL("$tcorBaseURL/courseList")
|
||||||
writer.write(coursesObject.toString())
|
|
||||||
writer.close()
|
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 CoroutineScope(Dispatchers.IO).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
|
||||||
|
}
|
||||||
|
|
||||||
// update cache time
|
|
||||||
coursesCacheTime = System.currentTimeMillis() / 1000
|
|
||||||
PreferencesController.save(context)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* get the json object from tcor api and write it as file (cache)
|
|
||||||
*/
|
|
||||||
fun getMensa() = doAsync {
|
|
||||||
val url = URL("https://tcor.mosad.xyz/mensamenu")
|
|
||||||
val file = File(context.filesDir, "mensa.json")
|
|
||||||
|
|
||||||
// read data from the API
|
|
||||||
val mensaObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
|
|
||||||
|
|
||||||
// write the json object to a file
|
|
||||||
val writer = BufferedWriter(FileWriter(file))
|
|
||||||
writer.write(mensaObject.toString())
|
|
||||||
writer.close()
|
|
||||||
|
|
||||||
// update cache time
|
|
||||||
mensaCacheTime = System.currentTimeMillis() / 1000
|
|
||||||
PreferencesController.save(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get the json object from tcor api and write it as file (cache)
|
|
||||||
*/
|
|
||||||
fun getTimetable(courseName: String, week: Int) = doAsync {
|
|
||||||
val url = URL("https://tcor.mosad.xyz/timetable?courseName=$courseName&week=$week")
|
|
||||||
val file = File(context.filesDir, "timetable-$courseName-$week.json")
|
|
||||||
|
|
||||||
// read data from the API
|
|
||||||
val mensaObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
|
|
||||||
|
|
||||||
// write the json object to a file
|
|
||||||
val writer = BufferedWriter(FileWriter(file))
|
|
||||||
writer.write(mensaObject.toString())
|
|
||||||
writer.close()
|
|
||||||
|
|
||||||
// update cache time
|
|
||||||
timetableCacheTime = System.currentTimeMillis() / 1000
|
|
||||||
PreferencesController.save(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
371
app/src/main/java/org/mosad/seil0/projectlaogai/controller/cache/CacheController.kt
vendored
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
/**
|
||||||
|
* 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.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
|
||||||
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cacheController reads and updates the cache files.
|
||||||
|
* It contains the courseList and mensaMenu object, all timetable objects
|
||||||
|
* are located in TimetableController.
|
||||||
|
*/
|
||||||
|
class CacheController(private val context: Context) {
|
||||||
|
|
||||||
|
private val className = this.javaClass.name
|
||||||
|
|
||||||
|
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 - mensaCacheTime) > 86400) {
|
||||||
|
Log.i(className, "Update mensa blocking")
|
||||||
|
CoroutineScope(Dispatchers.Default).launch { 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!")
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
val threads = listOf(
|
||||||
|
updateTimetable(Preferences.course.courseName, 0, context),
|
||||||
|
updateTimetable(Preferences.course.courseName, 1, context)
|
||||||
|
)
|
||||||
|
threads.joinAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCourseList(context)
|
||||||
|
|
||||||
|
readStartCache(Preferences.course.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 CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
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 CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
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 CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
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 CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
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(it, 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(it, 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(it, object : TypeToken<HashMap<String, Lesson>>() {}.type)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
FileReader(fileSubjects).use {
|
||||||
|
TimetableController.subjectMap.putAll(
|
||||||
|
GsonBuilder().create()
|
||||||
|
.fromJson(it, HashMap<String, ArrayList<String>>().javaClass)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add lessons to timetable
|
||||||
|
TimetableController.lessonMap.forEach { (_, lesson) ->
|
||||||
|
TimetableController.addLessonToTimetable(lesson)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
128
app/src/main/java/org/mosad/seil0/projectlaogai/controller/cache/TimetableController.kt
vendored
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
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()
|
||||||
|
*/
|
||||||
|
object TimetableController {
|
||||||
|
|
||||||
|
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(Preferences.course.courseName, 0, context),
|
||||||
|
CacheController.updateTimetable(Preferences.course.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,219 @@
|
|||||||
|
/**
|
||||||
|
* 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 gradesCacheTime: Long = 0
|
||||||
|
var colorPrimary: Int = Color.parseColor("#009688")
|
||||||
|
var colorAccent: Int = Color.parseColor("#0096ff")
|
||||||
|
var gradesSyncInterval = 0
|
||||||
|
var course = Course(
|
||||||
|
"https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0",
|
||||||
|
"AI3"
|
||||||
|
)
|
||||||
|
var showBuffet = 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
|
||||||
|
)
|
||||||
|
putLong(context.getString(R.string.save_key_gradesCacheTime),
|
||||||
|
gradesCacheTime
|
||||||
|
)
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.course = 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.colorPrimary = 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.colorAccent = colorAccent
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveGradesSync(context: Context, interval: 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_gradesSyncInterval),
|
||||||
|
interval
|
||||||
|
)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
gradesSyncInterval = interval
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showBuffet = 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
|
||||||
|
)
|
||||||
|
gradesCacheTime = sharedPref.getLong(
|
||||||
|
context.getString(R.string.save_key_gradesCacheTime), 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// load saved course
|
||||||
|
course = 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
|
||||||
|
colorPrimary = sharedPref.getInt(
|
||||||
|
context.getString(R.string.save_key_colorPrimary), colorPrimary
|
||||||
|
)
|
||||||
|
colorAccent = sharedPref.getInt(
|
||||||
|
context.getString(R.string.save_key_colorAccent), colorAccent
|
||||||
|
)
|
||||||
|
|
||||||
|
// load grades sync interval
|
||||||
|
gradesSyncInterval = sharedPref.getInt(
|
||||||
|
context.getString(R.string.save_key_gradesSyncInterval), gradesSyncInterval
|
||||||
|
)
|
||||||
|
|
||||||
|
// load showBuffet
|
||||||
|
showBuffet = sharedPref.getBoolean(
|
||||||
|
context.getString(R.string.save_key_showBuffet), true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* ProjectLaogai
|
* ProjectLaogai
|
||||||
*
|
*
|
||||||
* Copyright 2019 <seil0@mosad.xyz>
|
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -24,26 +24,23 @@ package org.mosad.seil0.projectlaogai.fragments
|
|||||||
|
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import androidx.lifecycle.lifecycleScope
|
||||||
import kotlinx.android.synthetic.main.fragment_home.*
|
import kotlinx.coroutines.*
|
||||||
import org.jetbrains.anko.doAsync
|
|
||||||
import org.jetbrains.anko.uiThread
|
|
||||||
import org.mosad.seil0.projectlaogai.R
|
import org.mosad.seil0.projectlaogai.R
|
||||||
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaCurrentWeek
|
import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.mensaMenu
|
||||||
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.timetables
|
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
|
||||||
import org.mosad.seil0.projectlaogai.hsoparser.DataTypes
|
import org.mosad.seil0.projectlaogai.databinding.FragmentHomeBinding
|
||||||
import org.mosad.seil0.projectlaogai.hsoparser.Meal
|
import org.mosad.seil0.projectlaogai.util.Meal
|
||||||
import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar
|
import org.mosad.seil0.projectlaogai.util.TimetableDay
|
||||||
import org.mosad.seil0.projectlaogai.hsoparser.TimetableDay
|
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.LessonLinearLayout
|
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
|
import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
|
||||||
|
import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -53,62 +50,68 @@ import java.util.*
|
|||||||
*/
|
*/
|
||||||
class HomeFragment : Fragment() {
|
class HomeFragment : Fragment() {
|
||||||
|
|
||||||
|
private val className = "HomeFragment"
|
||||||
private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault())
|
private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault())
|
||||||
|
private lateinit var binding: FragmentHomeBinding
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
|
binding = FragmentHomeBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
val view: View = inflater.inflate(R.layout.fragment_home, container, false)
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
addMensaMenu().get()
|
addMensaMenu()
|
||||||
addTimeTable()
|
addTimeTable()
|
||||||
|
|
||||||
// Inflate the layout for this fragment
|
|
||||||
return view
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* add the current mensa meal to the home screens
|
* add the current mensa meal to the home screens
|
||||||
*/
|
*/
|
||||||
private fun addMensaMenu() = doAsync {
|
private fun addMensaMenu() = lifecycleScope.launch(Dispatchers.Default) {
|
||||||
|
|
||||||
var dayMeals: ArrayList<Meal>
|
var dayMeals: ArrayList<Meal>
|
||||||
val cal = Calendar.getInstance()
|
val cal = Calendar.getInstance()
|
||||||
val mensaCardView = DayCardView(context!!)
|
val mensaCardView = DayCardView(requireContext())
|
||||||
|
|
||||||
uiThread {
|
withContext(Dispatchers.Main) {
|
||||||
|
|
||||||
if (cal.get(Calendar.HOUR_OF_DAY) < 15) {
|
if (isAdded) {
|
||||||
dayMeals = mensaCurrentWeek.days[NotRetardedCalendar().getDayOfWeekIndex()].meals
|
if (cal.get(Calendar.HOUR_OF_DAY) < 15) {
|
||||||
mensaCardView.setDayHeading(resources.getString(R.string.today_date, formatter.format(cal.time)))
|
dayMeals = mensaMenu.currentWeek.days[NotRetardedCalendar.getDayOfWeekIndex()].meals
|
||||||
} else {
|
mensaCardView.setDayHeading(getString(R.string.today_date, formatter.format(cal.time)))
|
||||||
dayMeals = mensaCurrentWeek.days[NotRetardedCalendar().getTomorrowWeekIndex()].meals
|
} else {
|
||||||
cal.add(Calendar.DATE, 1)
|
dayMeals = mensaMenu.currentWeek.days[NotRetardedCalendar.getTomorrowWeekIndex()].meals
|
||||||
mensaCardView.setDayHeading(resources.getString(R.string.tomorrow_date, formatter.format(cal.time)))
|
cal.add(Calendar.DATE, 1)
|
||||||
}
|
mensaCardView.setDayHeading(getString(R.string.tomorrow_date, formatter.format(cal.time)))
|
||||||
|
|
||||||
if (dayMeals.size >= 2) {
|
|
||||||
// get the index of the first meal, not a "Schneller Teller"
|
|
||||||
loop@ for ((i, meal) in dayMeals.withIndex()) {
|
|
||||||
if (meal.heading.contains("Essen")) {
|
|
||||||
|
|
||||||
val meal1Layout = MealLinearLayout(context)
|
|
||||||
meal1Layout.setMeal(dayMeals[i])
|
|
||||||
mensaCardView.getLinLayoutDay().addView(meal1Layout)
|
|
||||||
|
|
||||||
val meal2Layout = MealLinearLayout(context)
|
|
||||||
meal2Layout.setMeal(dayMeals[i + 1])
|
|
||||||
meal2Layout.disableDivider()
|
|
||||||
mensaCardView.getLinLayoutDay().addView(meal2Layout)
|
|
||||||
|
|
||||||
break@loop
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
if (dayMeals.size >= 2) {
|
||||||
mensaCardView.getLinLayoutDay().addView(getNoCard(resources.getString(R.string.mensa_closed)))
|
// get the index of the first meal, not a "Schneller Teller"
|
||||||
|
loop@ for ((i, meal) in dayMeals.withIndex()) {
|
||||||
|
if (meal.heading.contains("Essen")) {
|
||||||
|
|
||||||
|
val meal1Layout = MealLinearLayout(context)
|
||||||
|
meal1Layout.setMeal(dayMeals[i])
|
||||||
|
mensaCardView.getLinLayoutDay().addView(meal1Layout)
|
||||||
|
|
||||||
|
val meal2Layout = MealLinearLayout(context)
|
||||||
|
meal2Layout.setMeal(dayMeals[i + 1])
|
||||||
|
meal2Layout.disableDivider()
|
||||||
|
mensaCardView.getLinLayoutDay().addView(meal2Layout)
|
||||||
|
|
||||||
|
break@loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
mensaCardView.getLinLayoutDay().addView(getNoCard(resources.getString(R.string.mensa_closed)))
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.linLayoutHome.addView(mensaCardView)
|
||||||
}
|
}
|
||||||
|
|
||||||
linLayout_Home.addView(mensaCardView)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -116,100 +119,49 @@ class HomeFragment : Fragment() {
|
|||||||
/**
|
/**
|
||||||
* add the current timetable to the home screen
|
* add the current timetable to the home screen
|
||||||
*/
|
*/
|
||||||
private fun addTimeTable() = doAsync {
|
private fun addTimeTable() = lifecycleScope.launch(Dispatchers.Main) {
|
||||||
val dayIndex = NotRetardedCalendar().getDayOfWeekIndex()
|
if (isAdded && TimetableController.timetable.isNotEmpty()) {
|
||||||
val cal = Calendar.getInstance()
|
try {
|
||||||
var dayCardView: DayCardView
|
val dayCardView = findNextDay(NotRetardedCalendar.getDayOfWeekIndex())
|
||||||
|
binding.linLayoutHome.addView(dayCardView)
|
||||||
uiThread {
|
} catch (ex: Exception) {
|
||||||
|
Log.e(className, "could not load timetable", ex) // TODO send feedback
|
||||||
if (timetables.isNotEmpty() && dayIndex < 6) {
|
|
||||||
|
|
||||||
// first check the current day
|
|
||||||
dayCardView = addDayTimetable(timetables[0].days[dayIndex])
|
|
||||||
dayCardView.setDayHeading(resources.getString(R.string.today_date, formatter.format(cal.time)))
|
|
||||||
|
|
||||||
// if there are no lessons try to find the next day with a lesson
|
|
||||||
if (dayCardView.getLinLayoutDay().childCount <= 1)
|
|
||||||
dayCardView = findNextDay(0, dayIndex + 1)
|
|
||||||
|
|
||||||
linLayout_Home.addView(dayCardView)
|
|
||||||
} else if (dayIndex == 6) {
|
|
||||||
// if that's the case it's sunday
|
|
||||||
dayCardView = findNextDay(1, 0)
|
|
||||||
linLayout_Home.addView(dayCardView)
|
|
||||||
} else {
|
|
||||||
MaterialDialog(context!!)
|
|
||||||
.title(R.string.error)
|
|
||||||
.message(R.string.gen_tt_error)
|
|
||||||
.show()
|
|
||||||
// TODO log the error and send feedback
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* add the timetable of one day to the home-screen
|
|
||||||
* @param dayTimetable the day you wish to add
|
|
||||||
*/
|
|
||||||
private fun addDayTimetable(dayTimetable: TimetableDay) : DayCardView{
|
|
||||||
var helpLesson = LessonLinearLayout(context)
|
|
||||||
val dayCardView = DayCardView(context!!)
|
|
||||||
|
|
||||||
for ((tsIndex, timeslot) in dayTimetable.timeslots.withIndex()) {
|
|
||||||
|
|
||||||
for(lesson in timeslot) {
|
|
||||||
if(lesson.lessonSubject.isNotEmpty()) {
|
|
||||||
|
|
||||||
val lessonLayout = LessonLinearLayout(context)
|
|
||||||
lessonLayout.setLesson(lesson, DataTypes().getTime()[tsIndex])
|
|
||||||
dayCardView.getLinLayoutDay().addView(lessonLayout)
|
|
||||||
|
|
||||||
if (lesson != timeslot.last()) {
|
|
||||||
lessonLayout.disableDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
helpLesson = lessonLayout
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
helpLesson.disableDivider()
|
|
||||||
|
|
||||||
return dayCardView
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* find the next day with a lesson
|
* find the next day with a lesson
|
||||||
* @param startWeekIndex the week you want to start searching
|
* start at week 0, startDayIndex and search every cached week until we find a) a day with a timetable
|
||||||
|
* or b) we find no timetable and add a no lesson card
|
||||||
* @param startDayIndex the day index you want to start searching
|
* @param startDayIndex the day index you want to start searching
|
||||||
* @return a DayCardView with all lessons added
|
* @return a DayCardView with all lessons added
|
||||||
*/
|
*/
|
||||||
private fun findNextDay(startWeekIndex: Int, startDayIndex: Int) : DayCardView{
|
private fun findNextDay(startDayIndex: Int): DayCardView {
|
||||||
val cal = Calendar.getInstance()
|
val dayCardView = DayCardView(requireContext())
|
||||||
var dayCardView = DayCardView(context!!)
|
|
||||||
var dayTimetable: TimetableDay? = null
|
var dayTimetable: TimetableDay? = null
|
||||||
var dayIndexSearch = startDayIndex
|
var dayIndexSearch = startDayIndex
|
||||||
var weekIndexSearch = startWeekIndex
|
var weekIndexSearch = 0
|
||||||
loop@ while (dayTimetable == null && weekIndexSearch <= timetables.size) {
|
|
||||||
for (i in (dayIndexSearch) ..5) {
|
|
||||||
dayTimetable = timetables[weekIndexSearch].days[i]
|
|
||||||
cal.add(Calendar.DATE, 1)
|
|
||||||
|
|
||||||
// add the timetable to the card, if it contains at least one lesson break!
|
while (dayTimetable == null && weekIndexSearch < TimetableController.timetable.size) {
|
||||||
dayCardView = addDayTimetable(dayTimetable)
|
for (dayIndex in dayIndexSearch..5) {
|
||||||
dayCardView.setDayHeading(formatter.format(cal.time))
|
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)
|
if (dayCardView.getLinLayoutDay().childCount > 1)
|
||||||
return dayCardView
|
return dayCardView
|
||||||
}
|
}
|
||||||
dayIndexSearch = 0
|
|
||||||
weekIndexSearch++
|
weekIndexSearch++
|
||||||
cal.add(Calendar.DATE, 1)
|
dayIndexSearch = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// there was no day found in the cached weeks, add no lesson card
|
||||||
dayCardView.setDayHeading(formatter.format(Calendar.getInstance().time))
|
dayCardView.setDayHeading(formatter.format(Calendar.getInstance().time))
|
||||||
dayCardView.getLinLayoutDay().addView(getNoCard(resources.getString(R.string.no_lesson_today))) // if there is no lecture at all show the no lesson card
|
dayCardView.getLinLayoutDay().addView(getNoCard(resources.getString(R.string.no_lesson_today))) // if there is no lecture at all show the no lesson card
|
||||||
return dayCardView
|
return dayCardView
|
||||||
@ -222,11 +174,10 @@ class HomeFragment : Fragment() {
|
|||||||
private fun getNoCard(text: String): TextView {
|
private fun getNoCard(text: String): TextView {
|
||||||
val noLesson = TextView(context)
|
val noLesson = TextView(context)
|
||||||
noLesson.text = text
|
noLesson.text = text
|
||||||
noLesson.setTextColor(ContextCompat.getColor(context!!, R.color.textPrimary))
|
|
||||||
noLesson.textSize = 18.0F
|
noLesson.textSize = 18.0F
|
||||||
noLesson.setTypeface(null, Typeface.BOLD)
|
noLesson.setTypeface(null, Typeface.BOLD)
|
||||||
noLesson.textAlignment = View.TEXT_ALIGNMENT_CENTER
|
noLesson.textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||||
noLesson.setPadding(7,7,7,7)
|
noLesson.setPadding(7, 7, 7, 7)
|
||||||
|
|
||||||
return noLesson
|
return noLesson
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* ProjectLaogai
|
* ProjectLaogai
|
||||||
*
|
*
|
||||||
* Copyright 2019 <seil0@mosad.xyz>
|
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -26,19 +26,21 @@ import android.os.Bundle
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import kotlinx.android.synthetic.main.fragment_mensa.*
|
import androidx.lifecycle.lifecycleScope
|
||||||
import org.jetbrains.anko.doAsync
|
import kotlinx.coroutines.Dispatchers
|
||||||
import org.jetbrains.anko.uiThread
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.mosad.seil0.projectlaogai.R
|
import org.mosad.seil0.projectlaogai.R
|
||||||
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaCurrentWeek
|
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
|
||||||
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaNextWeek
|
import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.mensaMenu
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cShowBuffet
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||||
import org.mosad.seil0.projectlaogai.hsoparser.MensaWeek
|
import org.mosad.seil0.projectlaogai.databinding.FragmentMensaBinding
|
||||||
import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar
|
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
|
import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
|
||||||
|
import org.mosad.seil0.projectlaogai.uicomponents.TextViewInfo
|
||||||
|
import org.mosad.seil0.projectlaogai.util.MensaWeek
|
||||||
|
import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The mensa controller class
|
* The mensa controller class
|
||||||
@ -46,42 +48,58 @@ import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout
|
|||||||
*/
|
*/
|
||||||
class MensaFragment : Fragment() {
|
class MensaFragment : Fragment() {
|
||||||
|
|
||||||
private lateinit var linLayoutMensaFragment: LinearLayout
|
private lateinit var binding: FragmentMensaBinding
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
|
binding = FragmentMensaBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
val view: View = inflater.inflate(R.layout.fragment_mensa, container, false)
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
linLayoutMensaFragment = view.findViewById(R.id.linLayout_Mensa)
|
binding.refreshLayoutMensa.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
|
||||||
|
|
||||||
// add the current week (week starts on sunday)
|
initActions() // init actions
|
||||||
val dayCurrent = if(NotRetardedCalendar().getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar().getDayOfWeekIndex()
|
|
||||||
addWeek(mensaCurrentWeek, dayCurrent).get()
|
|
||||||
|
|
||||||
// add the next week
|
lifecycleScope.launch(Dispatchers.Default) {
|
||||||
addWeek(mensaNextWeek, 0).get()
|
val dayCurrent = if(NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
|
||||||
|
|
||||||
return view
|
// add the current and next week
|
||||||
|
addWeek(mensaMenu.currentWeek, dayCurrent).join()
|
||||||
|
addWeek(mensaMenu.nextWeek, 0).join()
|
||||||
|
|
||||||
|
// show a info if there are no more menus
|
||||||
|
if (binding.linLayoutMensa.childCount == 0) {
|
||||||
|
val txtViewInfo = TextViewInfo(requireContext()).set {
|
||||||
|
txt = resources.getString(R.string.no_more_meals)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) { binding.linLayoutMensa.addView(txtViewInfo) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* add all menus from dayStart to Friday for a given week
|
* 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) = doAsync {
|
private fun addWeek(menusWeek: MensaWeek, dayStart: Int) = lifecycleScope.launch(Dispatchers.Default) {
|
||||||
|
|
||||||
uiThread {
|
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
// only add the days dayStart to Fri since the mensa is closed on Sat/Sun
|
// only add the days dayStart to Fri since the mensa is closed on Sat/Sun
|
||||||
for (dayIndex in dayStart..4) {
|
for (dayIndex in dayStart..4) {
|
||||||
var helpMeal = MealLinearLayout(context)
|
var helpMeal = MealLinearLayout(context)
|
||||||
val dayCardView = DayCardView(context!!)
|
val dayCardView = DayCardView(requireContext())
|
||||||
dayCardView.setDayHeading(menusWeek.days[dayIndex].meals[0].day)
|
|
||||||
|
if(menusWeek.days[dayIndex].meals.isNotEmpty())
|
||||||
|
dayCardView.setDayHeading(menusWeek.days[dayIndex].meals[0].day)
|
||||||
|
|
||||||
for (meal in menusWeek.days[dayIndex].meals) {
|
for (meal in menusWeek.days[dayIndex].meals) {
|
||||||
val mealLayout = MealLinearLayout(context)
|
val mealLayout = MealLinearLayout(context)
|
||||||
mealLayout.setMeal(meal)
|
mealLayout.setMeal(meal)
|
||||||
|
|
||||||
if(meal.heading != "Buffet" || cShowBuffet) {
|
if(meal.heading != "Buffet" || Preferences.showBuffet) {
|
||||||
dayCardView.getLinLayoutDay().addView(mealLayout)
|
dayCardView.getLinLayoutDay().addView(mealLayout)
|
||||||
helpMeal = mealLayout
|
helpMeal = mealLayout
|
||||||
}
|
}
|
||||||
@ -89,12 +107,50 @@ class MensaFragment : Fragment() {
|
|||||||
|
|
||||||
helpMeal.disableDivider()
|
helpMeal.disableDivider()
|
||||||
|
|
||||||
if(dayCardView.getLinLayoutDay().childCount > 1)
|
if(dayCardView.getLinLayoutDay().childCount > 2)
|
||||||
linLayout_Mensa.addView(dayCardView)
|
binding.linLayoutMensa.addView(dayCardView)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initialize the actions
|
||||||
|
*/
|
||||||
|
private fun initActions() {
|
||||||
|
// set the refresh listener
|
||||||
|
binding.refreshLayoutMensa.setOnRefreshListener {
|
||||||
|
updateMensaScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* refresh the mensa cache and update the mensa screen
|
||||||
|
*/
|
||||||
|
private fun updateMensaScreen() = lifecycleScope.launch(Dispatchers.Default) {
|
||||||
|
CacheController.updateMensaMenu(requireContext()).join() // blocking since we want the new data
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
// remove all menus from the layout
|
||||||
|
binding.linLayoutMensa.removeAllViews()
|
||||||
|
|
||||||
|
// add the refreshed menus
|
||||||
|
val dayCurrent = if (NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
|
||||||
|
|
||||||
|
// add the current and next week
|
||||||
|
addWeek(mensaMenu.currentWeek, dayCurrent).join()
|
||||||
|
addWeek(mensaMenu.nextWeek, 0).join()
|
||||||
|
|
||||||
|
binding.refreshLayoutMensa.isRefreshing = false
|
||||||
|
|
||||||
|
// show a info if there are no more menus
|
||||||
|
if (binding.linLayoutMensa.childCount == 0) {
|
||||||
|
val txtViewInfo = TextViewInfo(requireContext()).set {
|
||||||
|
txt = resources.getString(R.string.no_more_meals)
|
||||||
|
}
|
||||||
|
binding.linLayoutMensa.addView(txtViewInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* ProjectLaogai
|
* ProjectLaogai
|
||||||
*
|
*
|
||||||
* Copyright 2019 <seil0@mosad.xyz>
|
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -42,8 +42,11 @@ class MoodleFragment : Fragment() {
|
|||||||
private lateinit var webSettings: WebSettings
|
private lateinit var webSettings: WebSettings
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_moodle, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
val view: View = inflater.inflate(R.layout.fragment_moodle, container, false)
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
webView = view.findViewById(R.id.webView)
|
webView = view.findViewById(R.id.webView)
|
||||||
webView.loadUrl("https://elearning.hs-offenburg.de/moodle/")
|
webView.loadUrl("https://elearning.hs-offenburg.de/moodle/")
|
||||||
@ -52,7 +55,5 @@ class MoodleFragment : Fragment() {
|
|||||||
//webSettings.setJavaScriptEnabled(true) // Enable Javascript
|
//webSettings.setJavaScriptEnabled(true) // Enable Javascript
|
||||||
|
|
||||||
webView.webViewClient = WebViewClient() // Force links and redirects to open in the WebView instead of in a browser
|
webView.webViewClient = WebViewClient() // Force links and redirects to open in the WebView instead of in a browser
|
||||||
|
|
||||||
return view
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* ProjectLaogai
|
* ProjectLaogai
|
||||||
*
|
*
|
||||||
* Copyright 2019 <seil0@mosad.xyz>
|
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -22,34 +22,35 @@
|
|||||||
|
|
||||||
package org.mosad.seil0.projectlaogai.fragments
|
package org.mosad.seil0.projectlaogai.fragments
|
||||||
|
|
||||||
import android.content.Context
|
//import com.afollestad.aesthetic.Aesthetic
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.TypedValue
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.Switch
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.afollestad.aesthetic.Aesthetic
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.afollestad.materialdialogs.color.colorChooser
|
import com.afollestad.materialdialogs.WhichButton
|
||||||
import com.afollestad.materialdialogs.customview.customView
|
import com.afollestad.materialdialogs.actions.getActionButton
|
||||||
import com.afollestad.materialdialogs.list.listItems
|
import com.afollestad.materialdialogs.list.listItemsMultiChoice
|
||||||
import kotlinx.android.synthetic.main.fragment_settings.*
|
import de.psdev.licensesdialog.LicensesDialog
|
||||||
import org.jetbrains.anko.doAsync
|
import kotlinx.coroutines.Dispatchers
|
||||||
import org.jetbrains.anko.uiThread
|
import kotlinx.coroutines.joinAll
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.mosad.seil0.projectlaogai.BuildConfig
|
import org.mosad.seil0.projectlaogai.BuildConfig
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController
|
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorAccent
|
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorPrimary
|
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
|
|
||||||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cShowBuffet
|
|
||||||
import org.mosad.seil0.projectlaogai.R
|
import org.mosad.seil0.projectlaogai.R
|
||||||
import org.mosad.seil0.projectlaogai.controller.CacheController
|
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
|
||||||
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.coursesList
|
import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.coursesList
|
||||||
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
|
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
|
||||||
import org.mosad.seil0.projectlaogai.hsoparser.DataTypes
|
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
|
||||||
import java.util.*
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||||
|
import org.mosad.seil0.projectlaogai.databinding.FragmentSettingsBinding
|
||||||
|
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.CourseSelectionDialog
|
||||||
|
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoadingDialog
|
||||||
|
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
|
||||||
|
import org.mosad.seil0.projectlaogai.util.DataTypes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The settings controller class
|
* The settings controller class
|
||||||
@ -57,138 +58,223 @@ import java.util.*
|
|||||||
*/
|
*/
|
||||||
class SettingsFragment : Fragment() {
|
class SettingsFragment : Fragment() {
|
||||||
|
|
||||||
private lateinit var linLayoutUser: LinearLayout
|
private lateinit var binding: FragmentSettingsBinding
|
||||||
private lateinit var linLayoutCourse: LinearLayout
|
|
||||||
private lateinit var linLayoutAbout: LinearLayout
|
|
||||||
private lateinit var linLayoutPrimaryColor: LinearLayout
|
|
||||||
private lateinit var linLayoutAccentColor: LinearLayout
|
|
||||||
private lateinit var switchBuffet: Switch
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
private var selectedTheme = DataTypes.Theme.Light
|
||||||
|
|
||||||
val view: View = inflater.inflate(R.layout.fragment_settings, container, false)
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
|
binding = FragmentSettingsBinding.inflate(inflater, container, false)
|
||||||
linLayoutUser = view.findViewById(R.id.linLayout_User)
|
return binding.root
|
||||||
linLayoutCourse = view.findViewById(R.id.linLayout_Course)
|
|
||||||
linLayoutAbout = view.findViewById(R.id.linLayout_About)
|
|
||||||
linLayoutPrimaryColor = view.findViewById(R.id.linLayout_PrimaryColor)
|
|
||||||
linLayoutAccentColor = view.findViewById(R.id.linLayout_AccentColor)
|
|
||||||
switchBuffet = view.findViewById(R.id.switch_buffet)
|
|
||||||
|
|
||||||
initActions()
|
|
||||||
|
|
||||||
// Inflate the layout for this fragment
|
|
||||||
return view
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initialize the settings gui
|
||||||
|
*/
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
txtView_Course.text = cCourse.courseName
|
initActions()
|
||||||
txtView_AboutDesc.text = resources.getString(R.string.about_version, BuildConfig.VERSION_NAME, getString(R.string.build_time))
|
|
||||||
switch_buffet.isChecked = cShowBuffet // init switch
|
binding.textUser.text = EncryptedPreferences.email.ifEmpty { resources.getString(R.string.sample_user) }
|
||||||
|
binding.textCourse.text = Preferences.course.courseName
|
||||||
|
binding.textAboutDesc.text = resources.getString(R.string.about_version, BuildConfig.VERSION_NAME, getString(R.string.build_time))
|
||||||
|
binding.switchBuffet.isChecked = Preferences.showBuffet // init switch
|
||||||
|
|
||||||
|
val outValue = TypedValue()
|
||||||
|
requireActivity().theme.resolveAttribute(R.attr.themeName, outValue, true)
|
||||||
|
when(outValue.string) {
|
||||||
|
"light" -> {
|
||||||
|
binding.switchBuffet.setTextColor(requireActivity().resources.getColor(R.color.textPrimaryLight, requireActivity().theme))
|
||||||
|
selectedTheme = DataTypes.Theme.Light
|
||||||
|
selectedTheme.string = resources.getString(R.string.themeLight)
|
||||||
|
}
|
||||||
|
"dark" -> {
|
||||||
|
binding.switchBuffet.setTextColor(requireActivity().resources.getColor(R.color.textPrimaryDark, requireActivity().theme))
|
||||||
|
selectedTheme = DataTypes.Theme.Dark
|
||||||
|
selectedTheme.string = resources.getString(R.string.themeDark)
|
||||||
|
}
|
||||||
|
"black" -> {
|
||||||
|
binding.switchBuffet.setTextColor(requireActivity().resources.getColor(R.color.textPrimaryDark, requireActivity().theme))
|
||||||
|
selectedTheme = DataTypes.Theme.Black
|
||||||
|
selectedTheme.string = resources.getString(R.string.themeBlack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.textThemeSelected.text = selectedTheme.string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* initialize some actions for SettingsFragment elements
|
* initialize some actions for SettingsFragment elements
|
||||||
*/
|
*/
|
||||||
private fun initActions() {
|
private fun initActions() {
|
||||||
linLayoutUser.setOnClickListener {
|
binding.linLayoutUser.setOnClickListener {
|
||||||
// open a new dialog
|
// open a new dialog
|
||||||
|
LoginDialog(requireContext())
|
||||||
|
.positiveButton {
|
||||||
|
EncryptedPreferences.saveCredentials(email, password, context)
|
||||||
|
}
|
||||||
|
.show {
|
||||||
|
email = EncryptedPreferences.email
|
||||||
|
password = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
linLayoutCourse.setOnClickListener {
|
binding.linLayoutUser.setOnLongClickListener {
|
||||||
selectCourse(context!!)
|
Preferences.oGiants = true // enable easter egg
|
||||||
txtView_Course.text = cCourse.courseName // update txtView
|
return@setOnLongClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
linLayoutAbout.setOnClickListener {
|
binding.linLayoutCourse.setOnClickListener {
|
||||||
|
CourseSelectionDialog(requireContext()).show {
|
||||||
|
list = coursesList.map { it.courseName }
|
||||||
|
listItems {
|
||||||
|
val loadingDialog = LoadingDialog(context)
|
||||||
|
loadingDialog.show()
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.Default) {
|
||||||
|
Preferences.saveCourse(context, coursesList[selectedIndex]) // save the course
|
||||||
|
|
||||||
|
// update current & next weeks timetable
|
||||||
|
val threads = listOf(
|
||||||
|
CacheController.updateTimetable(Preferences.course.courseName, 0, context),
|
||||||
|
CacheController.updateTimetable(Preferences.course.courseName, 1, context)
|
||||||
|
)
|
||||||
|
threads.joinAll() // blocking since we want the new data
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
loadingDialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onDismiss {
|
||||||
|
binding.textCourse.text = Preferences.course.courseName // update txtView after the dialog is dismissed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.linLayoutManageLessons.setOnClickListener {
|
||||||
|
val lessons = ArrayList<String>()
|
||||||
|
TimetableController.subjectMap.forEach { pair ->
|
||||||
|
pair.value.forEach {
|
||||||
|
lessons.add("${pair.key} - $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialDialog(requireContext()).show {
|
||||||
|
title(R.string.manage_lessons)
|
||||||
|
positiveButton(R.string.delete)
|
||||||
|
negativeButton(R.string.cancel)
|
||||||
|
getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.colorAccent)
|
||||||
|
getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.colorAccent)
|
||||||
|
|
||||||
|
listItemsMultiChoice(items = lessons) { _, _, items ->
|
||||||
|
items.forEach {
|
||||||
|
val list = it.split(" - ")
|
||||||
|
TimetableController.removeSubject(list[0], list[1], context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.linLayoutAbout.setOnClickListener {
|
||||||
// open a new info dialog
|
// open a new info dialog
|
||||||
MaterialDialog(context!!)
|
MaterialDialog(requireContext())
|
||||||
.title(R.string.about_dialog_heading)
|
.title(R.string.about_dialog_heading)
|
||||||
.message(R.string.about_dialog_text)
|
.message(R.string.about_dialog_text)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
linLayoutPrimaryColor.setOnClickListener {
|
binding.linLayoutLicence.setOnClickListener {
|
||||||
// open a new color chooser dialog
|
// do the theme magic, as the lib's theme support is broken
|
||||||
MaterialDialog(context!!)
|
val outValue = TypedValue()
|
||||||
.title(R.string.primary_color)
|
requireContext().theme.resolveAttribute(R.attr.themeName, outValue, true)
|
||||||
.colorChooser(DataTypes().primaryColors, allowCustomArgb = true, initialSelection = cColorPrimary) { _, color ->
|
|
||||||
view_PrimaryColor.setBackgroundColor(color)
|
|
||||||
Aesthetic.config {
|
|
||||||
colorPrimary(color)
|
|
||||||
colorPrimaryDark(color)
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
cColorPrimary = color
|
|
||||||
PreferencesController.save(context!!)
|
|
||||||
}
|
|
||||||
.positiveButton(R.string.select)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
linLayoutAccentColor.setOnClickListener {
|
|
||||||
// open a new color chooser dialog
|
|
||||||
MaterialDialog(context!!)
|
|
||||||
.title(R.string.accent_color)
|
|
||||||
.colorChooser(DataTypes().accentColors, allowCustomArgb = true, initialSelection = cColorAccent) { _, color ->
|
|
||||||
view_AccentColor.setBackgroundColor(color)
|
|
||||||
Aesthetic.config {
|
|
||||||
colorAccent(color)
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
cColorAccent = color
|
|
||||||
PreferencesController.save(context!!)
|
|
||||||
}
|
|
||||||
.positiveButton(R.string.select)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
switchBuffet.setOnClickListener {
|
|
||||||
cShowBuffet = switchBuffet.isChecked
|
|
||||||
PreferencesController.save(context!!)
|
|
||||||
println(switchBuffet.isChecked)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun selectCourse(context: Context) {
|
|
||||||
val courseNameList = ArrayList<String>()
|
|
||||||
coursesList.forEach { (_, courseName) ->
|
|
||||||
courseNameList.add(courseName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// open a new dialog
|
|
||||||
MaterialDialog(context)
|
|
||||||
.title(R.string.select_course)
|
|
||||||
.listItems(items = courseNameList) { _, index, _ ->
|
|
||||||
|
|
||||||
val dialog = MaterialDialog(context).cancelable(false)
|
|
||||||
.cancelOnTouchOutside(false)
|
|
||||||
.customView(R.layout.dialog_loading)
|
|
||||||
dialog.show()
|
|
||||||
|
|
||||||
doAsync {
|
|
||||||
cCourse = coursesList[index] // set the course
|
|
||||||
PreferencesController.save(context)
|
|
||||||
|
|
||||||
// update current & next weeks timetable
|
|
||||||
TCoRAPIController(context).getTimetable(cCourse.courseName, 0)
|
|
||||||
TCoRAPIController(context).getTimetable(cCourse.courseName, 1)
|
|
||||||
|
|
||||||
CacheController(context).readTimetable(cCourse.courseName, 0)
|
|
||||||
CacheController(context).readTimetable(cCourse.courseName, 1)
|
|
||||||
|
|
||||||
uiThread {
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
val dialogCss = when (outValue.string) {
|
||||||
|
"light" -> R.string.license_dialog_style_light
|
||||||
|
else -> R.string.license_dialog_style_dark
|
||||||
}
|
}
|
||||||
.show()
|
|
||||||
|
val themeId = when (outValue.string) {
|
||||||
|
"light" -> R.style.AppTheme_Light
|
||||||
|
else -> R.style.LicensesDialogTheme_Dark
|
||||||
|
}
|
||||||
|
|
||||||
|
// open a new license dialog
|
||||||
|
LicensesDialog.Builder(requireContext())
|
||||||
|
.setNotices(R.raw.notices)
|
||||||
|
.setTitle(R.string.licenses)
|
||||||
|
.setIncludeOwnLicense(true)
|
||||||
|
.setThemeResourceId(themeId)
|
||||||
|
.setNoticesCssStyle(dialogCss)
|
||||||
|
.build()
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// binding.linLayoutTheme.setOnClickListener {
|
||||||
|
// val themes = listOf(
|
||||||
|
// resources.getString(R.string.themeLight),
|
||||||
|
// resources.getString(R.string.themeDark),
|
||||||
|
// resources.getString(R.string.themeBlack)
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// MaterialDialog(requireContext()).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()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// binding.linLayoutPrimaryColor.setOnClickListener {
|
||||||
|
// // open a new color chooser dialog
|
||||||
|
// MaterialDialog(requireContext())
|
||||||
|
// .colorChooser(DataTypes().primaryColors, allowCustomArgb = true, initialSelection = Preferences.colorPrimary) { _, color ->
|
||||||
|
// binding.viewPrimaryColor.setBackgroundColor(color)
|
||||||
|
// Aesthetic.config {
|
||||||
|
// colorPrimary(color)
|
||||||
|
// colorPrimaryDark(color)
|
||||||
|
// apply()
|
||||||
|
// }
|
||||||
|
// Preferences.saveColorPrimary(requireContext(), color)
|
||||||
|
// }
|
||||||
|
// .show {
|
||||||
|
// title(R.string.primary_color)
|
||||||
|
// positiveButton(R.string.select)
|
||||||
|
// getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.colorAccent)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
// binding.linLayoutAccentColor.setOnClickListener {
|
||||||
|
// // open a new color chooser dialog
|
||||||
|
// MaterialDialog(requireContext())
|
||||||
|
// .colorChooser(DataTypes().accentColors, allowCustomArgb = true, initialSelection = Preferences.colorAccent) { _, color ->
|
||||||
|
// binding.viewAccentColor.setBackgroundColor(color)
|
||||||
|
// Aesthetic.config {
|
||||||
|
// colorAccent(color)
|
||||||
|
// apply()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Preferences.saveColorAccent(requireContext(), color)
|
||||||
|
// }
|
||||||
|
// .show{
|
||||||
|
// title(R.string.accent_color)
|
||||||
|
// positiveButton(R.string.select)
|
||||||
|
// getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.colorAccent)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
binding.switchBuffet.setOnClickListener {
|
||||||
|
Preferences.saveShowBuffet(requireContext(), binding.switchBuffet.isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,144 +0,0 @@
|
|||||||
/**
|
|
||||||
* ProjectLaogai
|
|
||||||
*
|
|
||||||
* Copyright 2019 <seil0@mosad.xyz>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
||||||
* MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.mosad.seil0.projectlaogai.fragments
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import kotlinx.android.synthetic.main.fragment_timetable.*
|
|
||||||
import org.jetbrains.anko.doAsync
|
|
||||||
import org.jetbrains.anko.uiThread
|
|
||||||
import org.mosad.seil0.projectlaogai.R
|
|
||||||
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.timetables
|
|
||||||
import org.mosad.seil0.projectlaogai.hsoparser.DataTypes
|
|
||||||
import org.mosad.seil0.projectlaogai.hsoparser.NotRetardedCalendar
|
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.*
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The timetable controller class
|
|
||||||
* contains all needed parts to display and the timetable detail screen
|
|
||||||
*/
|
|
||||||
class TimeTableFragment : Fragment() {
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
|
||||||
|
|
||||||
val view: View = inflater.inflate(R.layout.fragment_timetable, container, false)
|
|
||||||
|
|
||||||
if (timetables[0].days.isNotEmpty() && timetables[1].days.isNotEmpty()) {
|
|
||||||
addWeeks()
|
|
||||||
} else {
|
|
||||||
// TODO show card with error msg
|
|
||||||
}
|
|
||||||
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* add the all days with at least one lesson to the timetable screen
|
|
||||||
*/
|
|
||||||
private fun addWeeks() = doAsync {
|
|
||||||
val dayIndex = NotRetardedCalendar().getDayOfWeekIndex()
|
|
||||||
val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault()) // TODO change to android call when min api is 24
|
|
||||||
val calendar = Calendar.getInstance()
|
|
||||||
|
|
||||||
uiThread {
|
|
||||||
|
|
||||||
// add the current week
|
|
||||||
for (day in dayIndex..5) {
|
|
||||||
var helpLesson = LessonLinearLayout(context)
|
|
||||||
val dayCardView = DayCardView(context!!)
|
|
||||||
dayCardView.setDayHeading(formatter.format(calendar.time))
|
|
||||||
|
|
||||||
// for each timeslot of the day
|
|
||||||
for ((tsIndex, timeslot) in timetables[0].days[day].timeslots.withIndex()) {
|
|
||||||
|
|
||||||
for(lesson in timeslot) {
|
|
||||||
|
|
||||||
if(lesson.lessonSubject.isNotEmpty()) {
|
|
||||||
|
|
||||||
val lessonLayout = LessonLinearLayout(context)
|
|
||||||
lessonLayout.setLesson(lesson, DataTypes().getTime()[tsIndex])
|
|
||||||
dayCardView.getLinLayoutDay().addView(lessonLayout)
|
|
||||||
|
|
||||||
if (lesson != timeslot.last())
|
|
||||||
lessonLayout.disableDivider()
|
|
||||||
|
|
||||||
helpLesson = lessonLayout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
helpLesson.disableDivider()
|
|
||||||
calendar.add(Calendar.DATE, 1)
|
|
||||||
|
|
||||||
// if there are no lessons don't show the dayCardView
|
|
||||||
if (dayCardView.getLinLayoutDay().childCount > 1)
|
|
||||||
linLayout_Timetable.addView(dayCardView)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
calendar.add(Calendar.DATE, 1) // before this we are at a sunday (no lecture on sundays!)
|
|
||||||
|
|
||||||
// add the next week
|
|
||||||
for (day in 0..(dayIndex - 1)) {
|
|
||||||
var helpLesson = LessonLinearLayout(context!!)
|
|
||||||
val dayCardView = DayCardView(context!!)
|
|
||||||
dayCardView.setDayHeading(formatter.format(calendar.time))
|
|
||||||
|
|
||||||
// for each timeslot of the day
|
|
||||||
for ((tsIndex, timeslot) in timetables[1].days[day].timeslots.withIndex()) {
|
|
||||||
|
|
||||||
for(lesson in timeslot) {
|
|
||||||
|
|
||||||
if(lesson.lessonSubject.isNotEmpty()) {
|
|
||||||
|
|
||||||
val lessonLayout = LessonLinearLayout(context!!)
|
|
||||||
lessonLayout.setLesson(lesson, DataTypes().getTime()[tsIndex])
|
|
||||||
dayCardView.getLinLayoutDay().addView(lessonLayout)
|
|
||||||
|
|
||||||
if (lesson != timeslot.last())
|
|
||||||
lessonLayout.disableDivider()
|
|
||||||
|
|
||||||
helpLesson = lessonLayout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
helpLesson.disableDivider()
|
|
||||||
calendar.add(Calendar.DATE, 1)
|
|
||||||
|
|
||||||
// if there are no lessons don't show the dayCardView
|
|
||||||
if (dayCardView.getLinLayoutDay().childCount > 1)
|
|
||||||
linLayout_Timetable.addView(dayCardView)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import org.mosad.seil0.projectlaogai.R
|
||||||
|
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
|
||||||
|
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController.timetable
|
||||||
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||||
|
import org.mosad.seil0.projectlaogai.databinding.FragmentTimetableBinding
|
||||||
|
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
|
||||||
|
* contains all needed parts to display and the timetable detail screen
|
||||||
|
*/
|
||||||
|
class TimetableFragment : Fragment() {
|
||||||
|
|
||||||
|
private lateinit var binding: FragmentTimetableBinding
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
|
binding = FragmentTimetableBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
binding.refreshLayoutTimetable.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
|
||||||
|
|
||||||
|
initActions() // init actions
|
||||||
|
|
||||||
|
if (timetable.size > 1 && timetable[0].days.isNotEmpty() && timetable[1].days.isNotEmpty()) {
|
||||||
|
initTimetable()
|
||||||
|
} else {
|
||||||
|
val txtViewInfo = TextViewInfo(requireContext()).set {
|
||||||
|
txt = resources.getString(R.string.timetable_generic_error)
|
||||||
|
}.showImage()
|
||||||
|
binding.linLayoutTimetable.addView(txtViewInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initialize the actions
|
||||||
|
*/
|
||||||
|
private fun initActions() {
|
||||||
|
|
||||||
|
binding.refreshLayoutTimetable.setOnRefreshListener {
|
||||||
|
runBlocking { TimetableController.update(requireContext()).joinAll() }
|
||||||
|
reloadTimetableUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
// show the AddLessonDialog if the ftaBtn is clicked
|
||||||
|
binding.faBtnAddSubject.setOnClickListener {
|
||||||
|
AddSubjectDialog(requireContext())
|
||||||
|
.positiveButton {
|
||||||
|
TimetableController.addSubject(selectedCourse, selectedSubject, context)
|
||||||
|
runBlocking { reloadTimetableUI() }
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide the btnCardValue if the user is scrolling down
|
||||||
|
binding.scrollViewTimetable.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||||
|
if (scrollY > oldScrollY) {
|
||||||
|
binding.faBtnAddSubject.hide()
|
||||||
|
} else {
|
||||||
|
binding.faBtnAddSubject.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add the current and next weeks lessons
|
||||||
|
*/
|
||||||
|
private fun initTimetable() = lifecycleScope.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) = lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
for (dayIndex in dayBegin..dayEnd) {
|
||||||
|
val dayCardView = DayCardView(requireContext())
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
binding.linLayoutTimetable.addView(dayCardView)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clear linLayout_Timetable, add the updated timetable
|
||||||
|
*/
|
||||||
|
private fun reloadTimetableUI() = lifecycleScope.launch(Dispatchers.Default) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
// remove all lessons from the layout
|
||||||
|
binding.linLayoutTimetable.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
|
||||||
|
|
||||||
|
binding.refreshLayoutTimetable.isRefreshing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* ProjectLaogai
|
* ProjectLaogai
|
||||||
*
|
*
|
||||||
* Copyright 2019 <seil0@mosad.xyz>
|
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -24,26 +24,64 @@ package org.mosad.seil0.projectlaogai.uicomponents
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import kotlinx.android.synthetic.main.cardview_day.view.*
|
import org.mosad.seil0.projectlaogai.databinding.CardviewDayBinding
|
||||||
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) {
|
class DayCardView(context: Context) : CardView(context) {
|
||||||
|
|
||||||
init {
|
private var binding = CardviewDayBinding.inflate(LayoutInflater.from(context), this, true)
|
||||||
inflate(context, R.layout.cardview_day,this)
|
private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault())
|
||||||
|
|
||||||
// workaround to prevent a white border
|
init {
|
||||||
this.setBackgroundColor(Color.TRANSPARENT)
|
this.setBackgroundColor(Color.TRANSPARENT) // workaround to prevent a white border
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLinLayoutDay() : LinearLayout {
|
fun getLinLayoutDay() : LinearLayout {
|
||||||
return linLayout_Day
|
return binding.linearDay
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDayHeading(heading: String) {
|
fun setDayHeading(heading: String) {
|
||||||
txtView_DayHeading.text = heading
|
binding.textDayHeading.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)
|
||||||
|
binding.textDayHeading.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])
|
||||||
|
binding.linearDay.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
|
* ProjectLaogai
|
||||||
*
|
*
|
||||||
* Copyright 2019 <seil0@mosad.xyz>
|
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -23,28 +23,25 @@
|
|||||||
package org.mosad.seil0.projectlaogai.uicomponents
|
package org.mosad.seil0.projectlaogai.uicomponents
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.cardview.widget.CardView
|
import org.mosad.seil0.projectlaogai.databinding.LinearlayoutLessonBinding
|
||||||
import kotlinx.android.synthetic.main.linearlayout_lesson.view.*
|
import org.mosad.seil0.projectlaogai.util.Lesson
|
||||||
import org.mosad.seil0.projectlaogai.R
|
|
||||||
import org.mosad.seil0.projectlaogai.hsoparser.Lesson
|
|
||||||
|
|
||||||
class LessonLinearLayout(context: Context?) : LinearLayout(context) {
|
class LessonLinearLayout(context: Context?) : LinearLayout(context) {
|
||||||
|
|
||||||
init {
|
private val binding = LinearlayoutLessonBinding.inflate(LayoutInflater.from(context), this, true)
|
||||||
CardView.inflate(context, R.layout.linearlayout_lesson, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setLesson(lesson: Lesson, time: String) {
|
fun setLesson(lesson: Lesson, time: String) {
|
||||||
txtView_lessonTime.text = time
|
binding.textLessonTime.text = time
|
||||||
txtView_lessonSubject.text = lesson.lessonSubject
|
binding.textLessonSubject.text = lesson.lessonSubject
|
||||||
txtView_lessonTeacher.text = lesson.lessonTeacher
|
binding.textLessonTeacher.text = lesson.lessonTeacher
|
||||||
txtView_lessonRoom.text = lesson.lessonRoom
|
binding.textLessonRoom.text = lesson.lessonRoom
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disableDivider() {
|
fun disableDivider() {
|
||||||
divider_lesson.visibility = View.GONE
|
binding.dividerLesson.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* ProjectLaogai
|
* ProjectLaogai
|
||||||
*
|
*
|
||||||
* Copyright 2019 <seil0@mosad.xyz>
|
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -23,31 +23,28 @@
|
|||||||
package org.mosad.seil0.projectlaogai.uicomponents
|
package org.mosad.seil0.projectlaogai.uicomponents
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.cardview.widget.CardView
|
import org.mosad.seil0.projectlaogai.databinding.LinearlayoutMealBinding
|
||||||
import kotlinx.android.synthetic.main.linearlayout_meal.view.*
|
import org.mosad.seil0.projectlaogai.util.Meal
|
||||||
import org.mosad.seil0.projectlaogai.R
|
|
||||||
import org.mosad.seil0.projectlaogai.hsoparser.Meal
|
|
||||||
|
|
||||||
class MealLinearLayout(context: Context?): LinearLayout(context) {
|
class MealLinearLayout(context: Context?): LinearLayout(context) {
|
||||||
|
|
||||||
init {
|
private val binding = LinearlayoutMealBinding.inflate(LayoutInflater.from(context), this, true)
|
||||||
CardView.inflate(context, R.layout.linearlayout_meal, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setMeal(meal: Meal) {
|
fun setMeal(meal: Meal) {
|
||||||
txtView_MealHeading.text = meal.heading
|
binding.textMealHeading.text = meal.heading
|
||||||
|
|
||||||
meal.parts.forEachIndexed { partIndex, part ->
|
meal.parts.forEachIndexed { partIndex, part ->
|
||||||
txtView_Meal.append(part)
|
binding.textMeal.append(part)
|
||||||
if(partIndex < (meal.parts.size - 1))
|
if(partIndex < (meal.parts.size - 1))
|
||||||
txtView_Meal.append("\n")
|
binding.textMeal.append("\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disableDivider() {
|
fun disableDivider() {
|
||||||
divider_meal.visibility = View.GONE
|
binding.dividerMeal.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* 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,169 @@
|
|||||||
|
/**
|
||||||
|
* 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.colorAccent)
|
||||||
|
dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.colorAccent)
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun dismiss() {
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
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,47 @@
|
|||||||
|
package org.mosad.seil0.projectlaogai.uicomponents.dialogs
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import com.afollestad.materialdialogs.callbacks.onDismiss
|
||||||
|
import com.afollestad.materialdialogs.list.listItems
|
||||||
|
import org.mosad.seil0.projectlaogai.R
|
||||||
|
|
||||||
|
class CourseSelectionDialog(val context: Context) {
|
||||||
|
|
||||||
|
private val dialog = MaterialDialog(context)
|
||||||
|
|
||||||
|
var list: List<String> = listOf()
|
||||||
|
var selectedIndex = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
dialog.title(R.string.select_course)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show() {
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show(func: CourseSelectionDialog.() -> Unit): CourseSelectionDialog = apply {
|
||||||
|
func()
|
||||||
|
this.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun dismiss() {
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDismiss(func: CourseSelectionDialog.() -> Unit): CourseSelectionDialog = apply {
|
||||||
|
dialog.onDismiss {
|
||||||
|
func()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun listItems(func: CourseSelectionDialog.() -> Unit): CourseSelectionDialog = apply {
|
||||||
|
dialog.listItems(items = list) { _, index, _ ->
|
||||||
|
selectedIndex = index
|
||||||
|
|
||||||
|
func()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package org.mosad.seil0.projectlaogai.uicomponents.dialogs
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import com.afollestad.materialdialogs.customview.customView
|
||||||
|
import org.mosad.seil0.projectlaogai.R
|
||||||
|
|
||||||
|
class LoadingDialog(val context: Context) {
|
||||||
|
|
||||||
|
private val dialog = MaterialDialog(context)
|
||||||
|
|
||||||
|
init {
|
||||||
|
dialog.cancelable(false)
|
||||||
|
.cancelOnTouchOutside(false)
|
||||||
|
.customView(R.layout.dialog_loading)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show() {
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show(func: LoadingDialog.() -> Unit): LoadingDialog = apply {
|
||||||
|
func()
|
||||||
|
this.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismiss() {
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
/**
|
||||||
|
* 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.colorAccent)
|
||||||
|
dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.colorAccent)
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show(func: LoginDialog.() -> Unit): LoginDialog = apply {
|
||||||
|
func()
|
||||||
|
|
||||||
|
editTextEmail.setText(email)
|
||||||
|
editTextPassword.setText(password)
|
||||||
|
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun dismiss() {
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* ProjectLaogai
|
* ProjectLaogai
|
||||||
*
|
*
|
||||||
* Copyright 2019 <seil0@mosad.xyz>
|
* Copyright 2019-2020 <seil0@mosad.xyz>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -20,14 +20,13 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.mosad.seil0.projectlaogai.hsoparser
|
package org.mosad.seil0.projectlaogai.util
|
||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import java.util.*
|
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
class DataTypes {
|
class DataTypes {
|
||||||
val times = arrayOf("8.00 - 9.30", "9.45 - 11.15" ,"11.35 - 13.05", "14.00 -15.30", "15.45 - 17.15", "17.30 - 19.00")
|
val times = arrayOf("8.00 - 9.30", "9.45 - 11.15", "11.35 - 13.05", "14.00 -15.30", "15.45 - 17.15", "17.30 - 19.00")
|
||||||
|
|
||||||
val primaryColors = intArrayOf(
|
val primaryColors = intArrayOf(
|
||||||
Color.parseColor("#E53935"),
|
Color.parseColor("#E53935"),
|
||||||
@ -59,7 +58,7 @@ class DataTypes {
|
|||||||
Color.parseColor("#3F51B5"),
|
Color.parseColor("#3F51B5"),
|
||||||
Color.parseColor("#3D5AFE"),
|
Color.parseColor("#3D5AFE"),
|
||||||
Color.parseColor("#2979FF"),
|
Color.parseColor("#2979FF"),
|
||||||
Color.parseColor("#00B0FF"),
|
Color.parseColor("#0096FF"),
|
||||||
Color.parseColor("#00E5FF"),
|
Color.parseColor("#00E5FF"),
|
||||||
Color.parseColor("#1DE9B6"),
|
Color.parseColor("#1DE9B6"),
|
||||||
Color.parseColor("#00E676"),
|
Color.parseColor("#00E676"),
|
||||||
@ -69,58 +68,52 @@ class DataTypes {
|
|||||||
Color.parseColor("#FF9100"),
|
Color.parseColor("#FF9100"),
|
||||||
Color.parseColor("#FF3D00"),
|
Color.parseColor("#FF3D00"),
|
||||||
Color.parseColor("#000000")
|
Color.parseColor("#000000")
|
||||||
)
|
)
|
||||||
|
|
||||||
init {
|
enum class Theme(var string: String) {
|
||||||
// do something
|
Light("Light"),
|
||||||
}
|
Dark("Dark"),
|
||||||
|
Black("Black")
|
||||||
fun getTime(): Array<String> {
|
|
||||||
return times
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class NotRetardedCalendar {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// data classes for the course part
|
||||||
data class Course(val courseLink: String, val courseName: String)
|
data class Course(val courseLink: String, val courseName: String)
|
||||||
|
|
||||||
|
data class CoursesMeta(val updateTime: Long = 0, val totalCourses: Int = 0)
|
||||||
|
|
||||||
|
data class CoursesList(val meta: CoursesMeta = CoursesMeta(), val courses: ArrayList<Course> = ArrayList())
|
||||||
|
|
||||||
|
// data classes for the Mensa part
|
||||||
data class Meal(val day: String, val heading: String, val parts: ArrayList<String>, val additives: String)
|
data class Meal(val day: String, val heading: String, val parts: ArrayList<String>, val additives: String)
|
||||||
|
|
||||||
data class Meals(val meals: ArrayList<Meal>)
|
data class Meals(val meals: ArrayList<Meal>)
|
||||||
|
|
||||||
data class MensaWeek(val days: Array<Meals> = Array(7) { Meals(ArrayList()) })
|
data class MensaWeek(val days: Array<Meals> = Array(7) { Meals(ArrayList()) })
|
||||||
|
|
||||||
data class Lesson(val lessonSubject: String, val lessonTeacher: String, val lessonRoom:String, val lessonRemark: String)
|
data class MensaMeta(val updateTime: Long = 0, val mensaName: String = "")
|
||||||
|
|
||||||
data class TimetableDay( val timeslots: Array<ArrayList<Lesson>> = Array(6) { ArrayList<Lesson>()})
|
data class MensaMenu(val meta: MensaMeta = MensaMeta(), val currentWeek: MensaWeek = MensaWeek(), val nextWeek: MensaWeek = MensaWeek())
|
||||||
|
|
||||||
data class TimetableWeek(val days: Array<TimetableDay> = Array(6) { TimetableDay() })
|
// data classes for the timetable part
|
||||||
|
data class Lesson(
|
||||||
|
val 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package org.mosad.seil0.projectlaogai.util
|
||||||
|
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationUtils(val context: Context) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CHANNEL_ID_GRADES = "channel_grades"
|
||||||
|
|
||||||
|
val id = AtomicInteger(0)
|
||||||
|
|
||||||
|
fun getId() = id.incrementAndGet()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
createNotificationChannel(CHANNEL_ID_GRADES, "Grades Channel", "A Channel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(26)
|
||||||
|
private fun createNotificationChannel(channelId: String, name: String, desc: String) {
|
||||||
|
val mChannel = NotificationChannel(channelId, name, NotificationManager.IMPORTANCE_DEFAULT)
|
||||||
|
mChannel.description = desc
|
||||||
|
|
||||||
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
notificationManager.createNotificationChannel(mChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,14 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<item android:drawable="@color/colorPrimary"/>
|
<item android:drawable="@android:color/black"/>
|
||||||
|
|
||||||
<!--TODO when minAPI is 23 use this-->
|
<item android:gravity="center" android:width="144dp" android:height="144dp">
|
||||||
<!--<item android:gravity="center" android:width="144dp" android:height="144dp">-->
|
|
||||||
<item android:gravity="center">
|
|
||||||
<bitmap
|
<bitmap
|
||||||
android:gravity="fill_horizontal|fill_vertical"
|
android:gravity="fill_horizontal|fill_vertical"
|
||||||
android:src="@mipmap/ic_laogai_icon_splash"/>
|
android:src="@drawable/ic_splash_logo"/>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
</layer-list>
|
</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"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:autoMirrored="true"
|
|
||||||
android:height="24dp"
|
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:viewportHeight="24.0"
|
android:height="24dp"
|
||||||
android:viewportWidth="24.0">
|
android:autoMirrored="true"
|
||||||
<path android:fillColor="#FF000000"
|
android:viewportWidth="24.0"
|
||||||
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
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="?colorPrimary"
|
|
||||||
android:endColor="?colorPrimaryDark"
|
|
||||||
android:startColor="?colorPrimary"
|
|
||||||
android:type="linear"/>
|
|
||||||
</shape>
|
|
@ -1,19 +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:cardUseCompatPadding="true"
|
|
||||||
app:cardElevation="5dp" app:cardBackgroundColor="@color/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>
|
|
@ -1,24 +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"
|
|
||||||
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_Mensa"
|
|
||||||
android:background="@color/themePrimary">
|
|
||||||
</LinearLayout>
|
|
||||||
</ScrollView>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
@ -1,185 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context=".fragments.SettingsFragment">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
android:background="@android:color/background_light">
|
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="9dp" android:id="@+id/cardView_Info" app:cardElevation="5dp"
|
|
||||||
app:cardUseCompatPadding="true">
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<TextView
|
|
||||||
android:text="@string/info"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_Info"
|
|
||||||
android:typeface="sans"
|
|
||||||
android:textSize="18sp" android:textStyle="bold" android:fontFamily="sans-serif"
|
|
||||||
android:layout_margin="7dp" android:textColor="?colorAccent"/>
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:padding="7dp" android:id="@+id/linLayout_User">
|
|
||||||
<TextView
|
|
||||||
android:text="@string/sample_user"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_User"
|
|
||||||
android:textSize="16sp" android:textStyle="bold"
|
|
||||||
android:textColor="@android:color/primary_text_light"/>
|
|
||||||
<TextView
|
|
||||||
android:text="@string/user"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_UserDesc"/>
|
|
||||||
</LinearLayout>
|
|
||||||
<View
|
|
||||||
android:id="@+id/divider2"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:background="?android:attr/listDivider"
|
|
||||||
/>
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:layout_margin="7dp"
|
|
||||||
android:id="@+id/linLayout_Course">
|
|
||||||
<TextView
|
|
||||||
android:text="@string/sample_course"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_Course"
|
|
||||||
android:textSize="16sp" android:textColor="@android:color/primary_text_light"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
<TextView
|
|
||||||
android:text="@string/course_desc"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_CourseDesc"/>
|
|
||||||
</LinearLayout>
|
|
||||||
<View
|
|
||||||
android:id="@+id/divider"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:background="?android:attr/listDivider"
|
|
||||||
/>
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" android:layout_margin="7dp"
|
|
||||||
android:id="@+id/linLayout_About">
|
|
||||||
<TextView
|
|
||||||
android:text="@string/about_txtView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_About"
|
|
||||||
android:textStyle="bold" android:textSize="16sp"
|
|
||||||
android:textColor="@android:color/primary_text_light"/>
|
|
||||||
<TextView
|
|
||||||
android:text="@string/about_version"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_AboutDesc"/>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:id="@+id/cardView_Settings" app:cardElevation="5dp"
|
|
||||||
app:cardUseCompatPadding="true">
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/linLayout_Settings" android:padding="7dp">
|
|
||||||
<TextView
|
|
||||||
android:text="@string/settings"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_Settings"
|
|
||||||
android:typeface="sans"
|
|
||||||
android:textSize="18sp" android:textColor="?colorAccent"
|
|
||||||
android:textStyle="bold" android:paddingTop="3dp"/>
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical|end"
|
|
||||||
android:clickable="true" android:id="@+id/linLayout_PrimaryColor" android:focusable="true">
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<TextView
|
|
||||||
android:text="@string/primary_color"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_PrimaryColor"
|
|
||||||
android:textSize="16sp" android:textStyle="bold"
|
|
||||||
android:textColor="@android:color/primary_text_light"/>
|
|
||||||
<TextView
|
|
||||||
android:text="@string/primary_color_desc"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_PrimaryColorDesc"/>
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:layout_margin="7dp"
|
|
||||||
android:gravity="end">
|
|
||||||
<View
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp" android:id="@+id/view_PrimaryColor"
|
|
||||||
android:background="?colorPrimary"/>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical|end"
|
|
||||||
android:clickable="true" android:id="@+id/linLayout_AccentColor" android:focusable="true">
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<TextView
|
|
||||||
android:text="@string/accent_color"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_AccentColor"
|
|
||||||
android:textSize="16sp" android:textStyle="bold"
|
|
||||||
android:textColor="@android:color/primary_text_light"/>
|
|
||||||
<TextView
|
|
||||||
android:text="@string/accent_color_desc"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_AccentColorDesc"/>
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:layout_margin="7dp"
|
|
||||||
android:gravity="end">
|
|
||||||
<View
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp" android:id="@+id/view_AccentColor"
|
|
||||||
android:background="?colorAccent"/>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
<Switch
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/switch_buffet"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textColor="@android:color/primary_text_light" android:textStyle="bold"
|
|
||||||
android:text="@string/show_buffet"/>
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</FrameLayout>
|
|
@ -1,24 +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"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context=".fragments.TimeTableFragment">
|
|
||||||
|
|
||||||
<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_Timetable"
|
|
||||||
android:background="@color/themePrimary">
|
|
||||||
</LinearLayout>
|
|
||||||
</ScrollView>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
@ -1,35 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/linLayout_lesson" android:padding="7dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_lessonSubject" android:layout_weight="1"
|
|
||||||
android:textSize="15sp" android:textColor="@color/textPrimary"/>
|
|
||||||
<TextView
|
|
||||||
android:text="@string/a_time"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_lessonTime" android:layout_weight="1"
|
|
||||||
android:textColor="@color/textSecondary" android:gravity="end"/>
|
|
||||||
</LinearLayout>
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_lessonTeacher" android:textSize="15sp"
|
|
||||||
android:textColor="@color/textPrimary"/>
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_lessonRoom"
|
|
||||||
android:textColor="@color/textPrimary"/>
|
|
||||||
<View
|
|
||||||
android:id="@+id/divider_lesson"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:background="?android:attr/listDivider"
|
|
||||||
/>
|
|
||||||
</LinearLayout>
|
|
@ -1,23 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" android:id="@+id/linLayout_Meal" android:padding="7dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:text="@string/meal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_MealHeading" android:textSize="18sp"
|
|
||||||
android:textColor="@color/textPrimary" android:textAlignment="center" android:paddingBottom="5dp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/txtView_Meal" android:textSize="15sp"
|
|
||||||
android:textColor="@color/textPrimary" android:textAlignment="center"/>
|
|
||||||
<View
|
|
||||||
android:id="@+id/divider_meal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:background="?android:attr/listDivider"
|
|
||||||
/>
|
|
||||||
</LinearLayout>
|
|
@ -10,9 +10,10 @@
|
|||||||
tools:openDrawer="start">
|
tools:openDrawer="start">
|
||||||
|
|
||||||
<include
|
<include
|
||||||
layout="@layout/app_bar_main"
|
android:id="@+id/app_bar"
|
||||||
android:layout_width="match_parent"
|
layout="@layout/app_bar_main"
|
||||||
android:layout_height="match_parent"/>
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<com.google.android.material.navigation.NavigationView
|
<com.google.android.material.navigation.NavigationView
|
||||||
android:id="@+id/nav_view"
|
android:id="@+id/nav_view"
|
||||||
@ -20,8 +21,8 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="start"
|
android:layout_gravity="start"
|
||||||
android:fitsSystemWindows="true"
|
android:fitsSystemWindows="true"
|
||||||
|
android:background="?themeSecondary"
|
||||||
app:headerLayout="@layout/nav_header_main"
|
app:headerLayout="@layout/nav_header_main"
|
||||||
app:itemTextColor="?colorAccent"
|
|
||||||
app:menu="@menu/activity_main_drawer"/>
|
app:menu="@menu/activity_main_drawer"/>
|
||||||
|
|
||||||
</androidx.drawerlayout.widget.DrawerLayout>
|
</androidx.drawerlayout.widget.DrawerLayout>
|
@ -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>
|
27
app/src/main/res/layouts/activities/layout/cardview_day.xml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?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="?themeSecondary"
|
||||||
|
app:cardElevation="5dp"
|
||||||
|
app:cardUseCompatPadding="true">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/linear_day"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_day_heading"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/sample_date"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="?colorAccent"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</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,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/text_lesson_subject"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_lesson_time"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="end"
|
||||||
|
android:text="@string/a_time"
|
||||||
|
android:textColor="?textSecondary" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_lesson_teacher"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_lesson_room"
|
||||||
|
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/lienar_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/text_meal_heading"
|
||||||
|
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/text_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,38 +1,40 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/nav_header_main"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/nav_header_height"
|
android:layout_height="@dimen/nav_header_height"
|
||||||
android:background="@color/colorPrimary"
|
android:background="@android:color/black"
|
||||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
|
||||||
android:theme="@style/ThemeOverlay.AppCompat.Dark"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:gravity="bottom"
|
android:gravity="bottom"
|
||||||
android:id="@+id/nav_header_main">
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
android:theme="@style/ThemeOverlay.AppCompat.Dark">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/app_name"
|
||||||
android:paddingTop="@dimen/nav_header_vertical_spacing"
|
android:paddingTop="@dimen/nav_header_vertical_spacing"
|
||||||
app:srcCompat="@mipmap/ic_laogai_icon"
|
app:srcCompat="@mipmap/ic_laogai_icon" />
|
||||||
android:contentDescription="@string/nav_header_desc"
|
|
||||||
android:id="@+id/imageView"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/txtView_nav_header_title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingTop="@dimen/nav_header_vertical_spacing"
|
android:paddingTop="@dimen/nav_header_vertical_spacing"
|
||||||
android:text="@string/nav_header_title"
|
android:text="@string/nav_header_title"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:id="@+id/txtView_nav_header_title"/>
|
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||||
|
android:textColor="#FFFFFF" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/nav_header_subtitle"
|
android:text="@string/nav_header_subtitle"
|
||||||
android:id="@+id/textView"/>
|
android:textColor="#ffffff" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
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>
|
57
app/src/main/res/layouts/activities/xml/shortcuts.xml
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
|
<shortcut
|
||||||
|
android:shortcutId="grades"
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/ic_grading_black_24dp"
|
||||||
|
android:shortcutShortLabel="@string/shortcut_grades_short"
|
||||||
|
android:shortcutLongLabel="@string/shortcut_grades_long"
|
||||||
|
android:shortcutDisabledMessage="@string/shortcut_grades_disabled">
|
||||||
|
<intent
|
||||||
|
android:action="org.mosad.seil0.projectlaogai.fragments.GradesFragment"
|
||||||
|
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>
|
@ -3,7 +3,8 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".fragments.HomeFragment">
|
tools:context=".fragments.HomeFragment"
|
||||||
|
android:background="?themePrimary">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -15,7 +16,8 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" android:id="@+id/linLayout_Home">
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/linLayout_Home">
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
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 -->
|
<!-- TODO: Update blank fragment layout -->
|
||||||
<WebView
|
<WebView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" android:id="@+id/webView"/>
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/webView"/>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
@ -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>
|
358
app/src/main/res/layouts/fragments/layout/fragment_settings.xml
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
<?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"
|
||||||
|
android:paddingBottom="11dp">
|
||||||
|
|
||||||
|
<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/text_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:background="?android:selectableItemBackground"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="7dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_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/text_user_desc"
|
||||||
|
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:background="?android:selectableItemBackground"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="7dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_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/text_course_desc"
|
||||||
|
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:background="?android:selectableItemBackground"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="7dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_manage_lessons"
|
||||||
|
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:background="?android:selectableItemBackground"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="7dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_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/text_about_desc"
|
||||||
|
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:background="?android:selectableItemBackground"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="7dp">
|
||||||
|
|
||||||
|
<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/text_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:background="?android:selectableItemBackground"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="7dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_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/text_theme_selected"
|
||||||
|
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:background="?android:selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center_vertical|end"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="7dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_primary_color"
|
||||||
|
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/text_primary_color_desc"
|
||||||
|
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_primary_color"
|
||||||
|
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:background="?android:selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center_vertical|end"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="7dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_accent_color"
|
||||||
|
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/text_accent_color_desc"
|
||||||
|
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_accent_color"
|
||||||
|
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:padding="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>
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_laogai_icon_background"/>
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
<foreground android:drawable="@mipmap/ic_laogai_icon_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
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 |
BIN
app/src/main/res/raw/notenverwaltung_hs_offenburg_de.crt
Normal file
51
app/src/main/res/raw/notices.xml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<notices>
|
||||||
|
<notice>
|
||||||
|
<name>Material Dialogs</name>
|
||||||
|
<url>https://github.com/afollestad/material-dialogs</url>
|
||||||
|
<copyright>Copyright Aidan Follestad</copyright>
|
||||||
|
<license>Apache Software License 2.0</license>
|
||||||
|
</notice>
|
||||||
|
<notice>
|
||||||
|
<name>Aesthetic</name>
|
||||||
|
<url>https://github.com/afollestad/aesthetic</url>
|
||||||
|
<copyright>Copyright Aidan Follestad</copyright>
|
||||||
|
<license>Apache Software License 2.0</license>
|
||||||
|
</notice>
|
||||||
|
<notice>
|
||||||
|
<name>Gson</name>
|
||||||
|
<url>https://github.com/google/gson</url>
|
||||||
|
<copyright>Copyright 2008 Google Inc.</copyright>
|
||||||
|
<license>Apache Software License 2.0</license>
|
||||||
|
</notice>
|
||||||
|
<notice>
|
||||||
|
<name>Jsoup</name>
|
||||||
|
<url>https://jsoup.org/</url>
|
||||||
|
<copyright>Copyright 2009 - 2020 Jonathan Hedley</copyright>
|
||||||
|
<license>MIT License</license>
|
||||||
|
</notice>
|
||||||
|
<notice>
|
||||||
|
<name>kotlinx.coroutines</name>
|
||||||
|
<url>https://github.com/Kotlin/kotlinx.coroutines</url>
|
||||||
|
<copyright>Copyright JetBrains</copyright>
|
||||||
|
<license>Apache Software License 2.0</license>
|
||||||
|
</notice>
|
||||||
|
<notice>
|
||||||
|
<name>FareBot part for desfire cards</name>
|
||||||
|
<url>https://github.com/codebutler/farebot</url>
|
||||||
|
<copyright>Copyright 2011-2012, 2014, 2016 Eric Butler</copyright>
|
||||||
|
<license>GNU General Public License 3.0</license>
|
||||||
|
</notice>
|
||||||
|
<notice>
|
||||||
|
<name>AndroidX</name>
|
||||||
|
<url>https://developer.android.com/jetpack/androidx</url>
|
||||||
|
<copyright>Copyright 2018 The Android Open Source Project</copyright>
|
||||||
|
<license>Apache Software License 2.0</license>
|
||||||
|
</notice>
|
||||||
|
<notice>
|
||||||
|
<name>Material design icons</name>
|
||||||
|
<url>https://github.com/google/material-design-icons</url>
|
||||||
|
<copyright>Copyright Google Inc.</copyright>
|
||||||
|
<license>Apache Software License 2.0</license>
|
||||||
|
</notice>
|
||||||
|
</notices>
|
@ -1,31 +1,121 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<string name="navigation_drawer_close">Navigationsleiste schließen</string>
|
||||||
|
<string name="navigation_drawer_open">Navigationsleiste öffnen</string>
|
||||||
|
|
||||||
|
<!-- nav-view -->
|
||||||
<string name="home">Home</string>
|
<string name="home">Home</string>
|
||||||
<string name="mensa">Mensa</string>
|
<string name="mensa">Mensa</string>
|
||||||
<string name="timetable">Stundenplan</string>
|
<string name="timetable">Stundenplan</string>
|
||||||
<string name="moodle">Moodle</string>
|
<string name="moodle">Moodle</string>
|
||||||
|
<string name="grades">Noten</string>
|
||||||
<string name="settings">Einstellungen</string>
|
<string name="settings">Einstellungen</string>
|
||||||
|
|
||||||
|
<!-- Onboarding -->
|
||||||
|
<string name="skip">überspringen</string>
|
||||||
|
<string name="next">weiter</string>
|
||||||
|
<string name="start">fertig</string>
|
||||||
|
<string name="welcome">Willkommen bei Project Laogai.\nBevor wir loslegen können, richte bitte die App ein.</string>
|
||||||
|
<string name="get_started">los geht\'s</string>
|
||||||
|
<string name="course_heading">Studiengang</string>
|
||||||
|
<string name="course_desc_on">Wähle deinen aktuellen Studiengang.\nZusätzliche Vorlesungen können später hinzugefügt werden.</string>
|
||||||
|
<string name="login_heading">Login</string>
|
||||||
|
<string name="login_desc_on">Project Laogai kann auf die Notenverwaltung zugreifen. Deine Login-Daten werden verschlüsselt auf deinem Gerät gespeichert. Diese Funktion wird "as is" und ohne Garantie bereitgestellt.</string>
|
||||||
|
<string name="email">E-Mail</string>
|
||||||
|
<string name="password">Passwort</string>
|
||||||
|
<string name="login">login</string>
|
||||||
|
|
||||||
|
<!-- fragment_home -->
|
||||||
<string name="meal">Essen</string>
|
<string name="meal">Essen</string>
|
||||||
<string name="today_date">Heute, %1$s</string>
|
<string name="today_date">Heute, %1$s</string>
|
||||||
<string name="tomorrow_date">Morgen, %1$s</string>
|
<string name="tomorrow_date">Morgen, %1$s</string>
|
||||||
<string name="mensa_closed">keine Essensausgabe</string>
|
<string name="mensa_closed">keine Essensausgabe</string>
|
||||||
<string name="no_more_food">Diese Woche keine weitere Essensausgabe</string>
|
<string name="no_more_food">Diese Woche keine weitere Essensausgabe</string>
|
||||||
<string name="no_lesson_today">heute keine Vorlesung!</string>
|
<string name="no_lesson_today">heute keine Vorlesung!</string>
|
||||||
<string name="error">Fehler</string>
|
|
||||||
<string name="no_tt_error">Stundenplan konnte nicht geladen werden!</string>
|
<!-- fragment_mensa -->
|
||||||
<string name="gen_tt_error">Allgemeiner Stundenplan Fehler!"</string>
|
<string name="no_more_meals">Für diese und nächste Woche ist der Speiseplan leer.</string>
|
||||||
|
|
||||||
|
<!-- fragment_timetable -->
|
||||||
|
<string name="add_lesson">Eine Vorlesung hinzufügen</string>
|
||||||
|
<string name="add_lesson_desc">Füge eine Vorlesung eines anderen Studiengangs zu deinem Stundenplan hinzu.</string>
|
||||||
|
<string name="courses">Studiengänge:</string>
|
||||||
|
<string name="lessons">Vorlesungen:</string>
|
||||||
|
<string name="timetable_generic_error">Beim laden des Stundenplans ist ein Fehler aufgetreten.\n</string>
|
||||||
|
|
||||||
|
<!-- fragment_grades -->
|
||||||
|
<string name="loading_from_hs">Lade Daten von den Hochschul Servern.\nDas kann eine Weile dauern.</string>
|
||||||
|
<string name="credentials_missing">Diese Funktion benötigt deine Login-Daten. Bitte logge dich über die Einstellungen ein.</string>
|
||||||
|
<string name="qispos_unavailable">Die Notenverwaltung ist zur Zeit nicht ereichbar.\nVersuche es später noch einmal.\n</string>
|
||||||
|
<string name="qispos_generic_error">Error.\nVersuche es später noch einmal.\n</string>
|
||||||
|
<string name="without_guarantee">Alle Angaben ohne Gewähr.</string>
|
||||||
|
|
||||||
|
<!-- fragment_settings -->
|
||||||
<string name="info">Info</string>
|
<string name="info">Info</string>
|
||||||
<string name="user">Benutzer</string>
|
<string name="user_desc">Zum bearbeiten tippen</string>
|
||||||
<string name="course_desc">Tippen, um den Kurs zu ändern</string>
|
<string name="course_desc">Tippe um den Kurs zu ändern</string>
|
||||||
<string name="primary_color">Hauptfarbe</string>
|
<string name="manage_lessons">Bearbeite Zusätzliche Vorlesungen</string>
|
||||||
<string name="primary_color_desc">Die Primärfarbe, Standard ist Schwarz.</string>
|
<string name="manage_lessons_desc">Tippe um zusätzliche Vorlesungen zu bearbeiten</string>
|
||||||
<string name="accent_color">Akzentfarbe</string>
|
|
||||||
<string name="accent_color_desc">Die Akzentfarbe, Standard ist indigo</string>
|
|
||||||
<string name="select">auswählen</string>
|
|
||||||
<string name="about_dialog_heading">Über</string>
|
<string name="about_dialog_heading">Über</string>
|
||||||
<string name="loading_timetable">lade Stundenplan …</string>
|
<string name="licenses">Lizenzen</string>
|
||||||
<string name="navigation_drawer_close">Navigationsleiste schließen</string>
|
<string name="theme">Design</string>
|
||||||
<string name="navigation_drawer_open">Navigationsleiste öffnen</string>
|
<string name="themeLight">Hell</string>
|
||||||
|
<string name="themeDark">Dunkel</string>
|
||||||
|
<string name="themeBlack">Schwarz</string>
|
||||||
|
<string name="primary_color">Primärfarbe</string>
|
||||||
|
<string name="primary_color_desc">Zum Ändern tippen, Standard ist Blaugrün.</string>
|
||||||
|
<string name="accent_color">Akzentfarbe</string>
|
||||||
|
<string name="accent_color_desc">Zum Ändern tippen, Standard ist Hellblau.</string>
|
||||||
|
<string name="grades_sync">Aktualisierungsintervall Noten</string>
|
||||||
|
<string name="grades_sync_desc">%1$d Stunden</string>
|
||||||
|
<string name="grades_sync_desc_never">nie</string>
|
||||||
<string name="show_buffet">Buffet immer anzeigen</string>
|
<string name="show_buffet">Buffet immer anzeigen</string>
|
||||||
<string name="select_course">Wähle deinen Studiengang aus</string>
|
|
||||||
|
<!-- dialogs -->
|
||||||
|
<string name="select_course">Wähle deinen Studiengang</string>
|
||||||
|
<string name="loading_timetable">lade Stundenplan …</string>
|
||||||
|
<string name="add">hinzufügen</string>
|
||||||
|
<string name="delete">löschen</string>
|
||||||
|
<string name="cancel">abbrechen</string>
|
||||||
|
<string name="select">auswählen</string>
|
||||||
|
<string name="save">speichern</string>
|
||||||
|
<string name="mensa_credit">Mensa-Guthaben</string>
|
||||||
|
<string name="mensa_current">aktuell: %1$s\n</string>
|
||||||
|
<string name="mensa_last">letzte Abbuchung: %1$s</string>
|
||||||
|
|
||||||
|
<!-- notifications -->
|
||||||
|
<string name="notification_grades">Noten</string>
|
||||||
|
<string name="notification_grades_single_desc">%1$s wurden hinzugefügt oder aktualisiert</string>
|
||||||
|
<string name="notification_grades_multiple_desc">%1$s und %2$d weiter Vorlesungen wurden hinzugefügt oder aktualisiert</string>
|
||||||
|
<string name="notification_grades_updating_desc">Suche nach neuen Noten …</string>
|
||||||
|
|
||||||
|
<!-- errors -->
|
||||||
|
<string name="error">Fehler</string>
|
||||||
|
<string name="timetable_error">Der Stundenplan konnte nicht geladen werden.</string>
|
||||||
|
|
||||||
|
<!-- shortcuts -->
|
||||||
|
<string name="shortcut_mensa_short">Mensa</string>
|
||||||
|
<string name="shortcut_mensa_long">Mensa</string>
|
||||||
|
<string name="shortcut_mensa_disabled">Mensa deaktiviert</string>
|
||||||
|
|
||||||
|
<string name="shortcut_timetable_short">Stundenplan</string>
|
||||||
|
<string name="shortcut_timetable_long">Stundenplan</string>
|
||||||
|
<string name="shortcut_timetable_disabled">Stundenplan deaktiviert</string>
|
||||||
|
|
||||||
|
<string name="shortcut_moodle_short">Moodle</string>
|
||||||
|
<string name="shortcut_moodle_long">Moodle</string>
|
||||||
|
<string name="shortcut_moodle_disabled">Moodle deaktiviert</string>
|
||||||
|
|
||||||
|
<string name="shortcut_grades_short">Noten</string>
|
||||||
|
<string name="shortcut_grades_long">Noten</string>
|
||||||
|
<string name="shortcut_grades_disabled">Noten deaktiviert</string>
|
||||||
|
|
||||||
|
<string-array name="syncInterval">
|
||||||
|
<item>Manuell</item>
|
||||||
|
<item>1 Stunde</item>
|
||||||
|
<item>3 Stunden</item>
|
||||||
|
<item>6 Stunden</item>
|
||||||
|
<item>12 Stunden</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
9
app/src/main/res/values/attrs.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<attr format="string" name="themeName"/>
|
||||||
|
<attr format="color" name="themePrimary"/>
|
||||||
|
<attr format="color" name="themeSecondary"/>
|
||||||
|
<attr format="color" name="textPrimary"/>
|
||||||
|
<attr format="color" name="textSecondary"/>
|
||||||
|
<attr format="color" name="dividerColor"/>
|
||||||
|
</resources>
|