Compare commits

..

1 Commits

Author SHA1 Message Date
Jannik 28520bee74
changelog: minor text fixes
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2020-09-04 11:08:50 +02:00
53 changed files with 1418 additions and 1120 deletions

View File

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

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)
# Project Laogai
Project Laogai is an app to access the timetable, grades (qispos) and the canteen menu of Hochschule Offenburg. Laogai uses the TCoR-API for timetables and the canteen menu, wich makes acessing them super fast. To get the grades from Qispos, Laogai will ask for your login data, wich are stored encrypted on your device.
Project Laogai is an app to access the timetable, grades (qispos) and the canteen menu of Hochschule Offenburg. Laogai uses the TCoR-API fot timetables and the canteen menu, wich makes acessing them super fast. To get the grades from Qispos, Laogai will ask for your login data, wich are stored encrypted on your device.
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="75">](https://f-droid.org/packages/org.mosad.seil0.projectlaogai/)
[<img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" height="75">](https://play.google.com/store/apps/details?id=org.mosad.seil0.projectlaogai)
@ -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
## Screenshots
[<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.webp" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.webp)
[<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.webp" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.webp)
[<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)
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.png)
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.png)
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.png)
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.png)
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa_dark.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa_dark.png)
ProjectLaogai © 2019-2020 [@Seil0](https://git.mosad.xyz/Seil0), a [mosad.xyz](http://www.mosad.xyz) Project

View File

@ -1,35 +1,22 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
}
kotlin {
jvmToolchain 11
sourceSets.configureEach {
languageSettings.optIn("kotlin.RequiresOptIn")
}
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdk 34
buildToolsVersion = '34.0.0'
signingConfigs {
}
compileSdkVersion 29
defaultConfig {
applicationId "org.mosad.seil0.projectlaogai"
minSdkVersion 23
targetSdkVersion 33
versionCode 6100 // 00.06.100
versionName "0.6.1"
targetSdkVersion 29
versionCode 6000 // 0006000
versionName "0.6.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "build_time", buildTime()
setProperty("archivesBaseName", "projectlaogai-$versionName")
}
buildFeatures {
viewBinding true
}
buildTypes {
release {
minifyEnabled true
@ -38,6 +25,11 @@ android {
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
sourceSets {
main {
res.srcDirs =
@ -50,44 +42,32 @@ android {
]
}
}
testOptions.unitTests.all {
useJUnitPlatform()
testLogging {
events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
}
}
namespace 'org.mosad.seil0.projectlaogai'
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'
implementation 'androidx.core:core:1.3.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
implementation "androidx.work:work-runtime-ktx:2.9.0"
implementation 'com.google.android.material:material:1.11.0'
implementation 'com.google.code.gson:gson:2.10'
// implementation 'com.afollestad:aesthetic:1.0.0-beta05'
implementation 'androidx.security:security-crypto:1.1.0-alpha02'
implementation 'com.google.android.material:material:1.2.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.afollestad:aesthetic:1.0.0-beta05'
implementation 'com.afollestad.material-dialogs:core:3.3.0'
implementation 'com.afollestad.material-dialogs:color:3.3.0'
implementation 'com.afollestad.material-dialogs:bottomsheets:3.3.0'
implementation 'de.psdev.licensesdialog:licensesdialog:2.1.0'
implementation 'org.apache.commons:commons-lang3:3.11'
implementation 'org.jsoup:jsoup:1.15.3'
implementation 'org.jsoup:jsoup:1.13.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
}
static def buildTime() {

View File

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

View File

@ -1,55 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mosad.seil0.projectlaogai">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.NFC" />
<application
android:allowBackup="true"
android:fullBackupContent="@xml/backup_descriptor"
android:icon="@mipmap/ic_laogai_icon"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_laogai_icon"
android:supportsRtl="true"
android:theme="@style/AppTheme.Light">
android:allowBackup="true"
android:fullBackupContent="@xml/backup_descriptor"
android:icon="@mipmap/ic_laogai_icon"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_laogai_icon"
android:supportsRtl="true"
android:theme="@style/AppTheme.Light">
<activity
android:name=".SplashActivity"
android:screenOrientation="portrait"
android:theme="@style/SplashTheme"
android:exported="true">
android:name=".SplashActivity"
android:label="@string/app_name"
android:theme="@style/SplashTheme"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
<meta-data android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<activity
android:name=".OnboardingActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.Light" />
android:name=".OnboardingActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.Light"
android:screenOrientation="portrait"
android:launchMode="singleTop">
</activity>
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.Light">
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.Light"
android:screenOrientation="portrait"
android:launchMode="singleTop">
<!-- nfc stuff -->
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<action android:name="android.nfc.action.TECH_DISCOVERED"/>
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter" />
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter" />
</activity>
</application>

View File

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

View File

@ -31,20 +31,15 @@ import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.text.HtmlCompat
import androidx.lifecycle.lifecycleScope
import androidx.viewpager.widget.ViewPager
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
import com.afollestad.materialdialogs.callbacks.onDismiss
import kotlinx.android.synthetic.main.activity_onboarding.*
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.databinding.ActivityOnboardingBinding
import org.mosad.seil0.projectlaogai.fragments.SettingsFragment
import org.mosad.seil0.projectlaogai.onboarding.ViewPagerAdapter
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.CourseSelectionDialog
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoadingDialog
class OnboardingActivity : AppCompatActivity() {
private lateinit var binding: ActivityOnboardingBinding
companion object {
val layouts = intArrayOf(R.layout.fragment_on_welcome, R.layout.fragment_on_course, R.layout.fragment_on_login)
}
@ -59,9 +54,7 @@ class OnboardingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityOnboardingBinding.inflate(layoutInflater)
setContentView(binding.root)
setContentView(R.layout.activity_onboarding)
viewPager = findViewById(R.id.viewPager)
linLayoutDots = findViewById(R.id.linLayout_Dots)
@ -73,7 +66,7 @@ class OnboardingActivity : AppCompatActivity() {
viewPager.addOnPageChangeListener(viewPagerPageChangeListener)
// we don't use the skip button, instead we use the start button to skip the last fragment
binding.btnSkip.visibility = View.GONE
btn_Skip.visibility = View.GONE
}
fun btnNextClick(@Suppress("UNUSED_PARAMETER")v: View) {
@ -88,28 +81,8 @@ class OnboardingActivity : AppCompatActivity() {
launchHomeScreen()
}
fun btnSelectCourseClick(v: View) {
CourseSelectionDialog(this).show {
list = CacheController.coursesList.map { it.courseName }
listItems {
val loadingDialog = LoadingDialog(context)
loadingDialog.show()
lifecycleScope.launch(Dispatchers.Default) {
Preferences.saveCourse(context, CacheController.coursesList[selectedIndex]) // save the course
// update current & next weeks timetable
val threads = listOf(
CacheController.updateTimetable(Preferences.course.courseName, 0, context),
CacheController.updateTimetable(Preferences.course.courseName, 1, context)
)
threads.joinAll() // blocking since we want the new data
withContext(Dispatchers.Main) {
loadingDialog.dismiss()
}
}
}
fun btnSelectCourseClick(@Suppress("UNUSED_PARAMETER")v: View) {
SettingsFragment().selectCourse(this).show {
onDismiss {
btnNextClick(v) // show the next fragment
}
@ -156,12 +129,12 @@ class OnboardingActivity : AppCompatActivity() {
addBottomDots(position)
// changing the next button text to skip for the login fragment
if (position == layouts.size - 1) {
binding.btnNext.text = getString(R.string.skip)
binding.btnNext.visibility = View.VISIBLE
binding.btnSkip.visibility = View.GONE
btn_Next.text = getString(R.string.skip)
btn_Next.visibility = View.VISIBLE
btn_Skip.visibility = View.GONE
} else {
binding.btnNext.visibility = View.GONE
binding.btnSkip.visibility = View.GONE
btn_Next.visibility = View.GONE
btn_Skip.visibility = View.GONE
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ package org.mosad.seil0.projectlaogai.controller.cache
import android.content.Context
import kotlinx.coroutines.Job
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
import org.mosad.seil0.projectlaogai.util.Lesson
import org.mosad.seil0.projectlaogai.util.TimetableWeek
@ -38,91 +38,94 @@ import org.mosad.seil0.projectlaogai.util.TimetableWeek
* * add second week
* * add configurable week to addSubject() and removeSubject(), updateAdditionalLessons()
*/
object TimetableController {
class TimetableController {
val timetable = ArrayList<TimetableWeek>()
val lessonMap = HashMap<String, Lesson>() // the key is courseName-subject-lessonID
val subjectMap = HashMap<String, ArrayList<String>>() // the key is courseName
companion object {
val timetable = ArrayList<TimetableWeek>()
val lessonMap = HashMap<String, Lesson>() // the key is courseName-subject-lessonID
val subjectMap = HashMap<String, ArrayList<String>>() // the key is courseName
/**
* update the main timetable and all additional subjects, async
*/
fun update(context: Context): List<Job> {
return listOf(
CacheController.updateTimetable(Preferences.course.courseName, 0, context),
CacheController.updateTimetable(Preferences.course.courseName, 1, context),
CacheController.updateAdditionalLessons(context)
)
}
/**
* add a subject to the subjectMap and all it's lessons
* to the lessonMap
* @param courseName course to which the subject belongs
* @param subject the subjects name
*/
fun addSubject(courseName: String, subject: String, context: Context) {
// add subject
if (subjectMap.containsKey(courseName)) {
subjectMap[courseName]?.add(subject)
} else {
subjectMap[courseName] = arrayListOf(subject)
/**
* update the main timetable and all additional subjects, async
*/
fun update(context: Context): List<Job> {
return listOf(
CacheController.updateTimetable(cCourse.courseName, 0, context),
CacheController.updateTimetable(cCourse.courseName, 1, context),
CacheController.updateAdditionalLessons(context)
)
}
// add concrete lessons
TCoRAPIController.getLessons(courseName, subject, 0).forEach { lesson ->
addLesson(courseName, subject, lesson)
}
CacheController.saveAdditionalSubjects(context)
}
/**
* remove a subject from the subjectMap and all it's lessons
* from the lessonMap
* @param courseName course to which the subject belongs
* @param subject the subjects name
*/
fun removeSubject(courseName: String, subject: String, context: Context) {
// remove subject
subjectMap[courseName]?.remove(subject)
// remove concrete lessons
val iterator = lessonMap.iterator()
while (iterator.hasNext()) {
val it = iterator.next()
if(it.key.contains("$courseName-$subject")) {
// remove the lesson from the lessons list
iterator.remove() // use iterator to remove, otherwise ConcurrentModificationException
// remove the lesson from the timetable
val id = it.value.lessonID.split(".")
if(id.size == 3)
timetable[0].days[id[0].toInt()].timeslots[id[1].toInt()].remove(it.value)
/**
* add a subject to the subjectMap and all it's lessons
* to the lessonMap
* @param courseName course to which the subject belongs
* @param subject the subjects name
*/
fun addSubject(courseName: String, subject: String, context: Context) {
// add subject
if (subjectMap.containsKey(courseName)) {
subjectMap[courseName]?.add(subject)
} else {
subjectMap[courseName] = arrayListOf(subject)
}
// add concrete lessons
TCoRAPIController.getLessons(courseName, subject, 0).forEach { lesson ->
addLesson(courseName, subject, lesson)
}
CacheController.saveAdditionalSubjects(context)
}
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)
/**
* 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
// 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
addLessonToTimetable(lesson)
}
// remove the lesson from the timetable
val id = it.value.lessonID.split(".")
if(id.size == 3)
timetable[0].days[id[0].toInt()].timeslots[id[1].toInt()].remove(it.value)
}
}
CacheController.saveAdditionalSubjects(context)
}
/**
* add a lesson to the lessonMap, also add it to the timetable
*/
fun addLesson(courseName: String, subject: String, lesson: Lesson) {
//the courseName, subject and lessonID, separator: -
val key = "$courseName-$subject-${lesson.lessonID}"
lessonMap[key] = lesson
addLessonToTimetable(lesson)
}
/**
* add a lesson to the timetable
*/
fun addLessonToTimetable(lesson: Lesson) {
val id = lesson.lessonID.split(".")
if(id.size == 3)
timetable[0].days[id[0].toInt()].timeslots[id[1].toInt()].add(lesson)
}
/**
* 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,15 +36,13 @@ object Preferences {
var coursesCacheTime: Long = 0
var mensaCacheTime: Long = 0
var timetableCacheTime: Long = 0
var gradesCacheTime: Long = 0
var colorPrimary: Int = Color.parseColor("#009688")
var colorAccent: Int = Color.parseColor("#0096ff")
var gradesSyncInterval = 0
var course = Course(
var cColorPrimary: Int = Color.parseColor("#009688")
var cColorAccent: Int = Color.parseColor("#0096ff")
var cCourse = Course(
"https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0",
"AI3"
)
var showBuffet = true
var cShowBuffet = true
var oGiants = false
// TODO move!
@ -69,9 +67,6 @@ object Preferences {
putLong(context.getString(R.string.save_key_timetableCacheTime),
timetableCacheTime
)
putLong(context.getString(R.string.save_key_gradesCacheTime),
gradesCacheTime
)
apply()
}
@ -92,7 +87,7 @@ object Preferences {
apply()
}
this.course = course
cCourse = course
}
/**
@ -111,7 +106,7 @@ object Preferences {
apply()
}
this.colorPrimary = colorPrimary
cColorPrimary = colorPrimary
}
/**
@ -130,23 +125,7 @@ object Preferences {
apply()
}
this.colorAccent = colorAccent
}
fun saveGradesSync(context: Context, interval: Int) {
val sharedPref = context.getSharedPreferences(
context.getString(R.string.preference_file_key),
Context.MODE_PRIVATE
)
with (sharedPref.edit()) {
putInt(context.getString(R.string.save_key_gradesSyncInterval),
interval
)
apply()
}
gradesSyncInterval = interval
cColorAccent = colorAccent
}
/**
@ -165,7 +144,7 @@ object Preferences {
apply()
}
this.showBuffet = showBuffet
cShowBuffet = showBuffet
}
/**
@ -178,42 +157,36 @@ object Preferences {
)
// load the update times (cache)
coursesCacheTime = sharedPref.getLong(
context.getString(R.string.save_key_coursesCacheTime),0
)
mensaCacheTime = sharedPref.getLong(
context.getString(R.string.save_key_mensaCacheTime),0
)
timetableCacheTime = sharedPref.getLong(
context.getString(R.string.save_key_timetableCacheTime),0
)
gradesCacheTime = sharedPref.getLong(
context.getString(R.string.save_key_gradesCacheTime), 0
)
coursesCacheTime = sharedPref.getLong(context.getString(
R.string.save_key_coursesCacheTime
), 0)
mensaCacheTime = sharedPref.getLong(context.getString(
R.string.save_key_mensaCacheTime
), 0)
timetableCacheTime = sharedPref.getLong(context.getString(
R.string.save_key_timetableCacheTime
), 0)
// load saved course
course = Course(
sharedPref.getString(context.getString(R.string.save_key_courseTTLink),"https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0")!!,
cCourse = Course(
sharedPref.getString(context.getString(R.string.save_key_courseTTLink),
"https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0"
)!!,
sharedPref.getString(context.getString(R.string.save_key_course), "AI3")!!
)
// load saved colors
colorPrimary = sharedPref.getInt(
context.getString(R.string.save_key_colorPrimary), colorPrimary
)
colorAccent = sharedPref.getInt(
context.getString(R.string.save_key_colorAccent), colorAccent
)
// load grades sync interval
gradesSyncInterval = sharedPref.getInt(
context.getString(R.string.save_key_gradesSyncInterval), gradesSyncInterval
)
cColorPrimary = sharedPref.getInt(context.getString(
R.string.save_key_colorPrimary
), cColorPrimary)
cColorAccent = sharedPref.getInt(context.getString(
R.string.save_key_colorAccent
), cColorAccent)
// load showBuffet
showBuffet = sharedPref.getBoolean(
context.getString(R.string.save_key_showBuffet), true
)
cShowBuffet = sharedPref.getBoolean(context.getString(
R.string.save_key_showBuffet
), true)
}
}

View File

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

View File

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

View File

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

View File

@ -22,35 +22,43 @@
package org.mosad.seil0.projectlaogai.fragments
//import com.afollestad.aesthetic.Aesthetic
import android.content.Context
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.widget.SwitchCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.afollestad.aesthetic.Aesthetic
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.WhichButton
import com.afollestad.materialdialogs.actions.getActionButton
import com.afollestad.materialdialogs.callbacks.onDismiss
import com.afollestad.materialdialogs.color.colorChooser
import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.list.listItems
import com.afollestad.materialdialogs.list.listItemsMultiChoice
import com.afollestad.materialdialogs.list.listItemsSingleChoice
import de.psdev.licensesdialog.LicensesDialog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.android.synthetic.main.fragment_settings.*
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.BuildConfig
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.coursesList
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.databinding.FragmentSettingsBinding
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.CourseSelectionDialog
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoadingDialog
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorAccent
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cColorPrimary
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cShowBuffet
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
import org.mosad.seil0.projectlaogai.util.DataTypes
import java.util.*
/**
* The settings controller class
@ -58,13 +66,21 @@ import org.mosad.seil0.projectlaogai.util.DataTypes
*/
class SettingsFragment : Fragment() {
private lateinit var binding: FragmentSettingsBinding
private lateinit var linLayoutUser: LinearLayout
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
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentSettingsBinding.inflate(inflater, container, false)
return binding.root
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_settings, container, false)
}
/**
@ -73,42 +89,56 @@ class SettingsFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
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()
binding.textUser.text = EncryptedPreferences.email.ifEmpty { resources.getString(R.string.sample_user) }
binding.textCourse.text = Preferences.course.courseName
binding.textAboutDesc.text = resources.getString(R.string.about_version, BuildConfig.VERSION_NAME, getString(R.string.build_time))
binding.switchBuffet.isChecked = Preferences.showBuffet // init switch
txtView_User.text = EncryptedPreferences.email.ifEmpty { resources.getString(R.string.sample_user) }
txtView_Course.text = cCourse.courseName
txtView_AboutDesc.text = resources.getString(R.string.about_version, BuildConfig.VERSION_NAME, getString(R.string.build_time))
switch_buffet.isChecked = cShowBuffet // init switch
val outValue = TypedValue()
requireActivity().theme.resolveAttribute(R.attr.themeName, outValue, true)
activity!!.theme.resolveAttribute(R.attr.themeName, outValue, true)
when(outValue.string) {
"light" -> {
binding.switchBuffet.setTextColor(requireActivity().resources.getColor(R.color.textPrimaryLight, requireActivity().theme))
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryLight, activity!!.theme))
selectedTheme = DataTypes.Theme.Light
selectedTheme.string = resources.getString(R.string.themeLight)
}
"dark" -> {
binding.switchBuffet.setTextColor(requireActivity().resources.getColor(R.color.textPrimaryDark, requireActivity().theme))
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryDark, activity!!.theme))
selectedTheme = DataTypes.Theme.Dark
selectedTheme.string = resources.getString(R.string.themeDark)
}
"black" -> {
binding.switchBuffet.setTextColor(requireActivity().resources.getColor(R.color.textPrimaryDark, requireActivity().theme))
switch_buffet.setTextColor(activity!!.resources.getColor(R.color.textPrimaryDark, activity!!.theme))
selectedTheme = DataTypes.Theme.Black
selectedTheme.string = resources.getString(R.string.themeBlack)
}
}
binding.textThemeSelected.text = selectedTheme.string
txtView_SelectedTheme.text = selectedTheme.string
}
/**
* initialize some actions for SettingsFragment elements
*/
private fun initActions() {
binding.linLayoutUser.setOnClickListener {
linLayoutUser.setOnClickListener {
// open a new dialog
LoginDialog(requireContext())
LoginDialog(context!!)
.positiveButton {
EncryptedPreferences.saveCredentials(email, password, context)
}
@ -118,40 +148,20 @@ class SettingsFragment : Fragment() {
}
}
binding.linLayoutUser.setOnLongClickListener {
linLayoutUser.setOnLongClickListener {
Preferences.oGiants = true // enable easter egg
return@setOnLongClickListener true
}
binding.linLayoutCourse.setOnClickListener {
CourseSelectionDialog(requireContext()).show {
list = coursesList.map { it.courseName }
listItems {
val loadingDialog = LoadingDialog(context)
loadingDialog.show()
lifecycleScope.launch(Dispatchers.Default) {
Preferences.saveCourse(context, coursesList[selectedIndex]) // save the course
// update current & next weeks timetable
val threads = listOf(
CacheController.updateTimetable(Preferences.course.courseName, 0, context),
CacheController.updateTimetable(Preferences.course.courseName, 1, context)
)
threads.joinAll() // blocking since we want the new data
withContext(Dispatchers.Main) {
loadingDialog.dismiss()
}
}
}
linLayoutCourse.setOnClickListener {
selectCourse(context!!).show {
onDismiss {
binding.textCourse.text = Preferences.course.courseName // update txtView after the dialog is dismissed
txtViewCourse.text = cCourse.courseName // update txtView after the dialog is dismissed
}
}
}
binding.linLayoutManageLessons.setOnClickListener {
linLayoutManageLessons.setOnClickListener {
val lessons = ArrayList<String>()
TimetableController.subjectMap.forEach { pair ->
pair.value.forEach {
@ -159,12 +169,12 @@ class SettingsFragment : Fragment() {
}
}
MaterialDialog(requireContext()).show {
MaterialDialog(context!!).show {
title(R.string.manage_lessons)
positiveButton(R.string.delete)
negativeButton(R.string.cancel)
getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.colorAccent)
getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.colorAccent)
getActionButton(WhichButton.POSITIVE).updateTextColor(cColorAccent)
getActionButton(WhichButton.NEGATIVE).updateTextColor(cColorAccent)
listItemsMultiChoice(items = lessons) { _, _, items ->
items.forEach {
@ -175,18 +185,18 @@ class SettingsFragment : Fragment() {
}
}
binding.linLayoutAbout.setOnClickListener {
linLayoutAbout.setOnClickListener {
// open a new info dialog
MaterialDialog(requireContext())
MaterialDialog(context!!)
.title(R.string.about_dialog_heading)
.message(R.string.about_dialog_text)
.show()
}
binding.linLayoutLicence.setOnClickListener {
linLayoutLicence.setOnClickListener {
// do the theme magic, as the lib's theme support is broken
val outValue = TypedValue()
requireContext().theme.resolveAttribute(R.attr.themeName, outValue, true)
context!!.theme.resolveAttribute(R.attr.themeName, outValue, true)
val dialogCss = when (outValue.string) {
"light" -> R.string.license_dialog_style_light
@ -199,7 +209,7 @@ class SettingsFragment : Fragment() {
}
// open a new license dialog
LicensesDialog.Builder(requireContext())
LicensesDialog.Builder(context!!)
.setNotices(R.raw.notices)
.setTitle(R.string.licenses)
.setIncludeOwnLicense(true)
@ -209,72 +219,106 @@ class SettingsFragment : Fragment() {
.show()
}
// binding.linLayoutTheme.setOnClickListener {
// val themes = listOf(
// resources.getString(R.string.themeLight),
// resources.getString(R.string.themeDark),
// resources.getString(R.string.themeBlack)
// )
//
// MaterialDialog(requireContext()).show {
// title(R.string.theme)
// listItemsSingleChoice(items = themes, initialSelection = selectedTheme.ordinal) { _, index, _ ->
// Aesthetic.config {
// when (index) {
// 0 -> activityTheme(R.style.AppTheme_Light)
// 1 -> activityTheme(R.style.AppTheme_Dark)
// 2 -> activityTheme(R.style.AppTheme_Black)
// else -> activityTheme(R.style.AppTheme_Light)
// }
// apply()
// }
// }
// }
// }
linLayoutTheme.setOnClickListener {
val themes = listOf(
resources.getString(R.string.themeLight),
resources.getString(R.string.themeDark),
resources.getString(R.string.themeBlack)
)
// binding.linLayoutPrimaryColor.setOnClickListener {
// // open a new color chooser dialog
// MaterialDialog(requireContext())
// .colorChooser(DataTypes().primaryColors, allowCustomArgb = true, initialSelection = Preferences.colorPrimary) { _, color ->
// binding.viewPrimaryColor.setBackgroundColor(color)
// Aesthetic.config {
// colorPrimary(color)
// colorPrimaryDark(color)
// apply()
// }
// Preferences.saveColorPrimary(requireContext(), color)
// }
// .show {
// title(R.string.primary_color)
// positiveButton(R.string.select)
// getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.colorAccent)
// }
//
// }
MaterialDialog(context!!).show {
title(R.string.theme)
listItemsSingleChoice(items = themes, initialSelection = selectedTheme.ordinal) { _, index, _ ->
Aesthetic.config {
when (index) {
0 -> activityTheme(R.style.AppTheme_Light)
1 -> activityTheme(R.style.AppTheme_Dark)
2 -> activityTheme(R.style.AppTheme_Black)
else -> activityTheme(R.style.AppTheme_Light)
}
apply()
}
}
}
}
// 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)
// }
// }
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)
}
binding.switchBuffet.setOnClickListener {
Preferences.saveShowBuffet(requireContext(), binding.switchBuffet.isChecked)
}
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,14 +26,15 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ScrollView
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.android.synthetic.main.fragment_timetable.*
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController.timetable
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController.Companion.timetable
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.databinding.FragmentTimetableBinding
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.AddSubjectDialog
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.TextViewInfo
@ -43,45 +44,47 @@ import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
* The timetable controller class
* contains all needed parts to display and the timetable detail screen
*/
class TimetableFragment : Fragment() {
class TimeTableFragment : Fragment() {
private lateinit var binding: FragmentTimetableBinding
private lateinit var scrollViewTimetable: ScrollView
private lateinit var faBtnAddSubject: FloatingActionButton
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentTimetableBinding.inflate(inflater, container, false)
return binding.root
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_timetable, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.refreshLayoutTimetable.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
scrollViewTimetable = view.findViewById(R.id.scrollView_Timetable)
faBtnAddSubject = view.findViewById(R.id.faBtnAddSubject)
refreshLayout_Timetable.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
initActions() // init actions
if (timetable.size > 1 && timetable[0].days.isNotEmpty() && timetable[1].days.isNotEmpty()) {
initTimetable()
} else {
val txtViewInfo = TextViewInfo(requireContext()).set {
val txtViewInfo = TextViewInfo(context!!).set {
txt = resources.getString(R.string.timetable_generic_error)
}.showImage()
binding.linLayoutTimetable.addView(txtViewInfo)
linLayout_Timetable.addView(txtViewInfo)
}
}
/**
* initialize the actions
*/
private fun initActions() {
private fun initActions() = GlobalScope.launch(Dispatchers.Main) {
binding.refreshLayoutTimetable.setOnRefreshListener {
runBlocking { TimetableController.update(requireContext()).joinAll() }
refreshLayout_Timetable.setOnRefreshListener {
runBlocking { TimetableController.update(context!!).joinAll() }
reloadTimetableUI()
}
// show the AddLessonDialog if the ftaBtn is clicked
binding.faBtnAddSubject.setOnClickListener {
AddSubjectDialog(requireContext())
faBtnAddSubject.setOnClickListener {
AddSubjectDialog(context!!)
.positiveButton {
TimetableController.addSubject(selectedCourse, selectedSubject, context)
runBlocking { reloadTimetableUI() }
@ -89,11 +92,11 @@ class TimetableFragment : Fragment() {
}
// hide the btnCardValue if the user is scrolling down
binding.scrollViewTimetable.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
scrollViewTimetable.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if (scrollY > oldScrollY) {
binding.faBtnAddSubject.hide()
faBtnAddSubject.hide()
} else {
binding.faBtnAddSubject.show()
faBtnAddSubject.show()
}
}
@ -102,16 +105,16 @@ class TimetableFragment : Fragment() {
/**
* add the current and next weeks lessons
*/
private fun initTimetable() = lifecycleScope.launch(Dispatchers.Default) {
private fun initTimetable() = GlobalScope.launch(Dispatchers.Default) {
val currentDayIndex = NotRetardedCalendar.getDayOfWeekIndex()
addTimetableWeek(currentDayIndex, 5, 0).join() // add current week
addTimetableWeek(0, currentDayIndex - 1, 1) // add next week
}
private fun addTimetableWeek(dayBegin: Int, dayEnd: Int, week: Int) = lifecycleScope.launch(Dispatchers.Main) {
private fun addTimetableWeek(dayBegin: Int, dayEnd: Int, week: Int) = GlobalScope.launch(Dispatchers.Main) {
for (dayIndex in dayBegin..dayEnd) {
val dayCardView = DayCardView(requireContext())
val dayCardView = DayCardView(context!!)
// some wired calendar magic, calculate the correct date to be shown
// ((timetable week - current week * 7) + (dayIndex - dayIndex of current week)
@ -121,7 +124,7 @@ class TimetableFragment : Fragment() {
// if there are no lessons don't show the dayCardView
if (dayCardView.getLinLayoutDay().childCount > 1)
binding.linLayoutTimetable.addView(dayCardView)
linLayout_Timetable.addView(dayCardView)
}
}
@ -129,10 +132,10 @@ class TimetableFragment : Fragment() {
/**
* clear linLayout_Timetable, add the updated timetable
*/
private fun reloadTimetableUI() = lifecycleScope.launch(Dispatchers.Default) {
private fun reloadTimetableUI() = GlobalScope.launch(Dispatchers.Default) {
withContext(Dispatchers.Main) {
// remove all lessons from the layout
binding.linLayoutTimetable.removeAllViews()
linLayout_Timetable.removeAllViews()
// add the refreshed timetables
val dayIndex = NotRetardedCalendar.getDayOfWeekIndex()
@ -140,7 +143,7 @@ class TimetableFragment : Fragment() {
addTimetableWeek(dayIndex, 5, 0).join() // add current week
addTimetableWeek(0, dayIndex - 1, 1) // add next week
binding.refreshLayoutTimetable.isRefreshing = false
refreshLayout_Timetable.isRefreshing = false
}
}

View File

@ -24,10 +24,10 @@ package org.mosad.seil0.projectlaogai.uicomponents
import android.content.Context
import android.graphics.Color
import android.view.LayoutInflater
import android.widget.LinearLayout
import androidx.cardview.widget.CardView
import org.mosad.seil0.projectlaogai.databinding.CardviewDayBinding
import kotlinx.android.synthetic.main.cardview_day.view.*
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.util.DataTypes
import org.mosad.seil0.projectlaogai.util.TimetableDay
import java.text.SimpleDateFormat
@ -35,19 +35,21 @@ import java.util.*
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())
init {
this.setBackgroundColor(Color.TRANSPARENT) // workaround to prevent a white border
inflate(context, R.layout.cardview_day,this)
// workaround to prevent a white border
this.setBackgroundColor(Color.TRANSPARENT)
}
fun getLinLayoutDay() : LinearLayout {
return binding.linearDay
return linLayout_Day
}
fun setDayHeading(heading: String) {
binding.textDayHeading.text = heading
txtView_DayHeading.text = heading
}
/**
@ -60,7 +62,7 @@ class DayCardView(context: Context) : CardView(context) {
// set the heading
val cal = Calendar.getInstance()
cal.add(Calendar.DATE, daysToAdd)
binding.textDayHeading.text = formatter.format(cal.time)
txtView_DayHeading.text = formatter.format(cal.time)
// for every timeslot of that timetable
timetable.timeslots.forEachIndexed { tsIndex, timeslot ->
@ -69,7 +71,7 @@ class DayCardView(context: Context) : CardView(context) {
val lessonLayout = LessonLinearLayout(context)
lessonLayout.setLesson(lesson, DataTypes().times[tsIndex])
binding.linearDay.addView(lessonLayout)
linLayout_Day.addView(lessonLayout)
if (lesson != timeslot.last()) {
lessonLayout.disableDivider()

View File

@ -0,0 +1,58 @@
/**
* 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,25 +23,27 @@
package org.mosad.seil0.projectlaogai.uicomponents
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import org.mosad.seil0.projectlaogai.databinding.LinearlayoutLessonBinding
import kotlinx.android.synthetic.main.linearlayout_lesson.view.*
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.util.Lesson
class LessonLinearLayout(context: Context?) : LinearLayout(context) {
private val binding = LinearlayoutLessonBinding.inflate(LayoutInflater.from(context), this, true)
init {
inflate(context, R.layout.linearlayout_lesson, this)
}
fun setLesson(lesson: Lesson, time: String) {
binding.textLessonTime.text = time
binding.textLessonSubject.text = lesson.lessonSubject
binding.textLessonTeacher.text = lesson.lessonTeacher
binding.textLessonRoom.text = lesson.lessonRoom
txtView_lessonTime.text = time
txtView_lessonSubject.text = lesson.lessonSubject
txtView_lessonTeacher.text = lesson.lessonTeacher
txtView_lessonRoom.text = lesson.lessonRoom
}
fun disableDivider() {
binding.dividerLesson.visibility = View.GONE
divider_lesson.visibility = View.GONE
}
}

View File

@ -23,28 +23,30 @@
package org.mosad.seil0.projectlaogai.uicomponents
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import org.mosad.seil0.projectlaogai.databinding.LinearlayoutMealBinding
import kotlinx.android.synthetic.main.linearlayout_meal.view.*
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.util.Meal
class MealLinearLayout(context: Context?): LinearLayout(context) {
private val binding = LinearlayoutMealBinding.inflate(LayoutInflater.from(context), this, true)
init {
inflate(context, R.layout.linearlayout_meal, this)
}
fun setMeal(meal: Meal) {
binding.textMealHeading.text = meal.heading
txtView_MealHeading.text = meal.heading
meal.parts.forEachIndexed { partIndex, part ->
binding.textMeal.append(part)
txtView_Meal.append(part)
if(partIndex < (meal.parts.size - 1))
binding.textMeal.append("\n")
txtView_Meal.append("\n")
}
}
fun disableDivider() {
binding.dividerMeal.visibility = View.GONE
divider_meal.visibility = View.GONE
}
}

View File

@ -41,6 +41,7 @@ class TextViewInfo(context: Context?): AppCompatTextView(context!!) {
init {
params.setMargins(0,200,0,0)
}
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)
// fix not working accent color
dialog.getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.colorAccent)
dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.colorAccent)
dialog.getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.cColorAccent)
dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.cColorAccent)
initSpinners()
}
@ -92,11 +92,6 @@ class AddSubjectDialog(val context: Context) {
this.show()
}
@Suppress("unused")
fun dismiss() {
dialog.dismiss()
}
private fun initSpinners() {
setArrayAdapter(spinnerCourses, courseNamesList)
val lessonsAdapter = setArrayAdapter(spinnerSubjects, subjectsList)

View File

@ -1,47 +0,0 @@
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

@ -1,30 +0,0 @@
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)
// fix not working accent color
dialog.getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.colorAccent)
dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.colorAccent)
dialog.getActionButton(WhichButton.POSITIVE).updateTextColor(Preferences.cColorAccent)
dialog.getActionButton(WhichButton.NEGATIVE).updateTextColor(Preferences.cColorAccent)
}
fun positiveButton(func: LoginDialog.() -> Unit): LoginDialog = apply {
@ -75,22 +75,13 @@ class LoginDialog(val context: Context) {
}
}
fun show() {
dialog.show()
}
fun show(func: LoginDialog.() -> Unit): LoginDialog = apply {
func()
editTextEmail.setText(email)
editTextPassword.setText(password)
show()
}
@Suppress("unused")
fun dismiss() {
dialog.dismiss()
dialog.show()
}
}

View File

@ -1,36 +0,0 @@
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,10 +10,9 @@
tools:openDrawer="start">
<include
android:id="@+id/app_bar"
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
@ -23,6 +22,7 @@
android:fitsSystemWindows="true"
android:background="?themeSecondary"
app:headerLayout="@layout/nav_header_main"
app:itemTextColor="?colorAccent"
app:menu="@menu/activity_main_drawer"/>
</androidx.drawerlayout.widget.DrawerLayout>

View File

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

View File

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

View File

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

View File

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

View File

@ -21,7 +21,7 @@
android:shortcutLongLabel="@string/shortcut_timetable_long"
android:shortcutDisabledMessage="@string/shortcut_timetable_disabled">
<intent
android:action="org.mosad.seil0.projectlaogai.fragments.TimetableFragment"
android:action="org.mosad.seil0.projectlaogai.fragments.TimeTableFragment"
android:targetPackage="org.mosad.seil0.projectlaogai"
android:targetClass="org.mosad.seil0.projectlaogai.MainActivity" />
<categories android:name="android.shortcut.conversation" />
@ -40,18 +40,4 @@
android:targetClass="org.mosad.seil0.projectlaogai.MainActivity" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
<shortcut
android:shortcutId="grades"
android:enabled="true"
android:icon="@drawable/ic_grading_black_24dp"
android:shortcutShortLabel="@string/shortcut_grades_short"
android:shortcutLongLabel="@string/shortcut_grades_long"
android:shortcutDisabledMessage="@string/shortcut_grades_disabled">
<intent
android:action="org.mosad.seil0.projectlaogai.fragments.GradesFragment"
android:targetPackage="org.mosad.seil0.projectlaogai"
android:targetClass="org.mosad.seil0.projectlaogai.MainActivity" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
</shortcuts>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.GradesFragment">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/refreshLayout_Grades"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?themePrimary">
<ScrollView
android:id="@+id/scrollView_Grades"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/linLayout_Grades"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/txtView_Loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="100dp"
android:padding="7dp"
android:text="@string/loading_from_hs"
android:textAlignment="center"
android:textSize="15sp" />
</LinearLayout>
</ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</FrameLayout>

View File

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

View File

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

View File

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

View File

@ -66,9 +66,6 @@
<string name="primary_color_desc">Zum Ändern tippen, Standard ist Blaugrün.</string>
<string name="accent_color">Akzentfarbe</string>
<string name="accent_color_desc">Zum Ändern tippen, Standard ist Hellblau.</string>
<string name="grades_sync">Aktualisierungsintervall Noten</string>
<string name="grades_sync_desc">%1$d Stunden</string>
<string name="grades_sync_desc_never">nie</string>
<string name="show_buffet">Buffet immer anzeigen</string>
<!-- dialogs -->
@ -83,12 +80,6 @@
<string name="mensa_current">aktuell: %1$s\n</string>
<string name="mensa_last">letzte Abbuchung: %1$s</string>
<!-- notifications -->
<string name="notification_grades">Noten</string>
<string name="notification_grades_single_desc">%1$s wurden hinzugefügt oder aktualisiert</string>
<string name="notification_grades_multiple_desc">%1$s und %2$d weiter Vorlesungen wurden hinzugefügt oder aktualisiert</string>
<string name="notification_grades_updating_desc">Suche nach neuen Noten …</string>
<!-- errors -->
<string name="error">Fehler</string>
<string name="timetable_error">Der Stundenplan konnte nicht geladen werden.</string>
@ -106,16 +97,4 @@
<string name="shortcut_moodle_long">Moodle</string>
<string name="shortcut_moodle_disabled">Moodle deaktiviert</string>
<string name="shortcut_grades_short">Noten</string>
<string name="shortcut_grades_long">Noten</string>
<string name="shortcut_grades_disabled">Noten deaktiviert</string>
<string-array name="syncInterval">
<item>Manuell</item>
<item>1 Stunde</item>
<item>3 Stunden</item>
<item>6 Stunden</item>
<item>12 Stunden</item>
</string-array>
</resources>

View File

@ -1,28 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- base theme colors -->
<color name="colorPrimary">#009688</color>
<color name="colorPrimaryDark">#009688</color>
<color name="colorAccent">#0096ff</color>
<color name="ic_laogai_icon_background">#ffffff</color>
<!-- light theme colors -->
<!--theme color section, not the working 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="themeSecondaryLight">#ffffff</color>
<color name="textPrimaryLight">#de000000</color>
<color name="textSecondaryLight">#99000000</color>
<color name="dividerLight">#22000000</color>
<color name="textPrimaryLight">#000000</color>
<color name="textSecondaryLight">#818181</color>
<color name="dividerLight">#e0e0e0</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>

View File

@ -71,9 +71,6 @@
<string name="primary_color_desc">Tap to change, default is teal blue.</string>
<string name="accent_color">Accent color</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>
<!-- dialogs -->
@ -88,12 +85,6 @@
<string name="mensa_current">current: %1$s\n</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 -->
<string name="sample_user" translatable="false">spinefield@stud.hs-offenburg.de</string>
<string name="sample_course" translatable="false">Everything</string>
@ -121,10 +112,6 @@
<string name="shortcut_moodle_long">Moodle</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 -->
<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>
@ -132,28 +119,13 @@
<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_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_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_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_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">
<item>AI-1</item>

View File

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

View File

@ -0,0 +1,17 @@
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

@ -1,18 +0,0 @@
{
"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

@ -1,16 +0,0 @@
{
"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

@ -1,14 +0,0 @@
{
"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.
buildscript {
ext.kotlin_version = '1.9.22'
ext.kotlin_version = '1.4.0'
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.2.2'
classpath 'com.android.tools.build:gradle:4.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
@ -18,10 +18,10 @@ buildscript {
allprojects {
repositories {
google()
mavenCentral()
jcenter()
}
}
tasks.register('clean', Delete) {
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@ -1,5 +0,0 @@
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,6 +15,3 @@ org.gradle.jvmargs=-Xmx1536m
kotlin.code.style=official
android.useAndroidX=true
android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

Binary file not shown.

View File

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

283
gradlew vendored
View File

@ -1,7 +1,7 @@
#!/bin/sh
#!/usr/bin/env sh
#
# Copyright © 2015-2021 the original authors.
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -17,98 +17,67 @@
#
##############################################################################
#
# 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/.
#
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
MAX_FD="maximum"
warn () {
echo "$*"
} >&2
}
die () {
echo
echo "$*"
echo
exit 1
} >&2
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@ -118,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD=$JAVA_HOME/bin/java
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -129,120 +98,88 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
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.
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.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# 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" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
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
# 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
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# 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' ' '
)" '"$@"'
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

34
gradlew.bat vendored
View File

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