Compare commits

...

28 Commits

Author SHA1 Message Date
Jannik e7d733fc5b
update gradle wrapper, kotlin and libraries 2024-03-03 21:12:52 +01:00
Jannik 521045e0dd
remove grades/qispos 2022-12-22 17:39:42 +01:00
Jannik 3e52061a20
Update dependencies, AGP and make the app build again 2022-12-22 17:26:07 +01:00
Jannik b76f869ef0
update gradle wrapper to version 7.6 2022-12-22 16:15:53 +01:00
Jannik 175d3be882 „README.md“ ändern
continuous-integration/drone/push Build is failing Details
2020-12-23 22:29:16 +01:00
Jannik 2687a8124d „README.md“ ändern
continuous-integration/drone/push Build is failing Details
2020-12-17 17:48:21 +01:00
Jannik 5491971959
view binding, latest desing guidlines and minor updates
continuous-integration/drone/push Build was killed Details
2020-12-06 18:00:28 +01:00
Jannik b29392c6f6
fix crash in grades screen if qispos is not reachable
continuous-integration/drone/push Build is passing Details
* update to android studio 4.1
2020-10-18 15:53:37 +02:00
Jannik 4b1fb47abf
changelog 0.6.1 V2
continuous-integration/drone/push Build is passing Details
2020-10-02 14:37:44 +02:00
Jannik 0724502831
add change-log for 0.6.1
continuous-integration/drone/push Build is passing Details
2020-10-02 14:29:08 +02:00
Jannik 1162ec73e5
fix no meal info showing if first weeks meals are empty
continuous-integration/drone/push Build is passing Details
2020-10-02 14:00:42 +02:00
Jannik f40c0503c0
fix worker crash on grades sync & fix mensa blocking update on start
continuous-integration/drone/push Build is passing Details
* improve qispos logging
2020-09-17 23:23:05 +02:00
Jannik 451f6082ff
traget android sdk 30
continuous-integration/drone/push Build is passing Details
2020-09-16 18:38:13 +02:00
Jannik 36568e9682
make grades notification clickable
continuous-integration/drone/push Build is passing Details
2020-09-10 18:26:42 +02:00
Jannik 6388dfe54a
move course selection to a own class
continuous-integration/drone/push Build is passing Details
closes #48
2020-09-08 18:33:00 +02:00
Jannik c509381ec4
changelog: minor text fixes
continuous-integration/drone/push Build is passing Details
2020-09-04 11:08:34 +02:00
Jannik 390728bfb7
use KAE synthetic binding for layouts in SettingsFragment
continuous-integration/drone/push Build is passing Details
* closes #7
2020-09-02 15:36:03 +02:00
Jannik a3102bc3f2
add background grades updates with notify on changed grades
continuous-integration/drone/push Build is passing Details
2020-08-31 23:03:58 +02:00
Jannik 95ce9e14bd
test and build in one step
continuous-integration/drone/push Build is passing Details
2020-08-30 18:27:24 +02:00
Jannik d803d4dae1
use android docker image for unit tests
continuous-integration/drone/push Build is passing Details
2020-08-30 18:17:18 +02:00
Jannik 45d37f25de
use gradle jdk docker image
continuous-integration/drone/push Build is failing Details
2020-08-30 18:14:50 +02:00
Jannik 95362d5ab8
add diffGrades() to GradesController
continuous-integration/drone/push Build is failing Details
* implement unit tests for diffGrades()
2020-08-30 18:13:15 +02:00
Jannik f12873fe00
sort grades from cache by semester
continuous-integration/drone/push Build is passing Details
2020-08-30 01:38:45 +02:00
Jannik cb1f43ec66
use cache if qispos is not available
continuous-integration/drone/push Build is passing Details
2020-08-29 17:21:34 +02:00
Jannik d6eb1ff1e5
add refresh gesture to GradesFragment
continuous-integration/drone/push Build is passing Details
2020-08-28 23:21:17 +02:00
Jannik d94f38de93
save grades to a encrypted cache file, use cache if not older than 24hr
continuous-integration/drone/push Build is passing Details
2020-08-28 21:38:44 +02:00
Jannik 313ff4741a
add grades shortcut
continuous-integration/drone/push Build is passing Details
2020-08-26 17:53:03 +02:00
Jannik 07e348989a Merge pull request 'rebase develop' (#47) from master into develop
continuous-integration/drone/push Build is passing Details
Reviewed-on: #47
2020-08-25 19:26:51 +02:00
54 changed files with 1134 additions and 1432 deletions

View File

@ -2,8 +2,7 @@ kind: pipeline
name: default name: default
steps: steps:
- name: assembleRelease - name: build
image: nextcloudci/android10:android-56 image: nextcloudci/android10:android-56
commands: commands:
- ./gradlew assembleRelease - ./gradlew build

View File

@ -3,7 +3,7 @@
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg?style=flat-square)](https://www.gnu.org/licenses/gpl-3.0) [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg?style=flat-square)](https://www.gnu.org/licenses/gpl-3.0)
# Project Laogai # Project Laogai
Project Laogai is an app to access the timetable, grades (qispos) and the canteen menu of Hochschule Offenburg. Laogai uses the TCoR-API fot timetables and the canteen menu, wich makes acessing them super fast. To get the grades from Qispos, Laogai will ask for your login data, wich are stored encrypted on your device. 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://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) [<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)
@ -18,10 +18,10 @@ Project Laogai is an app to access the timetable, grades (qispos) and the cantee
Please report bugs and issues to support@mosad.xyz Please report bugs and issues to support@mosad.xyz
## Screenshots ## Screenshots
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.png) [<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.webp" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.webp)
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.png) [<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.webp" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.webp)
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.png) [<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.webp" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.webp)
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.png) [<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.webp" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.webp)
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa_dark.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa_dark.png) [<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-2020 [@Seil0](https://git.mosad.xyz/Seil0), a [mosad.xyz](http://www.mosad.xyz) Project ProjectLaogai © 2019-2020 [@Seil0](https://git.mosad.xyz/Seil0), a [mosad.xyz](http://www.mosad.xyz) Project

View File

@ -1,22 +1,35 @@
apply plugin: 'com.android.application' plugins {
apply plugin: 'kotlin-android' id 'com.android.application'
apply plugin: 'kotlin-android-extensions' id 'kotlin-android'
}
kotlin {
jvmToolchain 11
sourceSets.configureEach {
languageSettings.optIn("kotlin.RequiresOptIn")
}
}
android { android {
signingConfigs {
} compileSdk 34
compileSdkVersion 29 buildToolsVersion = '34.0.0'
defaultConfig { defaultConfig {
applicationId "org.mosad.seil0.projectlaogai" applicationId "org.mosad.seil0.projectlaogai"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 29 targetSdkVersion 33
versionCode 6000 // 0006000 versionCode 6100 // 00.06.100
versionName "0.6.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 true minifyEnabled true
@ -25,11 +38,6 @@ android {
} }
} }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
sourceSets { sourceSets {
main { main {
res.srcDirs = res.srcDirs =
@ -42,32 +50,44 @@ android {
] ]
} }
} }
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.kotlinx:kotlinx-coroutines-android:1.3.8' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
implementation 'androidx.core:core:1.3.1' implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.2.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' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.security:security-crypto:1.1.0-alpha02' implementation 'androidx.security:security-crypto:1.1.0-alpha06'
implementation 'com.google.android.material:material:1.2.0' implementation "androidx.work:work-runtime-ktx:2.9.0"
implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.google.android.material:material:1.11.0'
implementation 'com.afollestad:aesthetic:1.0.0-beta05' 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:core:3.3.0'
implementation 'com.afollestad.material-dialogs:color:3.3.0' implementation 'com.afollestad.material-dialogs:color:3.3.0'
implementation 'com.afollestad.material-dialogs:bottomsheets:3.3.0' implementation 'com.afollestad.material-dialogs:bottomsheets:3.3.0'
implementation 'de.psdev.licensesdialog:licensesdialog:2.1.0' implementation 'de.psdev.licensesdialog:licensesdialog:2.1.0'
implementation 'org.apache.commons:commons-lang3:3.11' implementation 'org.apache.commons:commons-lang3:3.11'
implementation 'org.jsoup:jsoup:1.13.1' implementation 'org.jsoup:jsoup:1.15.3'
testImplementation 'junit:junit:4.13' testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
androidTestImplementation 'androidx.test:core:1.2.0' androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
} }
static def buildTime() { static def buildTime() {

View File

@ -22,9 +22,13 @@
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-keep class org.mosad.seil0.projectlaogai.util.** { <fields>; } -keep class org.mosad.seil0.projectlaogai.util.** { <fields>; }
#Gson # Gson
-keepattributes Signature -keepattributes Signature
-dontwarn sun.misc.** -dontwarn sun.misc.**
#misc # com.google.crypto
-dontwarn javax.annotation.Nullable
-dontwarn javax.annotation.concurrent.GuardedBy
# misc
-dontwarn java.lang.instrument.ClassFileTransformer -dontwarn java.lang.instrument.ClassFileTransformer

View File

@ -1,55 +1,55 @@
<?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" /> <uses-permission android:name="android.permission.NFC" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="@xml/backup_descriptor" android:fullBackupContent="@xml/backup_descriptor"
android:icon="@mipmap/ic_laogai_icon" android:icon="@mipmap/ic_laogai_icon"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_laogai_icon" android:roundIcon="@mipmap/ic_laogai_icon"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme.Light"> 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" <meta-data
android:resource="@xml/shortcuts" /> android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity> </activity>
<activity <activity
android:name=".OnboardingActivity" android:name=".OnboardingActivity"
android:label="@string/app_name" android:launchMode="singleTop"
android:theme="@style/AppTheme.Light" android:screenOrientation="portrait"
android:screenOrientation="portrait" android:theme="@style/AppTheme.Light" />
android:launchMode="singleTop">
</activity>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:label="@string/app_name" android:exported="true"
android:theme="@style/AppTheme.Light" android:launchMode="singleTop"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:launchMode="singleTop"> android:theme="@style/AppTheme.Light">
<!-- nfc stuff --> <!-- nfc stuff -->
<intent-filter> <intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED"/> <action android:name="android.nfc.action.TECH_DISCOVERED" />
</intent-filter> </intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED" <meta-data
android:resource="@xml/nfc_tech_filter" /> android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter" />
</activity> </activity>
</application> </application>

View File

@ -34,23 +34,23 @@ import android.util.Log
import android.util.TypedValue 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.NavigationViewMode //import com.afollestad.aesthetic.Aesthetic
//import com.afollestad.aesthetic.NavigationViewMode
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.app_bar_main.*
import org.mosad.seil0.projectlaogai.controller.NFCMensaCard import org.mosad.seil0.projectlaogai.controller.NFCMensaCard
import org.mosad.seil0.projectlaogai.controller.cache.CacheController import org.mosad.seil0.projectlaogai.controller.cache.CacheController
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorAccent import org.mosad.seil0.projectlaogai.databinding.ActivityMainBinding
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorPrimary
import org.mosad.seil0.projectlaogai.fragments.* import org.mosad.seil0.projectlaogai.fragments.*
import org.mosad.seil0.projectlaogai.util.NotificationUtils
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
/** /**
@ -58,8 +58,8 @@ import kotlin.system.measureTimeMillis
*/ */
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 val className = "MainActivity"
private lateinit var adapter: NfcAdapter private lateinit var adapter: NfcAdapter
private lateinit var pendingIntent: PendingIntent private lateinit var pendingIntent: PendingIntent
@ -68,12 +68,12 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
private var useNFC = false private var useNFC = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction() // 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()
@ -81,24 +81,31 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
initForegroundDispatch() initForegroundDispatch()
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 // based on the intent we get, call readBalance or open a Fragment
when (intent.action) { when (intent.action) {
NfcAdapter.ACTION_TECH_DISCOVERED -> NFCMensaCard.readBalance(intent, this) NfcAdapter.ACTION_TECH_DISCOVERED -> NFCMensaCard.readBalance(intent, this)
"org.mosad.seil0.projectlaogai.fragments.MensaFragment" -> activeFragment = MensaFragment() getString(R.string.intent_action_mensaFragment) -> activeFragment = MensaFragment()
"org.mosad.seil0.projectlaogai.fragments.TimeTableFragment" -> activeFragment = TimeTableFragment() getString(R.string.intent_action_timetableFragment) -> activeFragment = TimetableFragment()
"org.mosad.seil0.projectlaogai.fragments.MoodleFragment" -> activeFragment = MoodleFragment() getString(R.string.intent_action_moodleFragment) -> activeFragment = MoodleFragment()
else -> activeFragment = HomeFragment()
} }
// open the activeFragment, default is the HomeFragment supportFragmentManager.commit {
fragmentTransaction.replace(R.id.fragment_container, activeFragment) replace(R.id.fragment_container, activeFragment, activeFragment.javaClass.simpleName)
fragmentTransaction.commit() }
onBackPressedDispatcher.addCallback {
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
binding.drawerLayout.closeDrawer(GravityCompat.START)
}
}
} }
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
@ -111,7 +118,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
Aesthetic.resume(this) // Aesthetic.resume(this)
if(useNFC) if(useNFC)
adapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray) adapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray)
@ -119,20 +126,11 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
Aesthetic.pause(this) // Aesthetic.pause(this)
if(useNFC) if(useNFC)
adapter.disableForegroundDispatch(this) adapter.disableForegroundDispatch(this)
} }
override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START)
} else {
// TODO only call on double tap
super.onBackPressed()
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present. // Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main, menu) menuInflater.inflate(R.menu.main, menu)
@ -154,9 +152,8 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
activeFragment = when(item.itemId) { activeFragment = when(item.itemId) {
R.id.nav_home -> HomeFragment() R.id.nav_home -> HomeFragment()
R.id.nav_mensa -> MensaFragment() R.id.nav_mensa -> MensaFragment()
R.id.nav_timetable -> TimeTableFragment() R.id.nav_timetable -> TimetableFragment()
R.id.nav_moodle -> MoodleFragment() R.id.nav_moodle -> MoodleFragment()
R.id.nav_grades -> GradesFragment()
R.id.nav_settings -> SettingsFragment() R.id.nav_settings -> SettingsFragment()
else -> HomeFragment() else -> HomeFragment()
} }
@ -165,7 +162,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
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
} }
@ -178,31 +175,32 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
Preferences.load(this) // load the settings, must be finished before doing anything else Preferences.load(this) // load the settings, must be finished before doing anything else
CacheController(this) // load the cache CacheController(this) // load the cache
EncryptedPreferences.load(this) EncryptedPreferences.load(this)
NotificationUtils(this)
} }
Log.i(className, "startup completed in $startupTime ms") Log.i(javaClass.simpleName, "Startup completed in $startupTime ms")
} }
private fun initAesthetic() { private fun initAesthetic() {
// If we haven't set any defaults, do that now // If we haven't set any defaults, do that now
if (Aesthetic.isFirstTime) { // if (Aesthetic.isFirstTime) {
// set the default theme at the first app start // // set the default theme at the first app start
Aesthetic.config { // Aesthetic.config {
activityTheme(R.style.AppTheme_Light) // activityTheme(R.style.AppTheme_Light)
apply() // apply()
} // }
// // TODO needs to be shown on first start
// show the onboarding activity // // show the onboarding activity
startActivity(Intent(this, OnboardingActivity::class.java)) // startActivity(Intent(this, OnboardingActivity::class.java))
finish() // finish()
} // }
//
Aesthetic.config { // Aesthetic.config {
colorPrimary(cColorPrimary) // colorPrimary(Preferences.colorPrimary)
colorPrimaryDark(cColorPrimary) // colorPrimaryDark(Preferences.colorPrimary)
colorAccent(cColorAccent) // colorAccent(Preferences.colorAccent)
navigationViewMode(NavigationViewMode.SELECTED_ACCENT) // navigationViewMode(NavigationViewMode.SELECTED_ACCENT)
apply() // apply()
} // }
// set theme color values // set theme color values
val out = TypedValue() val out = TypedValue()
@ -224,7 +222,8 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
adapter = NfcAdapter.getDefaultAdapter(this) adapter = NfcAdapter.getDefaultAdapter(this)
pendingIntent = PendingIntent.getActivity( pendingIntent = PendingIntent.getActivity(
this, 0, this, 0,
Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0 Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_IMMUTABLE
) )
} }
} }

View File

@ -31,15 +31,20 @@ import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.lifecycle.lifecycleScope
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.afollestad.materialdialogs.callbacks.onDismiss import kotlinx.coroutines.*
import kotlinx.android.synthetic.main.activity_onboarding.* import org.mosad.seil0.projectlaogai.controller.cache.CacheController
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
import org.mosad.seil0.projectlaogai.fragments.SettingsFragment 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.onboarding.ViewPagerAdapter
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.CourseSelectionDialog
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoadingDialog
class OnboardingActivity : AppCompatActivity() { class OnboardingActivity : AppCompatActivity() {
private lateinit var binding: ActivityOnboardingBinding
companion object { companion object {
val layouts = intArrayOf(R.layout.fragment_on_welcome, R.layout.fragment_on_course, R.layout.fragment_on_login) val layouts = intArrayOf(R.layout.fragment_on_welcome, R.layout.fragment_on_course, R.layout.fragment_on_login)
} }
@ -54,7 +59,9 @@ class OnboardingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_onboarding)
binding = ActivityOnboardingBinding.inflate(layoutInflater)
setContentView(binding.root)
viewPager = findViewById(R.id.viewPager) viewPager = findViewById(R.id.viewPager)
linLayoutDots = findViewById(R.id.linLayout_Dots) linLayoutDots = findViewById(R.id.linLayout_Dots)
@ -66,7 +73,7 @@ class OnboardingActivity : AppCompatActivity() {
viewPager.addOnPageChangeListener(viewPagerPageChangeListener) viewPager.addOnPageChangeListener(viewPagerPageChangeListener)
// we don't use the skip button, instead we use the start button to skip the last fragment // we don't use the skip button, instead we use the start button to skip the last fragment
btn_Skip.visibility = View.GONE binding.btnSkip.visibility = View.GONE
} }
fun btnNextClick(@Suppress("UNUSED_PARAMETER")v: View) { fun btnNextClick(@Suppress("UNUSED_PARAMETER")v: View) {
@ -81,8 +88,28 @@ class OnboardingActivity : AppCompatActivity() {
launchHomeScreen() launchHomeScreen()
} }
fun btnSelectCourseClick(@Suppress("UNUSED_PARAMETER")v: View) { fun btnSelectCourseClick(v: View) {
SettingsFragment().selectCourse(this).show { 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 { onDismiss {
btnNextClick(v) // show the next fragment btnNextClick(v) // show the next fragment
} }
@ -129,12 +156,12 @@ class OnboardingActivity : AppCompatActivity() {
addBottomDots(position) addBottomDots(position)
// changing the next button text to skip for the login fragment // changing the next button text to skip for the login fragment
if (position == layouts.size - 1) { if (position == layouts.size - 1) {
btn_Next.text = getString(R.string.skip) binding.btnNext.text = getString(R.string.skip)
btn_Next.visibility = View.VISIBLE binding.btnNext.visibility = View.VISIBLE
btn_Skip.visibility = View.GONE binding.btnSkip.visibility = View.GONE
} else { } else {
btn_Next.visibility = View.GONE binding.btnNext.visibility = View.GONE
btn_Skip.visibility = View.GONE binding.btnSkip.visibility = View.GONE
} }
} }

View File

@ -28,12 +28,12 @@ import android.nfc.NfcAdapter
import android.nfc.Tag import android.nfc.Tag
import android.nfc.tech.IsoDep import android.nfc.tech.IsoDep
import android.util.Log import android.util.Log
import android.widget.TextView
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView import com.afollestad.materialdialogs.customview.customView
import com.codebutler.farebot.Utils import com.codebutler.farebot.Utils
import com.codebutler.farebot.card.desfire.DesfireFileSettings import com.codebutler.farebot.card.desfire.DesfireFileSettings
import com.codebutler.farebot.card.desfire.DesfireProtocol import com.codebutler.farebot.card.desfire.DesfireProtocol
import kotlinx.android.synthetic.main.dialog_mensa_credit.*
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import java.lang.Exception import java.lang.Exception
@ -97,9 +97,9 @@ class NFCMensaCard {
String.format("%.4f shm", (lastRaw.toFloat() * 0.0000075)) String.format("%.4f shm", (lastRaw.toFloat() * 0.0000075))
} }
dialog.txtView_current.text = current dialog.findViewById<TextView>(R.id.txtView_current).text = current
dialog.txtView_last.text = context.resources.getString(R.string.mensa_last, last) dialog.findViewById<TextView>(R.id.txtView_last).text = context.resources.getString(R.string.mensa_last, last)
return dialog return dialog
} }
} }

View File

@ -1,236 +0,0 @@
/**
* ProjectLaogai
*
* Copyright 2019-2020 <seil0@mosad.xyz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
package org.mosad.seil0.projectlaogai.controller
import android.content.Context
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.jsoup.HttpStatusException
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
import org.mosad.seil0.projectlaogai.util.GradeSubject
import java.security.KeyStore
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.*
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManagerFactory
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
/**
* Parse the qispos site the get all needed data for the grades fragment
*/
class QISPOSParser(val context: Context) {
private val className = this.javaClass.name
private val baseURL = "https://notenverwaltung.hs-offenburg.de"
private val loginPath = "/qispos/rds?state=user&type=1&category=auth.login&startpage=portal.vm&breadCrumbSource=portal"
/**
* check if qispos is available
* @return a http status code, 999 if no HttpStatusException supplied
*/
fun checkQISPOSStatus(): Int {
return runBlocking {
withContext(Dispatchers.IO) {
val socketFactory = createSSLSocketFactory()
try {
val res = Jsoup.connect(baseURL + loginPath)
.sslSocketFactory(socketFactory)
.followRedirects(true)
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
.execute()
res.statusCode()
} catch (exHttp: HttpStatusException) {
exHttp.statusCode
} catch (ex: Exception) {
Log.e(className, "Error while checking status", ex)
999
}
}
}
}
/**
* parse the html from readGrades()
* @return a SortedMap, each entry is a semester, each semester has a ArrayList with subjects
*/
fun parseGrades(): SortedMap<String, ArrayList<GradeSubject>> {
val gradesMap = HashMap<String, ArrayList<GradeSubject>>()
val gradesListHtml = readGrades()
gradesListHtml?.select("table > tbody > tr")?.forEach {
val row = it.select("td.tabelle1_alignleft,td.tabelle1_aligncenter,td.tabelle1_alignright")
// only real subjects will be selected
if(row.size >= 6 && row[0].text().length >=7) {
val subject = GradeSubject(
id = row[0].text(),
name = row[1].text(),
semester = row[2].text(),
grade = if (row[3].text().isNotEmpty()) row[3].text() else row[4].text(),
credits = row[5].text()
)
if (gradesMap.containsKey(subject.semester)) {
gradesMap[subject.semester]!!.add(subject)
} else {
gradesMap[subject.semester] = arrayListOf(subject)
}
}
}
// return the sorted map
return gradesMap.toSortedMap(compareBy<String>{
val oText = it.substringAfter(" ")
if (oText.contains("/")) {
oText.substringBefore("/").toInt() + 0.5
} else {
oText.toDouble()
}
}.thenBy { it })
}
/**
* read the grades html from qispos
* @return the grades list as html element or null
*/
private fun readGrades(): Element?{
val credentials = EncryptedPreferences.readCredentials(context)
val username = credentials.first.substringBefore("@")
val password = credentials.second
return runBlocking {
withContext(Dispatchers.IO) {
try {
val socketFactory = createSSLSocketFactory()
// login, asdf = username, fdsa = password, wtf
val list = mapOf(
Pair("asdf", username),
Pair("fdsa", password),
Pair("submit", "Anmelden")
)
// login and get session cookies
val res = Jsoup.connect(baseURL + loginPath)
.sslSocketFactory(socketFactory)
.followRedirects(true)
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
.data(list)
.postDataCharset("UTF-8")
.execute()
Log.i(className, "login status is: ${res.statusMessage()}: ${res.statusCode()}")
val loginCookies = res.cookies()
// grades root document and url
val rootHtml =Jsoup.parse(res.body())
val gradesRootLink = rootHtml.select("li.menueListStyle > a.auflistung").last().attr("abs:href")
// parse grades url
val gradesHtml = Jsoup.connect(gradesRootLink)
.sslSocketFactory(socketFactory)
.followRedirects(true)
.cookies(loginCookies)
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
.get()
val gradesNextLink = gradesHtml.select("li.treelist > a.regular").attr("abs:href")
val gradesNextHtml = Jsoup.connect(gradesNextLink)
.sslSocketFactory(socketFactory)
.followRedirects(true)
.cookies(loginCookies)
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
.get()
val gradesListLink = gradesNextHtml.selectFirst("li.treelist > ul > li").selectFirst("a").attr("abs:href")
// get the grades list
val gradesListHtml = Jsoup.connect(gradesListLink)
.sslSocketFactory(socketFactory)
.followRedirects(true)
.cookies(loginCookies)
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0")
.get()
gradesListHtml.body()
} catch (ex: Exception) {
Log.e(className, "error while loading qispos", ex)
null
}
}
}
}
/**
* since the HS has a fucked up tls setup we need to work around that
*/
private fun createSSLSocketFactory(): SSLSocketFactory {
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
val caInput = context.resources.openRawResource(R.raw.notenverwaltung_hs_offenburg_de)
val ca = caInput.use {
cf.generateCertificate(it) as X509Certificate
}
// Create a KeyStore containing our trusted CAs
val keyStoreType = KeyStore.getDefaultType()
val keyStore = KeyStore.getInstance(keyStoreType).apply {
load(null, null)
setCertificateEntry("ca", ca)
}
// Create a TrustManager that trusts the CAs inputStream our KeyStore
val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
val tmf = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
init(keyStore)
}
// Create an SSLContext that uses our TrustManager
val sslContext = SSLContext.getInstance("TLS").apply {
init(null, tmf.trustManagers, null)
}
return sslContext.socketFactory
}
}

View File

@ -84,7 +84,7 @@ class TCoRAPIController {
fun getSubjectListAsync(courseName: String, week: Int): Deferred<ArrayList<String>> { fun getSubjectListAsync(courseName: String, week: Int): Deferred<ArrayList<String>> {
val url = URL("$tcorBaseURL/subjectList?course=$courseName&week=$week") val url = URL("$tcorBaseURL/subjectList?course=$courseName&week=$week")
return GlobalScope.async { return CoroutineScope(Dispatchers.IO).async {
Gson().fromJson(url.readText(), ArrayList<String>()::class.java) Gson().fromJson(url.readText(), ArrayList<String>()::class.java)
} }
} }

View File

@ -31,7 +31,6 @@ import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.coursesCacheTime import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.coursesCacheTime
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.mensaCacheTime import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.mensaCacheTime
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.timetableCacheTime import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.timetableCacheTime
@ -39,16 +38,16 @@ import org.mosad.seil0.projectlaogai.util.*
import java.io.* import java.io.*
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashMap
/** /**
* The cacheController reads and updates the cache files. * The cacheController reads and updates the cache files.
* It contains the courseList and mensaMenu object, all timetable objects * It contains the courseList and mensaMenu object, all timetable objects
* are located in TimetableController. * are located in TimetableController.
*/ */
class CacheController(cont: Context) { class CacheController(private val context: Context) {
private val className = "CacheController" private val className = this.javaClass.name
private val context = cont
init { init {
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
@ -59,9 +58,9 @@ class CacheController(cont: Context) {
cal.time = Date(mensaCacheTime * 1000) 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 a) it's monday and the last cache update was on sunday or b) the cache is older than 24hr, update blocking
if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - mensaMenu.meta.updateTime) > 86400) { if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - mensaCacheTime) > 86400) {
Log.i(className, "update mensa blocking") Log.i(className, "Update mensa blocking")
GlobalScope.launch(Dispatchers.Default) { updateMensaMenu(context).join() } CoroutineScope(Dispatchers.Default).launch { updateMensaMenu(context).join() }
} }
// check if we need to update the timetable before displaying it // check if we need to update the timetable before displaying it
@ -69,12 +68,12 @@ class CacheController(cont: Context) {
// 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 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) { if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - timetableCacheTime) > 86400) {
Log.i(className, "updating timetable after sunday!") Log.i(className, "Updating timetable after sunday!")
GlobalScope.launch(Dispatchers.Default) { CoroutineScope(Dispatchers.Default).launch {
val threads = listOf( val threads = listOf(
updateTimetable(cCourse.courseName, 0, context), updateTimetable(Preferences.course.courseName, 0, context),
updateTimetable(cCourse.courseName, 1, context) updateTimetable(Preferences.course.courseName, 1, context)
) )
threads.joinAll() threads.joinAll()
} }
@ -82,7 +81,7 @@ class CacheController(cont: Context) {
updateCourseList(context) updateCourseList(context)
readStartCache(cCourse.courseName) // initially read values from cache readStartCache(Preferences.course.courseName) // initially read values from cache
// check if an update is necessary, not blocking // check if an update is necessary, not blocking
if (currentTime - coursesCacheTime > 86400) if (currentTime - coursesCacheTime > 86400)
@ -91,10 +90,8 @@ class CacheController(cont: Context) {
if (currentTime - mensaCacheTime > 10800) if (currentTime - mensaCacheTime > 10800)
updateMensaMenu(context) updateMensaMenu(context)
if (currentTime - timetableCacheTime > 10800) { if (currentTime - timetableCacheTime > 10800)
TimetableController.update(context) TimetableController.update(context)
}
} }
companion object { companion object {
@ -109,7 +106,7 @@ class CacheController(cont: Context) {
val file = File(context.filesDir, "courses.json") val file = File(context.filesDir, "courses.json")
var courseListUp = CoursesList() var courseListUp = CoursesList()
return GlobalScope.launch(Dispatchers.IO) { return CoroutineScope(Dispatchers.IO).launch {
try { try {
courseListUp = TCoRAPIController.getCourseListNEW() courseListUp = TCoRAPIController.getCourseListNEW()
} catch (ex: Exception) { } catch (ex: Exception) {
@ -132,7 +129,7 @@ class CacheController(cont: Context) {
fun updateMensaMenu(context: Context): Job { fun updateMensaMenu(context: Context): Job {
val file = File(context.filesDir, "mensa.json") val file = File(context.filesDir, "mensa.json")
return GlobalScope.launch(Dispatchers.IO) { return CoroutineScope(Dispatchers.IO).launch {
try { try {
mensaMenu = TCoRAPIController.getMensaMenu() mensaMenu = TCoRAPIController.getMensaMenu()
} catch (ex: Exception) { } catch (ex: Exception) {
@ -156,7 +153,7 @@ class CacheController(cont: Context) {
var timetable = TimetableWeek() var timetable = TimetableWeek()
// try to update timetable from tcor, async // try to update timetable from tcor, async
return GlobalScope.launch(Dispatchers.IO) { return CoroutineScope(Dispatchers.IO).launch {
try { try {
timetable = TCoRAPIController.getTimetable(courseName, week) timetable = TCoRAPIController.getTimetable(courseName, week)
} catch (ex: Exception) { } catch (ex: Exception) {
@ -183,7 +180,7 @@ class CacheController(cont: Context) {
fun updateAdditionalLessons(context: Context): Job { fun updateAdditionalLessons(context: Context): Job {
val fileLessons = File(context.filesDir, "additional_lessons.json") val fileLessons = File(context.filesDir, "additional_lessons.json")
return GlobalScope.launch(Dispatchers.IO) { return CoroutineScope(Dispatchers.IO).launch {
TimetableController.subjectMap.forEach { (courseName, subjects) -> TimetableController.subjectMap.forEach { (courseName, subjects) ->
// update all subjects for a course // update all subjects for a course
subjects.forEach {subject -> subjects.forEach {subject ->
@ -222,7 +219,6 @@ class CacheController(cont: Context) {
Log.e(className, "failed to write file \"${file.absoluteFile}\"", ex) Log.e(className, "failed to write file \"${file.absoluteFile}\"", ex)
} }
} }
} }
/** /**
@ -275,7 +271,7 @@ class CacheController(cont: Context) {
} }
coursesList = FileReader(file).use { coursesList = FileReader(file).use {
GsonBuilder().create().fromJson(BufferedReader(it).readLine(), CoursesList().javaClass).courses GsonBuilder().create().fromJson(it, CoursesList().javaClass).courses
} }
} }
@ -293,7 +289,7 @@ class CacheController(cont: Context) {
} }
mensaMenu = FileReader(file).use { mensaMenu = FileReader(file).use {
GsonBuilder().create().fromJson(BufferedReader(it).readLine(), MensaMenu().javaClass) GsonBuilder().create().fromJson(it, MensaMenu().javaClass)
} }
} }
@ -354,14 +350,14 @@ class CacheController(cont: Context) {
FileReader(fileLessons).use { FileReader(fileLessons).use {
TimetableController.lessonMap.putAll( TimetableController.lessonMap.putAll(
GsonBuilder().create() GsonBuilder().create()
.fromJson(BufferedReader(it).readLine(), object : TypeToken<HashMap<String, Lesson>>() {}.type) .fromJson(it, object : TypeToken<HashMap<String, Lesson>>() {}.type)
) )
} }
FileReader(fileSubjects).use { FileReader(fileSubjects).use {
TimetableController.subjectMap.putAll( TimetableController.subjectMap.putAll(
GsonBuilder().create() GsonBuilder().create()
.fromJson(BufferedReader(it).readLine(), HashMap<String, ArrayList<String>>().javaClass) .fromJson(it, HashMap<String, ArrayList<String>>().javaClass)
) )
} }

View File

@ -25,7 +25,7 @@ package org.mosad.seil0.projectlaogai.controller.cache
import android.content.Context import android.content.Context
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.util.Lesson import org.mosad.seil0.projectlaogai.util.Lesson
import org.mosad.seil0.projectlaogai.util.TimetableWeek import org.mosad.seil0.projectlaogai.util.TimetableWeek
@ -38,94 +38,91 @@ import org.mosad.seil0.projectlaogai.util.TimetableWeek
* * add second week * * add second week
* * add configurable week to addSubject() and removeSubject(), updateAdditionalLessons() * * add configurable week to addSubject() and removeSubject(), updateAdditionalLessons()
*/ */
class TimetableController { object TimetableController {
companion object { val timetable = ArrayList<TimetableWeek>()
val timetable = ArrayList<TimetableWeek>() val lessonMap = HashMap<String, Lesson>() // the key is courseName-subject-lessonID
val lessonMap = HashMap<String, Lesson>() // the key is courseName-subject-lessonID val subjectMap = HashMap<String, ArrayList<String>>() // the key is courseName
val subjectMap = HashMap<String, ArrayList<String>>() // the key is courseName
/** /**
* update the main timetable and all additional subjects, async * update the main timetable and all additional subjects, async
*/ */
fun update(context: Context): List<Job> { fun update(context: Context): List<Job> {
return listOf( return listOf(
CacheController.updateTimetable(cCourse.courseName, 0, context), CacheController.updateTimetable(Preferences.course.courseName, 0, context),
CacheController.updateTimetable(cCourse.courseName, 1, context), CacheController.updateTimetable(Preferences.course.courseName, 1, context),
CacheController.updateAdditionalLessons(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
* add a subject to the subjectMap and all it's lessons TCoRAPIController.getLessons(courseName, subject, 0).forEach { lesson ->
* to the lessonMap addLesson(courseName, subject, lesson)
* @param courseName course to which the subject belongs }
* @param subject the subjects name
*/ CacheController.saveAdditionalSubjects(context)
fun addSubject(courseName: String, subject: String, context: Context) { }
// add subject
if (subjectMap.containsKey(courseName)) { /**
subjectMap[courseName]?.add(subject) * remove a subject from the subjectMap and all it's lessons
} else { * from the lessonMap
subjectMap[courseName] = arrayListOf(subject) * @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)
} }
// add concrete lessons
TCoRAPIController.getLessons(courseName, subject, 0).forEach { lesson ->
addLesson(courseName, subject, lesson)
}
CacheController.saveAdditionalSubjects(context)
} }
/** 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() * add a lesson to the lessonMap, also add it to the timetable
while (iterator.hasNext()) { */
val it = iterator.next() fun addLesson(courseName: String, subject: String, lesson: Lesson) {
if(it.key.contains("$courseName-$subject")) { //the courseName, subject and lessonID, separator: -
// remove the lesson from the lessons list val key = "$courseName-$subject-${lesson.lessonID}"
iterator.remove() // use iterator to remove, otherwise ConcurrentModificationException lessonMap[key] = lesson
// remove the lesson from the timetable addLessonToTimetable(lesson)
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)
}
/**
* 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)
} }
} }

View File

@ -36,13 +36,15 @@ object Preferences {
var coursesCacheTime: Long = 0 var coursesCacheTime: Long = 0
var mensaCacheTime: Long = 0 var mensaCacheTime: Long = 0
var timetableCacheTime: Long = 0 var timetableCacheTime: Long = 0
var cColorPrimary: Int = Color.parseColor("#009688") var gradesCacheTime: Long = 0
var cColorAccent: Int = Color.parseColor("#0096ff") var colorPrimary: Int = Color.parseColor("#009688")
var cCourse = Course( 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", "https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0",
"AI3" "AI3"
) )
var cShowBuffet = true var showBuffet = true
var oGiants = false var oGiants = false
// TODO move! // TODO move!
@ -67,6 +69,9 @@ object Preferences {
putLong(context.getString(R.string.save_key_timetableCacheTime), putLong(context.getString(R.string.save_key_timetableCacheTime),
timetableCacheTime timetableCacheTime
) )
putLong(context.getString(R.string.save_key_gradesCacheTime),
gradesCacheTime
)
apply() apply()
} }
@ -87,7 +92,7 @@ object Preferences {
apply() apply()
} }
cCourse = course this.course = course
} }
/** /**
@ -106,7 +111,7 @@ object Preferences {
apply() apply()
} }
cColorPrimary = colorPrimary this.colorPrimary = colorPrimary
} }
/** /**
@ -125,7 +130,23 @@ object Preferences {
apply() apply()
} }
cColorAccent = colorAccent 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
} }
/** /**
@ -144,7 +165,7 @@ object Preferences {
apply() apply()
} }
cShowBuffet = showBuffet this.showBuffet = showBuffet
} }
/** /**
@ -157,36 +178,42 @@ object Preferences {
) )
// load the update times (cache) // load the update times (cache)
coursesCacheTime = sharedPref.getLong(context.getString( coursesCacheTime = sharedPref.getLong(
R.string.save_key_coursesCacheTime context.getString(R.string.save_key_coursesCacheTime),0
), 0) )
mensaCacheTime = sharedPref.getLong(context.getString( mensaCacheTime = sharedPref.getLong(
R.string.save_key_mensaCacheTime context.getString(R.string.save_key_mensaCacheTime),0
), 0) )
timetableCacheTime = sharedPref.getLong(context.getString( timetableCacheTime = sharedPref.getLong(
R.string.save_key_timetableCacheTime context.getString(R.string.save_key_timetableCacheTime),0
), 0) )
gradesCacheTime = sharedPref.getLong(
context.getString(R.string.save_key_gradesCacheTime), 0
)
// load saved course // load saved course
cCourse = Course( course = Course(
sharedPref.getString(context.getString(R.string.save_key_courseTTLink), 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")!!,
"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")!! sharedPref.getString(context.getString(R.string.save_key_course), "AI3")!!
) )
// load saved colors // load saved colors
cColorPrimary = sharedPref.getInt(context.getString( colorPrimary = sharedPref.getInt(
R.string.save_key_colorPrimary context.getString(R.string.save_key_colorPrimary), colorPrimary
), cColorPrimary) )
cColorAccent = sharedPref.getInt(context.getString( colorAccent = sharedPref.getInt(
R.string.save_key_colorAccent context.getString(R.string.save_key_colorAccent), colorAccent
), cColorAccent) )
// load grades sync interval
gradesSyncInterval = sharedPref.getInt(
context.getString(R.string.save_key_gradesSyncInterval), gradesSyncInterval
)
// load showBuffet // load showBuffet
cShowBuffet = sharedPref.getBoolean(context.getString( showBuffet = sharedPref.getBoolean(
R.string.save_key_showBuffet context.getString(R.string.save_key_showBuffet), true
), true) )
} }
} }

View File

@ -1,200 +0,0 @@
/**
* ProjectLaogai
*
* Copyright 2019-2020 <seil0@mosad.xyz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
package org.mosad.seil0.projectlaogai.fragments
import android.graphics.Rect
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.Fragment
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import kotlinx.android.synthetic.main.fragment_grades.*
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.QISPOSParser
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.GradeLinearLayout
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
/**
* The grades fragment class
* contains all needed parts to display and the grades screen
*/
class GradesFragment : Fragment() {
private lateinit var refreshLayoutGrades: SwipeRefreshLayout
private lateinit var parser: QISPOSParser
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_grades, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
refreshLayoutGrades = view.findViewById(R.id.refreshLayout_Grades)
refreshLayoutGrades.isEnabled = false // disable swipe
refreshLayoutGrades.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
parser = QISPOSParser(context!!)// init the parser
if (checkCredentials() && checkQisposStatus()) {
GlobalScope.launch(Dispatchers.Default) {
addGrades()
}
}
}
/**
* check if the credentials are set, if not show a login dialog
*/
private fun checkCredentials(): Boolean {
val credentials = EncryptedPreferences.readCredentials(context!!)
var credentialsPresent = false
// if there is no password set, show the login dialog
if (credentials.first.isEmpty() || credentials.second.isEmpty()) {
LoginDialog(this.context!!)
.positiveButton {
EncryptedPreferences.saveCredentials(email, password, context)
addGrades()
}
.negativeButton {
txtView_Loading.text = resources.getString(R.string.credentials_missing)
}
.show {
email = EncryptedPreferences.email
password = ""
}
} else {
credentialsPresent = true
}
return credentialsPresent
}
/**
* check if qispos is available, if not show an error
*/
private fun checkQisposStatus(): Boolean {
val statusCode = parser.checkQISPOSStatus()
// show error if the status code is not 200
if (statusCode != 200) {
val infoText = resources.getString(when(statusCode) {
503 -> R.string.qispos_unavailable
else -> R.string.qispos_generic_error
})
val img = ResourcesCompat.getDrawable(resources, R.drawable.ic_error_outline_black_24dp, null)?.apply {
bounds = Rect(0, 0, 75, 75)
}
txtView_Loading?.apply {
text = infoText
setCompoundDrawables(null, null, null, img)
}
}
return statusCode == 200
}
// add the grades to the layout, async
private fun addGrades() = GlobalScope.launch(Dispatchers.Default) {
withContext(Dispatchers.Main) {
refreshLayout_Grades.isRefreshing = true
}
val grades = parser.parseGrades()
withContext(Dispatchers.Main) {
linLayout_Grades.removeAllViews() // clear layout
}
// for each semester add a new card
grades.forEach { semester ->
val usedSubjects = ArrayList<String>()
val semesterCard = DayCardView(context!!)
semesterCard.setDayHeading(semester.key)
// for each subject add a new linLayout
semester.value.forEachIndexed { index, subject ->
if (usedSubjects.contains(subject.name)) {
return@forEachIndexed
}
// get the first sub subjects
val subSubject = semester.value.firstOrNull {
it.name.contains(subject.name) && it.name != subject.name
}
// if sub subject is not null, add it to used subjects
subSubject?.let {
usedSubjects.add(it.name)
}
val subjectLayout = GradeLinearLayout(context).set {
subjectName = subject.name
grade = subject.grade
subSubjectName = subSubject?.name.toString()
subGrade = subSubject?.grade.toString()
}
// disable sub-subject if not set
if (subSubject == null)
subjectLayout.disableSubSubject()
// disable divider if last element
if (index == semester.value.lastIndex || semester.value.indexOf(subSubject) == semester.value.lastIndex)
subjectLayout.disableDivider()
semesterCard.getLinLayoutDay().addView(subjectLayout)
}
// without context we can't access the view
withContext(Dispatchers.Main) {
linLayout_Grades.addView(semesterCard)
}
}
val txtViewLegal = TextView(context).apply {
text = resources.getString(R.string.without_guarantee)
textAlignment = View.TEXT_ALIGNMENT_CENTER
}
// stop refreshing and show legal warning
withContext(Dispatchers.Main) {
linLayout_Grades.addView(txtViewLegal)
refreshLayout_Grades.isRefreshing = false
}
}
}

View File

@ -30,11 +30,12 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_home.* import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.mensaMenu import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.mensaMenu
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
import org.mosad.seil0.projectlaogai.databinding.FragmentHomeBinding
import org.mosad.seil0.projectlaogai.util.Meal import org.mosad.seil0.projectlaogai.util.Meal
import org.mosad.seil0.projectlaogai.util.TimetableDay import org.mosad.seil0.projectlaogai.util.TimetableDay
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
@ -51,9 +52,11 @@ class HomeFragment : Fragment() {
private val className = "HomeFragment" 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 {
return inflater.inflate(R.layout.fragment_home, container, false) binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -66,22 +69,22 @@ class HomeFragment : Fragment() {
/** /**
* add the current mensa meal to the home screens * add the current mensa meal to the home screens
*/ */
private fun addMensaMenu() = GlobalScope.launch(Dispatchers.Default) { 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())
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
if (isAdded) { if (isAdded) {
if (cal.get(Calendar.HOUR_OF_DAY) < 15) { if (cal.get(Calendar.HOUR_OF_DAY) < 15) {
dayMeals = mensaMenu.currentWeek.days[NotRetardedCalendar.getDayOfWeekIndex()].meals dayMeals = mensaMenu.currentWeek.days[NotRetardedCalendar.getDayOfWeekIndex()].meals
mensaCardView.setDayHeading(activity!!.resources.getString(R.string.today_date, formatter.format(cal.time))) mensaCardView.setDayHeading(getString(R.string.today_date, formatter.format(cal.time)))
} else { } else {
dayMeals = mensaMenu.currentWeek.days[NotRetardedCalendar.getTomorrowWeekIndex()].meals dayMeals = mensaMenu.currentWeek.days[NotRetardedCalendar.getTomorrowWeekIndex()].meals
cal.add(Calendar.DATE, 1) cal.add(Calendar.DATE, 1)
mensaCardView.setDayHeading(activity!!.resources.getString(R.string.tomorrow_date, formatter.format(cal.time))) mensaCardView.setDayHeading(getString(R.string.tomorrow_date, formatter.format(cal.time)))
} }
if (dayMeals.size >= 2) { if (dayMeals.size >= 2) {
@ -106,7 +109,7 @@ class HomeFragment : Fragment() {
mensaCardView.getLinLayoutDay().addView(getNoCard(resources.getString(R.string.mensa_closed))) mensaCardView.getLinLayoutDay().addView(getNoCard(resources.getString(R.string.mensa_closed)))
} }
linLayout_Home.addView(mensaCardView) binding.linLayoutHome.addView(mensaCardView)
} }
} }
@ -116,20 +119,15 @@ class HomeFragment : Fragment() {
/** /**
* add the current timetable to the home screen * add the current timetable to the home screen
*/ */
private fun addTimeTable() = GlobalScope.launch(Dispatchers.Default) { private fun addTimeTable() = lifecycleScope.launch(Dispatchers.Main) {
if (isAdded && TimetableController.timetable.isNotEmpty()) {
withContext(Dispatchers.Main) { try {
val dayCardView = findNextDay(NotRetardedCalendar.getDayOfWeekIndex())
if (isAdded && TimetableController.timetable.isNotEmpty()) { binding.linLayoutHome.addView(dayCardView)
try { } catch (ex: Exception) {
val dayCardView = findNextDay(NotRetardedCalendar.getDayOfWeekIndex()) Log.e(className, "could not load timetable", ex) // TODO send feedback
linLayout_Home.addView(dayCardView)
} catch (ex: Exception) {
Log.e(className, "could not load timetable", ex) // TODO send feedback
}
} }
} }
} }
/** /**
@ -140,7 +138,7 @@ class HomeFragment : Fragment() {
* @return a DayCardView with all lessons added * @return a DayCardView with all lessons added
*/ */
private fun findNextDay(startDayIndex: Int): DayCardView { private fun findNextDay(startDayIndex: Int): DayCardView {
val dayCardView = DayCardView(context!!) val dayCardView = DayCardView(requireContext())
var dayTimetable: TimetableDay? = null var dayTimetable: TimetableDay? = null
var dayIndexSearch = startDayIndex var dayIndexSearch = startDayIndex
var weekIndexSearch = 0 var weekIndexSearch = 0

View File

@ -27,16 +27,15 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_mensa.* import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.cache.CacheController import org.mosad.seil0.projectlaogai.controller.cache.CacheController
import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.mensaMenu import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.mensaMenu
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cShowBuffet import org.mosad.seil0.projectlaogai.databinding.FragmentMensaBinding
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.uicomponents.TextViewInfo
@ -49,31 +48,34 @@ import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
*/ */
class MensaFragment : Fragment() { class MensaFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { private lateinit var binding: FragmentMensaBinding
return inflater.inflate(R.layout.fragment_mensa, container, false)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentMensaBinding.inflate(inflater, container, false)
return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
refreshLayout_Mensa.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary) binding.refreshLayoutMensa.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
refreshAction() // init actions initActions() // init actions
GlobalScope.launch(Dispatchers.Default) { lifecycleScope.launch(Dispatchers.Default) {
val dayCurrent = if(NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex() val dayCurrent = if(NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
// add the current and next week
addWeek(mensaMenu.currentWeek, dayCurrent).join() addWeek(mensaMenu.currentWeek, dayCurrent).join()
addWeek(mensaMenu.nextWeek, 0).join()
// add the next week // show a info if there are no more menus
addWeek(mensaMenu.nextWeek, 0) if (binding.linLayoutMensa.childCount == 0) {
} val txtViewInfo = TextViewInfo(requireContext()).set {
txt = resources.getString(R.string.no_more_meals)
// show a info if there are no more menus }
if (linLayout_Mensa.childCount == 0) { withContext(Dispatchers.Main) { binding.linLayoutMensa.addView(txtViewInfo) }
val txtViewInfo = TextViewInfo(context!!).set {
txt = resources.getString(R.string.no_more_meals)
} }
linLayout_Mensa.addView(txtViewInfo)
} }
} }
@ -82,13 +84,13 @@ class MensaFragment : Fragment() {
* @param menusWeek menu of type MensaWeek you want to add * @param menusWeek menu of type MensaWeek you want to add
* @param dayStart the first day of the week to add * @param dayStart the first day of the week to add
*/ */
private fun addWeek(menusWeek: MensaWeek, dayStart: Int) = GlobalScope.launch(Dispatchers.Default) { private fun addWeek(menusWeek: MensaWeek, dayStart: Int) = lifecycleScope.launch(Dispatchers.Default) {
withContext(Dispatchers.Main) { 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())
if(menusWeek.days[dayIndex].meals.isNotEmpty()) if(menusWeek.days[dayIndex].meals.isNotEmpty())
dayCardView.setDayHeading(menusWeek.days[dayIndex].meals[0].day) dayCardView.setDayHeading(menusWeek.days[dayIndex].meals[0].day)
@ -97,7 +99,7 @@ class MensaFragment : Fragment() {
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
} }
@ -106,50 +108,47 @@ class MensaFragment : Fragment() {
helpMeal.disableDivider() helpMeal.disableDivider()
if(dayCardView.getLinLayoutDay().childCount > 2) if(dayCardView.getLinLayoutDay().childCount > 2)
linLayout_Mensa.addView(dayCardView) binding.linLayoutMensa.addView(dayCardView)
} }
} }
} }
/** /**
* initialize the refresh action * initialize the actions
*/ */
private fun refreshAction() = GlobalScope.launch(Dispatchers.Default) { private fun initActions() {
withContext(Dispatchers.Main) { // set the refresh listener
// set the refresh listener binding.refreshLayoutMensa.setOnRefreshListener {
refreshLayout_Mensa.setOnRefreshListener { updateMensaScreen()
updateMensaScreen()
}
} }
} }
/** /**
* refresh the mensa cache and update the mensa screen * refresh the mensa cache and update the mensa screen
*/ */
private fun updateMensaScreen() = GlobalScope.launch(Dispatchers.Default) { private fun updateMensaScreen() = lifecycleScope.launch(Dispatchers.Default) {
CacheController.updateMensaMenu(context!!).join() // blocking since we want the new data CacheController.updateMensaMenu(requireContext()).join() // blocking since we want the new data
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
// remove all menus from the layout // remove all menus from the layout
linLayout_Mensa.removeAllViews() binding.linLayoutMensa.removeAllViews()
// add the refreshed menus // add the refreshed menus
val dayCurrent = if (NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex() val dayCurrent = if (NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
// add the current and next week
addWeek(mensaMenu.currentWeek, dayCurrent).join() addWeek(mensaMenu.currentWeek, dayCurrent).join()
addWeek(mensaMenu.nextWeek, 0).join()
// add the next week binding.refreshLayoutMensa.isRefreshing = false
addWeek(mensaMenu.nextWeek, 0)
refreshLayout_Mensa.isRefreshing = false
// show a info if there are no more menus // show a info if there are no more menus
if (linLayout_Mensa.childCount == 0) { if (binding.linLayoutMensa.childCount == 0) {
val txtViewInfo = TextViewInfo(context!!).set { val txtViewInfo = TextViewInfo(requireContext()).set {
txt = resources.getString(R.string.no_more_meals) txt = resources.getString(R.string.no_more_meals)
} }
linLayout_Mensa.addView(txtViewInfo) binding.linLayoutMensa.addView(txtViewInfo)
} }
} }
} }

View File

@ -22,43 +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.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.TextView
import androidx.appcompat.widget.SwitchCompat
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.WhichButton import com.afollestad.materialdialogs.WhichButton
import com.afollestad.materialdialogs.actions.getActionButton import com.afollestad.materialdialogs.actions.getActionButton
import com.afollestad.materialdialogs.callbacks.onDismiss
import com.afollestad.materialdialogs.color.colorChooser
import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.list.listItems
import com.afollestad.materialdialogs.list.listItemsMultiChoice import com.afollestad.materialdialogs.list.listItemsMultiChoice
import com.afollestad.materialdialogs.list.listItemsSingleChoice
import de.psdev.licensesdialog.LicensesDialog import de.psdev.licensesdialog.LicensesDialog
import kotlinx.android.synthetic.main.fragment_settings.* import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.* 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.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.cache.CacheController import org.mosad.seil0.projectlaogai.controller.cache.CacheController
import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.coursesList import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.coursesList
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorAccent import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorPrimary import org.mosad.seil0.projectlaogai.databinding.FragmentSettingsBinding
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse import org.mosad.seil0.projectlaogai.uicomponents.dialogs.CourseSelectionDialog
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cShowBuffet import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoadingDialog
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
import org.mosad.seil0.projectlaogai.util.DataTypes import org.mosad.seil0.projectlaogai.util.DataTypes
import java.util.*
/** /**
* The settings controller class * The settings controller class
@ -66,21 +58,13 @@ 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 linLayoutManageLessons: LinearLayout
private lateinit var linLayoutAbout: LinearLayout
private lateinit var linLayoutLicence: LinearLayout
private lateinit var linLayoutTheme: LinearLayout
private lateinit var linLayoutPrimaryColor: LinearLayout
private lateinit var linLayoutAccentColor: LinearLayout
private lateinit var switchBuffet: SwitchCompat
private lateinit var txtViewCourse: TextView
private var selectedTheme = DataTypes.Theme.Light private var selectedTheme = DataTypes.Theme.Light
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_settings, container, false) binding = FragmentSettingsBinding.inflate(inflater, container, false)
return binding.root
} }
/** /**
@ -89,56 +73,42 @@ class SettingsFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
linLayoutUser = view.findViewById(R.id.linLayout_User)
linLayoutCourse = view.findViewById(R.id.linLayout_Course)
linLayoutManageLessons = view.findViewById(R.id.linLayout_ManageLessons)
linLayoutAbout = view.findViewById(R.id.linLayout_About)
linLayoutLicence = view.findViewById(R.id.linLayout_Licence)
linLayoutTheme = view.findViewById(R.id.linLayout_Theme)
linLayoutPrimaryColor = view.findViewById(R.id.linLayout_PrimaryColor)
linLayoutAccentColor = view.findViewById(R.id.linLayout_AccentColor)
switchBuffet = view.findViewById(R.id.switch_buffet)
// if we call txtView_Course via KAE view binding it'll result in a NPE in the onDismissed call
txtViewCourse = view.findViewById(R.id.txtView_Course)
initActions() initActions()
binding.textUser.text = EncryptedPreferences.email.ifEmpty { resources.getString(R.string.sample_user) }
txtView_User.text = EncryptedPreferences.email.ifEmpty { resources.getString(R.string.sample_user) } binding.textCourse.text = Preferences.course.courseName
txtView_Course.text = cCourse.courseName binding.textAboutDesc.text = resources.getString(R.string.about_version, BuildConfig.VERSION_NAME, getString(R.string.build_time))
txtView_AboutDesc.text = resources.getString(R.string.about_version, BuildConfig.VERSION_NAME, getString(R.string.build_time)) binding.switchBuffet.isChecked = Preferences.showBuffet // init switch
switch_buffet.isChecked = cShowBuffet // init switch
val outValue = TypedValue() val outValue = TypedValue()
activity!!.theme.resolveAttribute(R.attr.themeName, outValue, true) requireActivity().theme.resolveAttribute(R.attr.themeName, outValue, true)
when(outValue.string) { when(outValue.string) {
"light" -> { "light" -> {
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryLight, activity!!.theme)) binding.switchBuffet.setTextColor(requireActivity().resources.getColor(R.color.textPrimaryLight, requireActivity().theme))
selectedTheme = DataTypes.Theme.Light selectedTheme = DataTypes.Theme.Light
selectedTheme.string = resources.getString(R.string.themeLight) selectedTheme.string = resources.getString(R.string.themeLight)
} }
"dark" -> { "dark" -> {
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryDark, activity!!.theme)) binding.switchBuffet.setTextColor(requireActivity().resources.getColor(R.color.textPrimaryDark, requireActivity().theme))
selectedTheme = DataTypes.Theme.Dark selectedTheme = DataTypes.Theme.Dark
selectedTheme.string = resources.getString(R.string.themeDark) selectedTheme.string = resources.getString(R.string.themeDark)
} }
"black" -> { "black" -> {
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryDark, activity!!.theme)) binding.switchBuffet.setTextColor(requireActivity().resources.getColor(R.color.textPrimaryDark, requireActivity().theme))
selectedTheme = DataTypes.Theme.Black selectedTheme = DataTypes.Theme.Black
selectedTheme.string = resources.getString(R.string.themeBlack) selectedTheme.string = resources.getString(R.string.themeBlack)
} }
} }
txtView_SelectedTheme.text = selectedTheme.string 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(context!!) LoginDialog(requireContext())
.positiveButton { .positiveButton {
EncryptedPreferences.saveCredentials(email, password, context) EncryptedPreferences.saveCredentials(email, password, context)
} }
@ -148,20 +118,40 @@ class SettingsFragment : Fragment() {
} }
} }
linLayoutUser.setOnLongClickListener { binding.linLayoutUser.setOnLongClickListener {
Preferences.oGiants = true // enable easter egg Preferences.oGiants = true // enable easter egg
return@setOnLongClickListener true return@setOnLongClickListener true
} }
linLayoutCourse.setOnClickListener { binding.linLayoutCourse.setOnClickListener {
selectCourse(context!!).show { 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 { onDismiss {
txtViewCourse.text = cCourse.courseName // update txtView after the dialog is dismissed binding.textCourse.text = Preferences.course.courseName // update txtView after the dialog is dismissed
} }
} }
} }
linLayoutManageLessons.setOnClickListener { binding.linLayoutManageLessons.setOnClickListener {
val lessons = ArrayList<String>() val lessons = ArrayList<String>()
TimetableController.subjectMap.forEach { pair -> TimetableController.subjectMap.forEach { pair ->
pair.value.forEach { pair.value.forEach {
@ -169,12 +159,12 @@ class SettingsFragment : Fragment() {
} }
} }
MaterialDialog(context!!).show { MaterialDialog(requireContext()).show {
title(R.string.manage_lessons) title(R.string.manage_lessons)
positiveButton(R.string.delete) positiveButton(R.string.delete)
negativeButton(R.string.cancel) negativeButton(R.string.cancel)
getActionButton(WhichButton.POSITIVE).updateTextColor(cColorAccent) getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.colorAccent)
getActionButton(WhichButton.NEGATIVE).updateTextColor(cColorAccent) getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.colorAccent)
listItemsMultiChoice(items = lessons) { _, _, items -> listItemsMultiChoice(items = lessons) { _, _, items ->
items.forEach { items.forEach {
@ -185,18 +175,18 @@ class SettingsFragment : Fragment() {
} }
} }
linLayoutAbout.setOnClickListener { 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()
} }
linLayoutLicence.setOnClickListener { binding.linLayoutLicence.setOnClickListener {
// do the theme magic, as the lib's theme support is broken // do the theme magic, as the lib's theme support is broken
val outValue = TypedValue() val outValue = TypedValue()
context!!.theme.resolveAttribute(R.attr.themeName, outValue, true) requireContext().theme.resolveAttribute(R.attr.themeName, outValue, true)
val dialogCss = when (outValue.string) { val dialogCss = when (outValue.string) {
"light" -> R.string.license_dialog_style_light "light" -> R.string.license_dialog_style_light
@ -209,7 +199,7 @@ class SettingsFragment : Fragment() {
} }
// open a new license dialog // open a new license dialog
LicensesDialog.Builder(context!!) LicensesDialog.Builder(requireContext())
.setNotices(R.raw.notices) .setNotices(R.raw.notices)
.setTitle(R.string.licenses) .setTitle(R.string.licenses)
.setIncludeOwnLicense(true) .setIncludeOwnLicense(true)
@ -219,106 +209,72 @@ class SettingsFragment : Fragment() {
.show() .show()
} }
linLayoutTheme.setOnClickListener { // binding.linLayoutTheme.setOnClickListener {
val themes = listOf( // val themes = listOf(
resources.getString(R.string.themeLight), // resources.getString(R.string.themeLight),
resources.getString(R.string.themeDark), // resources.getString(R.string.themeDark),
resources.getString(R.string.themeBlack) // 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()
// }
// }
// }
// }
MaterialDialog(context!!).show { // binding.linLayoutPrimaryColor.setOnClickListener {
title(R.string.theme) // // open a new color chooser dialog
listItemsSingleChoice(items = themes, initialSelection = selectedTheme.ordinal) { _, index, _ -> // MaterialDialog(requireContext())
Aesthetic.config { // .colorChooser(DataTypes().primaryColors, allowCustomArgb = true, initialSelection = Preferences.colorPrimary) { _, color ->
when (index) { // binding.viewPrimaryColor.setBackgroundColor(color)
0 -> activityTheme(R.style.AppTheme_Light) // Aesthetic.config {
1 -> activityTheme(R.style.AppTheme_Dark) // colorPrimary(color)
2 -> activityTheme(R.style.AppTheme_Black) // colorPrimaryDark(color)
else -> activityTheme(R.style.AppTheme_Light) // apply()
} // }
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)
} }
linLayoutPrimaryColor.setOnClickListener {
// open a new color chooser dialog
MaterialDialog(context!!)
.colorChooser(DataTypes().primaryColors, allowCustomArgb = true, initialSelection = cColorPrimary) { _, color ->
view_PrimaryColor.setBackgroundColor(color)
Aesthetic.config {
colorPrimary(color)
colorPrimaryDark(color)
apply()
}
Preferences.saveColorPrimary(context!!, color)
}
.show {
title(R.string.primary_color)
positiveButton(R.string.select)
getActionButton(WhichButton.POSITIVE).updateTextColor(cColorAccent)
}
}
linLayoutAccentColor.setOnClickListener {
// open a new color chooser dialog
MaterialDialog(context!!)
.colorChooser(DataTypes().accentColors, allowCustomArgb = true, initialSelection = cColorAccent) { _, color ->
view_AccentColor.setBackgroundColor(color)
Aesthetic.config {
colorAccent(color)
apply()
}
Preferences.saveColorAccent(context!!, color)
}
.show{
title(R.string.accent_color)
positiveButton(R.string.select)
getActionButton(WhichButton.POSITIVE).updateTextColor(cColorAccent)
}
}
switchBuffet.setOnClickListener {
Preferences.saveShowBuffet(context!!, switchBuffet.isChecked)
}
}
fun selectCourse(context: Context) : MaterialDialog {
val courseNameList = ArrayList<String>()
coursesList.forEach { (_, courseName) ->
courseNameList.add(courseName)
}
// return a new course selection dialog
return MaterialDialog(context)
.title(R.string.select_course)
.listItems(items = courseNameList) { _, index, _ ->
val loadingDialog = MaterialDialog(context).cancelable(false)
.cancelOnTouchOutside(false)
.customView(R.layout.dialog_loading)
loadingDialog.show()
GlobalScope.launch(Dispatchers.Default) {
Preferences.saveCourse(context, coursesList[index]) // save the course
// update current & next weeks timetable
val threads = listOf(
CacheController.updateTimetable(cCourse.courseName, 0, context),
CacheController.updateTimetable(cCourse.courseName, 1, context)
)
threads.joinAll() // blocking since we want the new data
withContext(Dispatchers.Main) {
loadingDialog.dismiss()
}
}
}
} }
} }

View File

@ -26,15 +26,14 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ScrollView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.google.android.material.floatingactionbutton.FloatingActionButton import androidx.lifecycle.lifecycleScope
import kotlinx.android.synthetic.main.fragment_timetable.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController.Companion.timetable import org.mosad.seil0.projectlaogai.controller.cache.TimetableController.timetable
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences 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.dialogs.AddSubjectDialog
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.TextViewInfo import org.mosad.seil0.projectlaogai.uicomponents.TextViewInfo
@ -44,47 +43,45 @@ import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
* The timetable controller class * The timetable controller class
* contains all needed parts to display and the timetable detail screen * contains all needed parts to display and the timetable detail screen
*/ */
class TimeTableFragment : Fragment() { class TimetableFragment : Fragment() {
private lateinit var scrollViewTimetable: ScrollView private lateinit var binding: FragmentTimetableBinding
private lateinit var faBtnAddSubject: FloatingActionButton
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_timetable, container, false) binding = FragmentTimetableBinding.inflate(inflater, container, false)
return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
scrollViewTimetable = view.findViewById(R.id.scrollView_Timetable) binding.refreshLayoutTimetable.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
faBtnAddSubject = view.findViewById(R.id.faBtnAddSubject)
refreshLayout_Timetable.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
initActions() // init actions initActions() // init actions
if (timetable.size > 1 && timetable[0].days.isNotEmpty() && timetable[1].days.isNotEmpty()) { if (timetable.size > 1 && timetable[0].days.isNotEmpty() && timetable[1].days.isNotEmpty()) {
initTimetable() initTimetable()
} else { } else {
val txtViewInfo = TextViewInfo(context!!).set { val txtViewInfo = TextViewInfo(requireContext()).set {
txt = resources.getString(R.string.timetable_generic_error) txt = resources.getString(R.string.timetable_generic_error)
}.showImage() }.showImage()
linLayout_Timetable.addView(txtViewInfo) binding.linLayoutTimetable.addView(txtViewInfo)
} }
} }
/** /**
* initialize the actions * initialize the actions
*/ */
private fun initActions() = GlobalScope.launch(Dispatchers.Main) { private fun initActions() {
refreshLayout_Timetable.setOnRefreshListener { binding.refreshLayoutTimetable.setOnRefreshListener {
runBlocking { TimetableController.update(context!!).joinAll() } runBlocking { TimetableController.update(requireContext()).joinAll() }
reloadTimetableUI() reloadTimetableUI()
} }
// show the AddLessonDialog if the ftaBtn is clicked // show the AddLessonDialog if the ftaBtn is clicked
faBtnAddSubject.setOnClickListener { binding.faBtnAddSubject.setOnClickListener {
AddSubjectDialog(context!!) AddSubjectDialog(requireContext())
.positiveButton { .positiveButton {
TimetableController.addSubject(selectedCourse, selectedSubject, context) TimetableController.addSubject(selectedCourse, selectedSubject, context)
runBlocking { reloadTimetableUI() } runBlocking { reloadTimetableUI() }
@ -92,11 +89,11 @@ class TimeTableFragment : Fragment() {
} }
// hide the btnCardValue if the user is scrolling down // hide the btnCardValue if the user is scrolling down
scrollViewTimetable.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> binding.scrollViewTimetable.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if (scrollY > oldScrollY) { if (scrollY > oldScrollY) {
faBtnAddSubject.hide() binding.faBtnAddSubject.hide()
} else { } else {
faBtnAddSubject.show() binding.faBtnAddSubject.show()
} }
} }
@ -105,16 +102,16 @@ class TimeTableFragment : Fragment() {
/** /**
* add the current and next weeks lessons * add the current and next weeks lessons
*/ */
private fun initTimetable() = GlobalScope.launch(Dispatchers.Default) { private fun initTimetable() = lifecycleScope.launch(Dispatchers.Default) {
val currentDayIndex = NotRetardedCalendar.getDayOfWeekIndex() val currentDayIndex = NotRetardedCalendar.getDayOfWeekIndex()
addTimetableWeek(currentDayIndex, 5, 0).join() // add current week addTimetableWeek(currentDayIndex, 5, 0).join() // add current week
addTimetableWeek(0, currentDayIndex - 1, 1) // add next week addTimetableWeek(0, currentDayIndex - 1, 1) // add next week
} }
private fun addTimetableWeek(dayBegin: Int, dayEnd: Int, week: Int) = GlobalScope.launch(Dispatchers.Main) { private fun addTimetableWeek(dayBegin: Int, dayEnd: Int, week: Int) = lifecycleScope.launch(Dispatchers.Main) {
for (dayIndex in dayBegin..dayEnd) { for (dayIndex in dayBegin..dayEnd) {
val dayCardView = DayCardView(context!!) val dayCardView = DayCardView(requireContext())
// some wired calendar magic, calculate the correct date to be shown // some wired calendar magic, calculate the correct date to be shown
// ((timetable week - current week * 7) + (dayIndex - dayIndex of current week) // ((timetable week - current week * 7) + (dayIndex - dayIndex of current week)
@ -124,7 +121,7 @@ class TimeTableFragment : Fragment() {
// if there are no lessons don't show the dayCardView // if there are no lessons don't show the dayCardView
if (dayCardView.getLinLayoutDay().childCount > 1) if (dayCardView.getLinLayoutDay().childCount > 1)
linLayout_Timetable.addView(dayCardView) binding.linLayoutTimetable.addView(dayCardView)
} }
} }
@ -132,10 +129,10 @@ class TimeTableFragment : Fragment() {
/** /**
* clear linLayout_Timetable, add the updated timetable * clear linLayout_Timetable, add the updated timetable
*/ */
private fun reloadTimetableUI() = GlobalScope.launch(Dispatchers.Default) { private fun reloadTimetableUI() = lifecycleScope.launch(Dispatchers.Default) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
// remove all lessons from the layout // remove all lessons from the layout
linLayout_Timetable.removeAllViews() binding.linLayoutTimetable.removeAllViews()
// add the refreshed timetables // add the refreshed timetables
val dayIndex = NotRetardedCalendar.getDayOfWeekIndex() val dayIndex = NotRetardedCalendar.getDayOfWeekIndex()
@ -143,7 +140,7 @@ class TimeTableFragment : Fragment() {
addTimetableWeek(dayIndex, 5, 0).join() // add current week addTimetableWeek(dayIndex, 5, 0).join() // add current week
addTimetableWeek(0, dayIndex - 1, 1) // add next week addTimetableWeek(0, dayIndex - 1, 1) // add next week
refreshLayout_Timetable.isRefreshing = false binding.refreshLayoutTimetable.isRefreshing = false
} }
} }

View File

@ -24,10 +24,10 @@ 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.DataTypes
import org.mosad.seil0.projectlaogai.util.TimetableDay import org.mosad.seil0.projectlaogai.util.TimetableDay
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -35,21 +35,19 @@ import java.util.*
class DayCardView(context: Context) : CardView(context) { class DayCardView(context: Context) : CardView(context) {
private var binding = CardviewDayBinding.inflate(LayoutInflater.from(context), this, true)
private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault()) private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault())
init { init {
inflate(context, R.layout.cardview_day,this) this.setBackgroundColor(Color.TRANSPARENT) // workaround to prevent a white border
// workaround to prevent a white border
this.setBackgroundColor(Color.TRANSPARENT)
} }
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
} }
/** /**
@ -62,7 +60,7 @@ class DayCardView(context: Context) : CardView(context) {
// set the heading // set the heading
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
cal.add(Calendar.DATE, daysToAdd) cal.add(Calendar.DATE, daysToAdd)
txtView_DayHeading.text = formatter.format(cal.time) binding.textDayHeading.text = formatter.format(cal.time)
// for every timeslot of that timetable // for every timeslot of that timetable
timetable.timeslots.forEachIndexed { tsIndex, timeslot -> timetable.timeslots.forEachIndexed { tsIndex, timeslot ->
@ -71,7 +69,7 @@ class DayCardView(context: Context) : CardView(context) {
val lessonLayout = LessonLinearLayout(context) val lessonLayout = LessonLinearLayout(context)
lessonLayout.setLesson(lesson, DataTypes().times[tsIndex]) lessonLayout.setLesson(lesson, DataTypes().times[tsIndex])
linLayout_Day.addView(lessonLayout) binding.linearDay.addView(lessonLayout)
if (lesson != timeslot.last()) { if (lesson != timeslot.last()) {
lessonLayout.disableDivider() lessonLayout.disableDivider()

View File

@ -1,58 +0,0 @@
/**
* ProjectLaogai
*
* Copyright 2019-2020 <seil0@mosad.xyz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
package org.mosad.seil0.projectlaogai.uicomponents
import android.content.Context
import android.view.View
import android.widget.LinearLayout
import kotlinx.android.synthetic.main.linearlayout_grade.view.*
import org.mosad.seil0.projectlaogai.R
class GradeLinearLayout(context: Context?): LinearLayout(context) {
var subjectName = ""
var grade = ""
var subSubjectName = ""
var subGrade = ""
init {
inflate(context, R.layout.linearlayout_grade, this)
}
fun set(func: GradeLinearLayout.() -> Unit): GradeLinearLayout = apply {
func()
txtView_subject.text = subjectName
txtView_grade.text = grade
txtView_sub_subject.text = subSubjectName
txtView_sub_grade.text = subGrade
}
fun disableDivider() {
divider_grade.visibility = View.GONE
}
fun disableSubSubject() {
linLayout_sub_subject.visibility = View.GONE
}
}

View File

@ -23,27 +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 kotlinx.android.synthetic.main.linearlayout_lesson.view.* import org.mosad.seil0.projectlaogai.databinding.LinearlayoutLessonBinding
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.util.Lesson import org.mosad.seil0.projectlaogai.util.Lesson
class LessonLinearLayout(context: Context?) : LinearLayout(context) { class LessonLinearLayout(context: Context?) : LinearLayout(context) {
init { private val binding = LinearlayoutLessonBinding.inflate(LayoutInflater.from(context), this, true)
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
} }
} }

View File

@ -23,30 +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 kotlinx.android.synthetic.main.linearlayout_meal.view.* import org.mosad.seil0.projectlaogai.databinding.LinearlayoutMealBinding
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.util.Meal import org.mosad.seil0.projectlaogai.util.Meal
class MealLinearLayout(context: Context?): LinearLayout(context) { class MealLinearLayout(context: Context?): LinearLayout(context) {
init { private val binding = LinearlayoutMealBinding.inflate(LayoutInflater.from(context), this, true)
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
} }
} }

View File

@ -41,7 +41,6 @@ class TextViewInfo(context: Context?): AppCompatTextView(context!!) {
init { init {
params.setMargins(0,200,0,0) params.setMargins(0,200,0,0)
} }
fun set(func: TextViewInfo.() -> Unit): TextViewInfo = apply { fun set(func: TextViewInfo.() -> Unit): TextViewInfo = apply {

View File

@ -71,8 +71,8 @@ class AddSubjectDialog(val context: Context) {
spinnerSubjects = dialog.getCustomView().findViewById(R.id.spinner_Lessons) spinnerSubjects = dialog.getCustomView().findViewById(R.id.spinner_Lessons)
// fix not working accent color // fix not working accent color
dialog.getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.cColorAccent) dialog.getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.colorAccent)
dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.cColorAccent) dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.colorAccent)
initSpinners() initSpinners()
} }
@ -92,6 +92,11 @@ class AddSubjectDialog(val context: Context) {
this.show() this.show()
} }
@Suppress("unused")
fun dismiss() {
dialog.dismiss()
}
private fun initSpinners() { private fun initSpinners() {
setArrayAdapter(spinnerCourses, courseNamesList) setArrayAdapter(spinnerCourses, courseNamesList)
val lessonsAdapter = setArrayAdapter(spinnerSubjects, subjectsList) val lessonsAdapter = setArrayAdapter(spinnerSubjects, subjectsList)

View File

@ -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()
}
}
}

View File

@ -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()
}
}

View File

@ -56,8 +56,8 @@ class LoginDialog(val context: Context) {
editTextPassword = dialog.getCustomView().findViewById(R.id.editText_password) editTextPassword = dialog.getCustomView().findViewById(R.id.editText_password)
// fix not working accent color // fix not working accent color
dialog.getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.cColorAccent) dialog.getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.colorAccent)
dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.cColorAccent) dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.colorAccent)
} }
fun positiveButton(func: LoginDialog.() -> Unit): LoginDialog = apply { fun positiveButton(func: LoginDialog.() -> Unit): LoginDialog = apply {
@ -75,13 +75,22 @@ class LoginDialog(val context: Context) {
} }
} }
fun show() {
dialog.show()
}
fun show(func: LoginDialog.() -> Unit): LoginDialog = apply { fun show(func: LoginDialog.() -> Unit): LoginDialog = apply {
func() func()
editTextEmail.setText(email) editTextEmail.setText(email)
editTextPassword.setText(password) editTextPassword.setText(password)
dialog.show() show()
}
@Suppress("unused")
fun dismiss() {
dialog.dismiss()
} }
} }

View File

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

View File

@ -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"
@ -22,7 +23,6 @@
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
android:background="?themeSecondary" android:background="?themeSecondary"
app:headerLayout="@layout/nav_header_main" app:headerLayout="@layout/nav_header_main"
app:itemTextColor="?colorAccent"
app:menu="@menu/activity_main_drawer"/> app:menu="@menu/activity_main_drawer"/>
</androidx.drawerlayout.widget.DrawerLayout> </androidx.drawerlayout.widget.DrawerLayout>

View File

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

View File

@ -1,70 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linLayout_grade"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="7dp"
android:paddingTop="2dp"
android:paddingRight="7dp"
android:paddingBottom="3dp">
<LinearLayout
android:id="@+id/linLayout_subject"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/txtView_subject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/sample_subject"
android:textSize="15sp" />
<TextView
android:id="@+id/txtView_grade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:text="@string/sample_grade"
android:textAlignment="textEnd"
android:textSize="15sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/linLayout_sub_subject"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:paddingStart="7dp"
android:paddingTop="3dp"
android:paddingEnd="0dp">
<TextView
android:id="@+id/txtView_sub_subject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/sample_sub_subject" />
<TextView
android:id="@+id/txtView_sub_grade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:text="@string/sample_grade_state"
android:textAlignment="textEnd" />
</LinearLayout>
<View
android:id="@+id/divider_grade"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="3dp"
android:background="?dividerColor" />
</LinearLayout>

View File

@ -15,30 +15,30 @@
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
android:id="@+id/txtView_lessonSubject" android:id="@+id/text_lesson_subject"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:textSize="15sp" /> android:textSize="15sp" />
<TextView <TextView
android:id="@+id/txtView_lessonTime" android:id="@+id/text_lesson_time"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:gravity="end" android:gravity="end"
android:text="@string/a_time" android:text="@string/a_time"
android:textColor="@color/textSecondaryLight" /> android:textColor="?textSecondary" />
</LinearLayout> </LinearLayout>
<TextView <TextView
android:id="@+id/txtView_lessonTeacher" android:id="@+id/text_lesson_teacher"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="15sp" /> android:textSize="15sp" />
<TextView <TextView
android:id="@+id/txtView_lessonRoom" android:id="@+id/text_lesson_room"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linLayout_Meal" android:id="@+id/lienar_meal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
@ -10,7 +10,7 @@
android:paddingBottom="2dp"> android:paddingBottom="2dp">
<TextView <TextView
android:id="@+id/txtView_MealHeading" android:id="@+id/text_meal_heading"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="2dp" android:layout_marginBottom="2dp"
@ -20,7 +20,7 @@
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/txtView_Meal" android:id="@+id/text_meal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAlignment="center" android:textAlignment="center"

View File

@ -21,7 +21,7 @@
android:shortcutLongLabel="@string/shortcut_timetable_long" android:shortcutLongLabel="@string/shortcut_timetable_long"
android:shortcutDisabledMessage="@string/shortcut_timetable_disabled"> android:shortcutDisabledMessage="@string/shortcut_timetable_disabled">
<intent <intent
android:action="org.mosad.seil0.projectlaogai.fragments.TimeTableFragment" android:action="org.mosad.seil0.projectlaogai.fragments.TimetableFragment"
android:targetPackage="org.mosad.seil0.projectlaogai" android:targetPackage="org.mosad.seil0.projectlaogai"
android:targetClass="org.mosad.seil0.projectlaogai.MainActivity" /> android:targetClass="org.mosad.seil0.projectlaogai.MainActivity" />
<categories android:name="android.shortcut.conversation" /> <categories android:name="android.shortcut.conversation" />
@ -40,4 +40,18 @@
android:targetClass="org.mosad.seil0.projectlaogai.MainActivity" /> android:targetClass="org.mosad.seil0.projectlaogai.MainActivity" />
<categories android:name="android.shortcut.conversation" /> <categories android:name="android.shortcut.conversation" />
</shortcut> </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> </shortcuts>

View File

@ -1,38 +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.GradesFragment">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/refreshLayout_Grades"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?themePrimary">
<ScrollView
android:id="@+id/scrollView_Grades"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/linLayout_Grades"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/txtView_Loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="100dp"
android:padding="7dp"
android:text="@string/loading_from_hs"
android:textAlignment="center"
android:textSize="15sp" />
</LinearLayout>
</ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</FrameLayout>

View File

@ -18,7 +18,8 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical"
android:paddingBottom="11dp">
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/cardView_Info" android:id="@+id/cardView_Info"
@ -35,7 +36,7 @@
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/txtView_Info" android:id="@+id/text_info"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="7dp" android:layout_margin="7dp"
@ -50,11 +51,12 @@
android:id="@+id/linLayout_User" android:id="@+id/linLayout_User"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:selectableItemBackground"
android:orientation="vertical" android:orientation="vertical"
android:padding="7dp"> android:padding="7dp">
<TextView <TextView
android:id="@+id/txtView_User" android:id="@+id/text_user"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/sample_user" android:text="@string/sample_user"
@ -62,7 +64,7 @@
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/txtView_UserDesc" android:id="@+id/text_user_desc"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/user_desc" /> android:text="@string/user_desc" />
@ -78,11 +80,12 @@
android:id="@+id/linLayout_Course" android:id="@+id/linLayout_Course"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="7dp" android:background="?android:selectableItemBackground"
android:orientation="vertical"> android:orientation="vertical"
android:padding="7dp">
<TextView <TextView
android:id="@+id/txtView_Course" android:id="@+id/text_course"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/sample_course" android:text="@string/sample_course"
@ -90,7 +93,7 @@
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/txtView_CourseDesc" android:id="@+id/text_course_desc"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/course_desc" /> android:text="@string/course_desc" />
@ -106,11 +109,12 @@
android:id="@+id/linLayout_ManageLessons" android:id="@+id/linLayout_ManageLessons"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="7dp" android:background="?android:selectableItemBackground"
android:orientation="vertical"> android:orientation="vertical"
android:padding="7dp">
<TextView <TextView
android:id="@+id/txtView_ManageLessons" android:id="@+id/text_manage_lessons"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/manage_lessons" android:text="@string/manage_lessons"
@ -134,11 +138,12 @@
android:id="@+id/linLayout_About" android:id="@+id/linLayout_About"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_margin="7dp" android:background="?android:selectableItemBackground"
android:orientation="vertical"> android:orientation="vertical"
android:padding="7dp">
<TextView <TextView
android:id="@+id/txtView_About" android:id="@+id/text_about"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/about_txtView" android:text="@string/about_txtView"
@ -146,7 +151,7 @@
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/txtView_AboutDesc" android:id="@+id/text_about_desc"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/about_version" /> android:text="@string/about_version" />
@ -162,8 +167,9 @@
android:id="@+id/linLayout_Licence" android:id="@+id/linLayout_Licence"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_margin="7dp" android:background="?android:selectableItemBackground"
android:orientation="vertical"> android:orientation="vertical"
android:padding="7dp">
<TextView <TextView
android:id="@+id/textView3" android:id="@+id/textView3"
@ -191,7 +197,7 @@
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/txtView_Settings" android:id="@+id/text_settings"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="7dp" android:layout_margin="7dp"
@ -206,11 +212,12 @@
android:id="@+id/linLayout_Theme" android:id="@+id/linLayout_Theme"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_margin="7dp" android:background="?android:selectableItemBackground"
android:orientation="vertical"> android:orientation="vertical"
android:padding="7dp">
<TextView <TextView
android:id="@+id/txtView_Theme" android:id="@+id/text_theme"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/theme" android:text="@string/theme"
@ -218,7 +225,7 @@
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/txtView_SelectedTheme" android:id="@+id/text_theme_selected"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/themeLight" /> android:text="@string/themeLight" />
@ -234,11 +241,12 @@
android:id="@+id/linLayout_PrimaryColor" android:id="@+id/linLayout_PrimaryColor"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="7dp" android:background="?android:selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:gravity="center_vertical|end" android:gravity="center_vertical|end"
android:orientation="horizontal"> android:orientation="horizontal"
android:padding="7dp">
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -246,7 +254,7 @@
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/txtView_PrimaryColor" android:id="@+id/text_primary_color"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/primary_color" android:text="@string/primary_color"
@ -254,7 +262,7 @@
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/txtView_PrimaryColorDesc" android:id="@+id/text_primary_color_desc"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/primary_color_desc" /> android:text="@string/primary_color_desc" />
@ -268,7 +276,7 @@
android:orientation="vertical"> android:orientation="vertical">
<View <View
android:id="@+id/view_PrimaryColor" android:id="@+id/view_primary_color"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:background="?colorPrimary" /> android:background="?colorPrimary" />
@ -285,11 +293,12 @@
android:id="@+id/linLayout_AccentColor" android:id="@+id/linLayout_AccentColor"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="7dp" android:background="?android:selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:gravity="center_vertical|end" android:gravity="center_vertical|end"
android:orientation="horizontal"> android:orientation="horizontal"
android:padding="7dp">
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -297,7 +306,7 @@
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/txtView_AccentColor" android:id="@+id/text_accent_color"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/accent_color" android:text="@string/accent_color"
@ -305,7 +314,7 @@
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/txtView_AccentColorDesc" android:id="@+id/text_accent_color_desc"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/accent_color_desc" /> android:text="@string/accent_color_desc" />
@ -319,7 +328,7 @@
android:orientation="vertical"> android:orientation="vertical">
<View <View
android:id="@+id/view_AccentColor" android:id="@+id/view_accent_color"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:background="?colorAccent" /> android:background="?colorAccent" />
@ -336,10 +345,11 @@
android:id="@+id/switch_buffet" android:id="@+id/switch_buffet"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="7dp" android:padding="7dp"
android:text="@string/show_buffet" android:text="@string/show_buffet"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
</LinearLayout> </LinearLayout>

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".fragments.TimeTableFragment" tools:context=".fragments.TimetableFragment"
android:background="?themePrimary"> android:background="?themePrimary">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout

View File

@ -20,10 +20,6 @@
android:id="@+id/nav_moodle" android:id="@+id/nav_moodle"
android:icon="@drawable/ic_school_black_24dp" android:icon="@drawable/ic_school_black_24dp"
android:title="@string/moodle"/> android:title="@string/moodle"/>
<item
android:id="@+id/nav_grades"
android:icon="@drawable/ic_grading_black_24dp"
android:title="@string/grades" />
<item <item
android:id="@+id/nav_settings" android:id="@+id/nav_settings"
android:icon="@drawable/ic_settings_black_24dp" android:icon="@drawable/ic_settings_black_24dp"

View File

@ -66,6 +66,9 @@
<string name="primary_color_desc">Zum Ändern tippen, Standard ist Blaugrün.</string> <string name="primary_color_desc">Zum Ändern tippen, Standard ist Blaugrün.</string>
<string name="accent_color">Akzentfarbe</string> <string name="accent_color">Akzentfarbe</string>
<string name="accent_color_desc">Zum Ändern tippen, Standard ist Hellblau.</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>
<!-- dialogs --> <!-- dialogs -->
@ -80,6 +83,12 @@
<string name="mensa_current">aktuell: %1$s\n</string> <string name="mensa_current">aktuell: %1$s\n</string>
<string name="mensa_last">letzte Abbuchung: %1$s</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 --> <!-- errors -->
<string name="error">Fehler</string> <string name="error">Fehler</string>
<string name="timetable_error">Der Stundenplan konnte nicht geladen werden.</string> <string name="timetable_error">Der Stundenplan konnte nicht geladen werden.</string>
@ -97,4 +106,16 @@
<string name="shortcut_moodle_long">Moodle</string> <string name="shortcut_moodle_long">Moodle</string>
<string name="shortcut_moodle_disabled">Moodle deaktiviert</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>

View File

@ -1,22 +1,28 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- base theme colors -->
<color name="colorPrimary">#009688</color> <color name="colorPrimary">#009688</color>
<color name="colorPrimaryDark">#009688</color> <color name="colorPrimaryDark">#009688</color>
<color name="colorAccent">#0096ff</color> <color name="colorAccent">#0096ff</color>
<color name="ic_laogai_icon_background">#ffffff</color> <color name="ic_laogai_icon_background">#ffffff</color>
<!--theme color section, not the working colors--> <!-- light theme colors -->
<color name="themePrimaryDark">#000000</color>
<color name="themeSecondaryDark">#303030</color>
<color name="textPrimaryDark">#ffffff</color>
<color name="textSecondaryDark">#c0c0c0</color>
<color name="dividerDark">#232323</color>
<color name="themePrimaryLight">#f5f5f5</color> <color name="themePrimaryLight">#f5f5f5</color>
<color name="themeSecondaryLight">#ffffff</color> <color name="themeSecondaryLight">#ffffff</color>
<color name="textPrimaryLight">#000000</color> <color name="textPrimaryLight">#de000000</color>
<color name="textSecondaryLight">#818181</color> <color name="textSecondaryLight">#99000000</color>
<color name="dividerLight">#e0e0e0</color> <color name="dividerLight">#22000000</color>
<!-- dark theme colors -->
<color name="themePrimaryDark">#121212</color>
<color name="themeSecondaryDark">#202020</color>
<color name="textPrimaryDark">#deffffff</color>
<color name="textSecondaryDark">#99ffffff</color>
<color name="dividerDark">#22ffffff</color>
<color name="controlHighlightDark">#11ffffff</color>
<!-- black theme colors -->
<color name="themePrimaryBlack">#000000</color>
<color name="themeSecondaryBlack">#303030</color>
</resources> </resources>

View File

@ -71,6 +71,9 @@
<string name="primary_color_desc">Tap to change, default is teal blue.</string> <string name="primary_color_desc">Tap to change, default is teal blue.</string>
<string name="accent_color">Accent color</string> <string name="accent_color">Accent color</string>
<string name="accent_color_desc">Tap to change, default is light blue.</string> <string name="accent_color_desc">Tap to change, default is light blue.</string>
<string name="grades_sync">Update interval grades</string>
<string name="grades_sync_desc">%1$d hours</string>
<string name="grades_sync_desc_never">never</string>
<string name="show_buffet">Always show buffet</string> <string name="show_buffet">Always show buffet</string>
<!-- dialogs --> <!-- dialogs -->
@ -85,6 +88,12 @@
<string name="mensa_current">current: %1$s\n</string> <string name="mensa_current">current: %1$s\n</string>
<string name="mensa_last">last: %1$s</string> <string name="mensa_last">last: %1$s</string>
<!-- notifications -->
<string name="notification_grades">Grades</string>
<string name="notification_grades_single_desc">%1$s was added or updated</string>
<string name="notification_grades_multiple_desc">%1$s and %2$d other subjects have been added or updated</string>
<string name="notification_grades_updating_desc">Checking for new grades …</string>
<!-- sample strings --> <!-- sample strings -->
<string name="sample_user" translatable="false">spinefield@stud.hs-offenburg.de</string> <string name="sample_user" translatable="false">spinefield@stud.hs-offenburg.de</string>
<string name="sample_course" translatable="false">Everything</string> <string name="sample_course" translatable="false">Everything</string>
@ -112,6 +121,10 @@
<string name="shortcut_moodle_long">Moodle</string> <string name="shortcut_moodle_long">Moodle</string>
<string name="shortcut_moodle_disabled">Moodle disabled</string> <string name="shortcut_moodle_disabled">Moodle disabled</string>
<string name="shortcut_grades_short">Grades</string>
<string name="shortcut_grades_long">Grades</string>
<string name="shortcut_grades_disabled">Grades disabled</string>
<!-- save keys --> <!-- save keys -->
<string name="preference_file_key" translatable="false">org.mosad.seil0.projectlaogai_preferences</string> <string name="preference_file_key" translatable="false">org.mosad.seil0.projectlaogai_preferences</string>
<string name="encrypted_preference_file_key" translatable="false">org.mosad.seil0.projectlaogai_encrypted_preferences</string> <string name="encrypted_preference_file_key" translatable="false">org.mosad.seil0.projectlaogai_encrypted_preferences</string>
@ -119,13 +132,28 @@
<string name="save_key_courseTTLink" translatable="false">org.mosad.seil0.projectlaogai.courseTTLink</string> <string name="save_key_courseTTLink" translatable="false">org.mosad.seil0.projectlaogai.courseTTLink</string>
<string name="save_key_colorPrimary" translatable="false">org.mosad.seil0.projectlaogai.colorPrimary</string> <string name="save_key_colorPrimary" translatable="false">org.mosad.seil0.projectlaogai.colorPrimary</string>
<string name="save_key_colorAccent" translatable="false">org.mosad.seil0.projectlaogai.colorAccent</string> <string name="save_key_colorAccent" translatable="false">org.mosad.seil0.projectlaogai.colorAccent</string>
<string name="save_key_gradesSyncInterval" translatable="false">org.mosad.seil0.projectlaogai.gradesSyncInterval</string>
<string name="save_key_showBuffet" translatable="false">org.mosad.seil0.projectlaogai.showBuffet</string> <string name="save_key_showBuffet" translatable="false">org.mosad.seil0.projectlaogai.showBuffet</string>
<string name="save_key_coursesCacheTime" translatable="false">org.mosad.seil0.projectlaogai.coursesCacheTime</string> <string name="save_key_coursesCacheTime" translatable="false">org.mosad.seil0.projectlaogai.coursesCacheTime</string>
<string name="save_key_mensaCacheTime" translatable="false">org.mosad.seil0.projectlaogai.mensaCacheTime</string> <string name="save_key_mensaCacheTime" translatable="false">org.mosad.seil0.projectlaogai.mensaCacheTime</string>
<string name="save_key_timetableCacheTime" translatable="false">org.mosad.seil0.projectlaogai.timetableCacheTime</string> <string name="save_key_timetableCacheTime" translatable="false">org.mosad.seil0.projectlaogai.timetableCacheTime</string>
<string name="save_key_gradesCacheTime" translatable="false">org.mosad.seil0.projectlaogai.gradesCacheTime</string>
<string name="save_key_user_email" translatable="false">org.mosad.seil0.projectlaogai.user_email</string> <string name="save_key_user_email" translatable="false">org.mosad.seil0.projectlaogai.user_email</string>
<string name="save_key_user_password" translatable="false">org.mosad.seil0.projectlaogai.user_password</string> <string name="save_key_user_password" translatable="false">org.mosad.seil0.projectlaogai.user_password</string>
<!-- intent actions -->
<string name="intent_action_mensaFragment" translatable="false">org.mosad.seil0.projectlaogai.fragments.MensaFragment</string>
<string name="intent_action_timetableFragment" translatable="false">org.mosad.seil0.projectlaogai.fragments.TimetableFragment</string>
<string name="intent_action_moodleFragment" translatable="false">org.mosad.seil0.projectlaogai.fragments.MoodleFragment</string>
<string name="intent_action_gradesFragment" translatable="false">org.mosad.seil0.projectlaogai.fragments.GradesFragment</string>
<string-array name="syncInterval">
<item>Manually</item>
<item>1 Hour</item>
<item>3 Hours</item>
<item>6 Hours</item>
<item>12 Hours</item>
</string-array>
<string-array name="courses"> <string-array name="courses">
<item>AI-1</item> <item>AI-1</item>

View File

@ -12,14 +12,13 @@
<item name="themeName">light</item> <item name="themeName">light</item>
<item name="themePrimary">@color/themePrimaryLight</item> <item name="themePrimary">@color/themePrimaryLight</item>
<item name="themeSecondary">@color/themeSecondaryLight</item> <item name="themeSecondary">@color/themeSecondaryLight</item>
<item name="textPrimary">@color/textPrimaryLight</item>
<item name="textSecondary">@color/textSecondaryLight</item>
<item name="android:textColor">@color/textPrimaryLight</item> <item name="android:textColor">@color/textPrimaryLight</item>
<item name="android:textColorPrimary">@color/textPrimaryLight</item> <item name="android:textColorPrimary">@color/textPrimaryLight</item>
<item name="android:textColorHint">@color/textSecondaryLight</item> <item name="android:textColorHint">@color/textSecondaryLight</item>
<item name="textPrimary">@color/textPrimaryLight</item>
<item name="textSecondary">@color/textSecondaryLight</item>
<item name="dividerColor">@color/dividerLight</item> <item name="dividerColor">@color/dividerLight</item>
<item name="md_background_color">@color/themeSecondaryLight</item> <item name="md_background_color">@color/themeSecondaryLight</item>
<item name="md_color_title">@color/textPrimaryLight</item>
<item name="md_color_content">@color/textPrimaryLight</item> <item name="md_color_content">@color/textPrimaryLight</item>
</style> </style>
@ -27,30 +26,23 @@
<item name="themeName">dark</item> <item name="themeName">dark</item>
<item name="themePrimary">@color/themePrimaryDark</item> <item name="themePrimary">@color/themePrimaryDark</item>
<item name="themeSecondary">@color/themeSecondaryDark</item> <item name="themeSecondary">@color/themeSecondaryDark</item>
<item name="textPrimary">@color/textPrimaryDark</item>
<item name="textSecondary">@color/textSecondaryDark</item>
<item name="android:textColor">@color/textPrimaryDark</item> <item name="android:textColor">@color/textPrimaryDark</item>
<item name="android:textColorPrimary">@color/textPrimaryDark</item> <item name="android:textColorPrimary">@color/textPrimaryDark</item>
<item name="android:textColorHint">@color/textSecondaryDark</item> <item name="android:textColorHint">@color/textSecondaryDark</item>
<item name="textPrimary">@color/textPrimaryDark</item>
<item name="textSecondary">@color/textSecondaryDark</item>
<item name="dividerColor">@color/dividerDark</item> <item name="dividerColor">@color/dividerDark</item>
<item name="md_background_color">@color/themeSecondaryDark</item> <item name="md_background_color">@color/themeSecondaryDark</item>
<item name="md_color_title">@color/textPrimaryDark</item>
<item name="md_color_content">@color/textPrimaryDark</item> <item name="md_color_content">@color/textPrimaryDark</item>
<!-- change on click indicator color for manually set components -->
<item name="colorControlHighlight">@color/controlHighlightDark</item>
</style> </style>
<style name="AppTheme.Black" parent="AppTheme"> <style name="AppTheme.Black" parent="AppTheme.Dark">
<item name="themeName">black</item> <item name="themeName">black</item>
<item name="themePrimary">@color/themePrimaryDark</item> <item name="themePrimary">@color/themePrimaryBlack</item>
<item name="themeSecondary">@color/themePrimaryDark</item> <item name="themeSecondary">@color/themePrimaryBlack</item>
<item name="android:textColor">@color/textPrimaryDark</item>
<item name="android:textColorPrimary">@color/textPrimaryDark</item>
<item name="android:textColorHint">@color/textSecondaryDark</item>
<item name="textPrimary">@color/textPrimaryDark</item>
<item name="textSecondary">@color/textSecondaryDark</item>
<item name="dividerColor">@color/dividerDark</item>
<item name="md_background_color">@color/themeSecondaryDark</item>
<item name="md_color_title">@color/textPrimaryDark</item>
<item name="md_color_content">@color/textPrimaryDark</item>
</style> </style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/> <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>

View File

@ -1,17 +0,0 @@
package org.mosad.seil0.projectlaogai
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View File

@ -0,0 +1,18 @@
{
"WiSe 17/18":[
{"credits":"4,0","grade":"1,0","id":"AI-1020","name":"Grundlagen der Elektronik","semester":"WiSe 17/18"},
{"credits":"2,0","grade":"2,3","id":"AI-1040","name":"Prozedurale Programmierung","semester":"WiSe 17/18"}
],
"SoSe 18":[
{"credits":"4,0","grade":"1,7","id":"AI-2010","name":"Technische Informatik","semester":"SoSe 18"},
{"credits":"1,0","grade":"bestanden","id":"AI-2015","name":"Praktikum Technische Informatik","semester":"SoSe 18"}
],
"WiSe 18/19":[
{"credits":"2,0","grade":"2,7","id":"AI-3010","name":"Computernetze","semester":"WiSe 18/19"},
{"credits":"3,0","grade":"bestanden","id":"AI-3015","name":"Praktikum Computernetze","semester":"WiSe 18/19"}
],
"SoSe 19":[
{"credits":"4,0","grade":"1,7","id":"AI-2010","name":"Mathemaik 7","semester":"SoSe 19"},
{"credits":"1,0","grade":"bestanden","id":"AI-2015","name":"Praktikum Mathemaik 7","semester":"SoSe 19"}
]
}

View File

@ -0,0 +1,16 @@
{
"WiSe 17/18":[
{"credits":"4,0","grade":"1,0","id":"AI-1020","name":"Grundlagen der Elektronik","semester":"WiSe 17/18"},
{"credits":"2,0","grade":"2,3","id":"AI-1040","name":"Prozedurale Programmierung","semester":"WiSe 17/18"}
],
"SoSe 18":[
{"credits":"4,0","grade":"1,7","id":"AI-2010","name":"Technische Informatik","semester":"SoSe 18"},
{"credits":"1,0","grade":"bestanden","id":"AI-2015","name":"Praktikum Technische Informatik","semester":"SoSe 18"}
],
"WiSe 18/19":[
{"credits":"2,0","grade":"0,7","id":"AI-3010","name":"Computernetze","semester":"WiSe 18/19"},
{"credits":"3,0","grade":"bestanden","id":"AI-3015","name":"Praktikum Computernetze","semester":"WiSe 18/19"},
{"credits":"2,0","grade":"1,7","id":"AI-3020","name":"Datenbanksysteme 1","semester":"WiSe 18/19"},
{"credits":"3,0","grade":"bestanden","id":"AI-3025","name":"Praktikum Datenbanksysteme","semester":"WiSe 18/19"}
]
}

View File

@ -0,0 +1,14 @@
{
"WiSe 17/18":[
{"credits":"4,0","grade":"1,0","id":"AI-1020","name":"Grundlagen der Elektronik","semester":"WiSe 17/18"},
{"credits":"2,0","grade":"2,3","id":"AI-1040","name":"Prozedurale Programmierung","semester":"WiSe 17/18"}
],
"SoSe 18":[
{"credits":"4,0","grade":"1,7","id":"AI-2010","name":"Technische Informatik","semester":"SoSe 18"},
{"credits":"1,0","grade":"bestanden","id":"AI-2015","name":"Praktikum Technische Informatik","semester":"SoSe 18"}
],
"WiSe 18/19":[
{"credits":"2,0","grade":"2,7","id":"AI-3010","name":"Computernetze","semester":"WiSe 18/19"},
{"credits":"3,0","grade":"bestanden","id":"AI-3015","name":"Praktikum Computernetze","semester":"WiSe 18/19"}
]
}

View File

@ -1,13 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.4.0' ext.kotlin_version = '1.9.22'
repositories { repositories {
google() google()
jcenter() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.1' classpath 'com.android.tools.build:gradle:8.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
@ -18,10 +18,10 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
google() google()
jcenter() mavenCentral()
} }
} }
task clean(type: Delete) { tasks.register('clean', Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

View File

@ -1,9 +1,9 @@
Here comes version 0.6.0 "anthropomorphised Athena" Here comes version 0.6.0 "anthropomorphised Athena"
* new: the new Onboarding Process allows you to select your course and login to the qispos system * new Onboarding Process
* new: it's now possible to access the grades from qispos * it's now possible to access the grades from qispos
* new: you can now add additinal lessons wich are not part of your cours timetable * you can now add additional lessons which are not part of your course timetable
* change: new primary and accent colors to match the mosad.xyz design * new primary and accent colors to match the mosad.xyz design
* change: updated some libraries * updated some libraries
* fix: compatibility with tcor version 1.2.2 and higher * better compatibility with tcor version 1.2.2 and higher
* fix: crash on first start, if tcor is not reachable * fixed a crash on first start, if tcor is not reachable

View File

@ -0,0 +1,5 @@
Version 0.6.1 "anthropomorphised Athena"
* Grades can be synced in the background
* Grades will be cached for offline use or if qispos is not reachable
* The message "Menu empty" is no longer displayed if there is a menu in the next week

View File

@ -15,3 +15,6 @@ org.gradle.jvmargs=-Xmx1536m
kotlin.code.style=official kotlin.code.style=official
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

Binary file not shown.

View File

@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

293
gradlew vendored
View File

@ -1,7 +1,7 @@
#!/usr/bin/env sh #!/bin/sh
# #
# Copyright 2015 the original author or authors. # Copyright © 2015-2021 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -17,67 +17,98 @@
# #
############################################################################## ##############################################################################
## #
## Gradle start up script for UN*X # Gradle start up script for POSIX generated by Gradle.
## #
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" app_path=$0
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do # Need this for daisy-chained symlinks.
ls=`ls -ld "$PRG"` while
link=`expr "$ls" : '.*-> \(.*\)$'` APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
if expr "$link" : '/.*' > /dev/null; then [ -h "$app_path" ]
PRG="$link" do
else ls=$( ls -ld "$app_path" )
PRG=`dirname "$PRG"`"/$link" link=${ls#*' -> '}
fi case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" # This is normally unused
APP_BASE_NAME=`basename "$0"` # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD=maximum
warn () { warn () {
echo "$*" echo "$*"
} } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$( uname )" in #(
CYGWIN* ) CYGWIN* ) cygwin=true ;; #(
cygwin=true Darwin* ) darwin=true ;; #(
;; MSYS* | MINGW* ) msys=true ;; #(
Darwin* ) NONSTOP* ) nonstop=true ;;
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@ -87,9 +118,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -98,88 +129,120 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD="java" JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
MAX_FD="$MAX_FD_LIMIT" # shellcheck disable=SC3045
fi MAX_FD=$( ulimit -H -n ) ||
ulimit -n $MAX_FD warn "Could not query maximum file descriptor limit"
if [ $? -ne 0 ] ; then esac
warn "Could not set maximum file descriptor limit: $MAX_FD" case $MAX_FD in #(
fi '' | soft) :;; #(
else *)
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
fi # shellcheck disable=SC3045
fi ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
# Escape application args # Collect all arguments for the java command, stacking in reverse order:
save () { # * args from the command line
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done # * the main class name
echo " " # * -classpath
} # * -D...appname settings
APP_ARGS=`save "$@"` # * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules # For Cygwin or MSYS, switch paths to Windows format before running java
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"

34
gradlew.bat vendored
View File

@ -14,7 +14,7 @@
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init if %ERRORLEVEL% equ 0 goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -54,7 +55,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init if exist "%JAVA_EXE%" goto execute
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -64,21 +65,6 @@ echo location of your Java installation.
goto fail goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute :execute
@rem Setup the command line @rem Setup the command line
@ -86,17 +72,19 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 set EXIT_CODE=%ERRORLEVEL%
exit /b 1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal