Browse Source

save additional subjects and load them on start

* the app now saves all aditional subjects and lessons and loads them on start
* udpate gradle 6.4.0 -> 6.5.1
* update coroutines 1.3.7 -> 1.3.8
* update several androidx libraries
pull/46/head
Jannik 2 years ago
parent
commit
fb3dab6dc3
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
  1. 10
      app/build.gradle
  2. 2
      app/src/main/java/org/mosad/seil0/projectlaogai/MainActivity.kt
  3. 114
      app/src/main/java/org/mosad/seil0/projectlaogai/controller/cache/CacheController.kt
  4. 65
      app/src/main/java/org/mosad/seil0/projectlaogai/controller/cache/TimetableController.kt
  5. 4
      app/src/main/java/org/mosad/seil0/projectlaogai/fragments/HomeFragment.kt
  6. 5
      app/src/main/java/org/mosad/seil0/projectlaogai/fragments/MensaFragment.kt
  7. 8
      app/src/main/java/org/mosad/seil0/projectlaogai/fragments/SettingsFragment.kt
  8. 34
      app/src/main/java/org/mosad/seil0/projectlaogai/fragments/TimeTableFragment.kt
  9. 12
      app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/AddSubjectDialog.kt
  10. BIN
      gradle/wrapper/gradle-wrapper.jar
  11. 2
      gradle/wrapper/gradle-wrapper.properties
  12. 2
      gradlew
  13. 1
      gradlew.bat

10
app/build.gradle

@ -47,13 +47,13 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7'
implementation 'androidx.core:core:1.3.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
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.0.0-beta8'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-rc1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
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'

2
app/src/main/java/org/mosad/seil0/projectlaogai/MainActivity.kt

@ -43,7 +43,7 @@ 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.CacheController
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
import org.mosad.seil0.projectlaogai.controller.NFCMensaCard
import org.mosad.seil0.projectlaogai.controller.PreferencesController
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorAccent

114
app/src/main/java/org/mosad/seil0/projectlaogai/controller/CacheController.kt → app/src/main/java/org/mosad/seil0/projectlaogai/controller/cache/CacheController.kt vendored

@ -20,33 +20,30 @@
*
*/
package org.mosad.seil0.projectlaogai.controller
package org.mosad.seil0.projectlaogai.controller.cache
import android.content.Context
import android.util.Log
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonParser
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.controller.PreferencesController
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.coursesCacheTime
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.mensaCacheTime
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.timetableCacheTime
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.util.*
import java.io.*
import java.util.*
import kotlin.Exception
import kotlin.collections.ArrayList
/**
* TODO rework
* * move all write functions from TCorController to CacheController
* * init: check for blocking updates, read all data from cache, check for non blocking update
* * [x] save needed data to validate cache in separate file
* * new functions:
* * [x] updateTimetable(): get Timetable object from TCoRController, write object to file
* * [x] updateMensa(): get MensaMenu object from TCoRController, write object to file
* * [ ] save additional subject and lessons
* The cacheController reads and updates the cache files.
* It contains the courseList and mensaMenu object, all timetable objects
* are located in TimetableController.
*/
class CacheController(cont: Context) {
@ -67,6 +64,7 @@ class CacheController(cont: Context) {
GlobalScope.launch(Dispatchers.Default) { updateMensaMenu(context).join() }
}
// check if we need to update the timetable before displaying it
cal.time = Date(timetableCacheTime * 1000)
// if a) it`s monday and the last cache update was not on a sunday or b) the cache is older than 24hr, update blocking
@ -94,8 +92,7 @@ class CacheController(cont: Context) {
updateMensaMenu(context)
if (currentTime - timetableCacheTime > 10800) {
updateTimetable(cCourse.courseName, 0, context)
updateTimetable(cCourse.courseName, 1, context)
TimetableController.update(context)
}
}
@ -163,7 +160,7 @@ class CacheController(cont: Context) {
try {
timetable = TCoRAPIController.getTimetable(courseName, week)
} catch (ex: Exception) {
Log.e(className, "could not load timetable from tcor", ex)
Log.e(className, "could not load timetable $courseName[$week] from tcor", ex)
}
// update timetable in TTC
@ -180,6 +177,42 @@ class CacheController(cont: Context) {
}
}
/**
* update all additional subject lessons, async
*/
fun updateAdditionalLessons(context: Context): Job {
val fileLessons = File(context.filesDir, "additional_lessons.json")
return GlobalScope.launch(Dispatchers.IO) {
TimetableController.subjectMap.forEach { (courseName, subjects) ->
// update all subjects for a course
subjects.forEach {subject ->
try {
TCoRAPIController.getLessons(courseName, subject, 0).forEach { lesson ->
TimetableController.addLesson(courseName, subject, lesson)
}
} catch (ex: Exception) {
Log.e(className, "could not load $courseName: $subject", ex)
}
}
}
save(fileLessons, Gson().toJson(TimetableController.lessonMap))
}
}
/**
* save changes in lessonMap and subjectMap,
* called on addSubject or removeSubject
*/
fun saveAdditionalSubjects(context: Context) {
val fileLessons = File(context.filesDir, "additional_lessons.json")
val fileSubjects = File(context.filesDir, "additional_subjects.json")
save(fileLessons, Gson().toJson(TimetableController.lessonMap))
save(fileSubjects, Gson().toJson(TimetableController.subjectMap))
}
private fun save(file: File, text: String) {
try {
val writer = BufferedWriter(FileWriter(file))
@ -188,7 +221,6 @@ class CacheController(cont: Context) {
} catch (ex: Exception) {
Log.e(className, "failed to write file \"${file.absoluteFile}\"", ex)
}
}
}
@ -199,36 +231,41 @@ class CacheController(cont: Context) {
*/
private fun readStartCache(courseName: String) {
try {
readCoursesList(context)
readCoursesList()
} catch (ex : Exception) {
Log.e(className, "Error while reading the course list", ex)
}
try {
readMensa(context)
readMensa()
} catch (ex : Exception) {
Log.e(className, "Error while reading the mensa menu", ex)
}
try {
readTimetable(courseName, 0, context)
readTimetable(courseName, 0)
} catch (ex : Exception) {
Log.e(className, "Error while reading timetable week 0", ex)
}
try {
readTimetable(courseName, 1, context)
readTimetable(courseName, 1)
} catch (ex : Exception) {
Log.e(className, "Error while reading timetable week 1", ex)
}
try {
readAdditionalSubjects()
} catch (ex : Exception) {
Log.e(className, "Error while reading additional subjects", ex)
}
}
/**
* read the courses list from the cached file
* add them to the coursesList object
*/
private fun readCoursesList(context: Context) {
private fun readCoursesList() {
val file = File(context.filesDir, "courses.json")
// make sure the file exists
@ -246,7 +283,7 @@ class CacheController(cont: Context) {
* get the MensaMenu object from the cached json,
* if cache is empty create the cache file
*/
private fun readMensa(context: Context) {
private fun readMensa() {
val file = File(context.filesDir, "mensa.json")
// make sure the file exists
@ -265,7 +302,7 @@ class CacheController(cont: Context) {
* @param courseName the course name (e.g AI1)
* @param week the week to read (0 for the current and so on)
*/
private fun readTimetable(courseName: String, week: Int, context: Context) {
private fun readTimetable(courseName: String, week: Int) {
val file = File(context.filesDir, "timetable-$courseName-$week.json")
// if the file does not exist, call updateTimetable blocking and return
@ -300,4 +337,39 @@ class CacheController(cont: Context) {
}
}
private fun readAdditionalSubjects() {
val fileLessons = File(context.filesDir, "additional_lessons.json")
val fileSubjects = File(context.filesDir, "additional_subjects.json")
// make sure the file exists
if (!fileLessons.exists() || !fileSubjects.exists()) {
return
}
// clear the maps before loading, just to be save
TimetableController.lessonMap.clear()
TimetableController.subjectMap.clear()
// read subjects and lessons from cache file
FileReader(fileLessons).use {
TimetableController.lessonMap.putAll(
GsonBuilder().create()
.fromJson(BufferedReader(it).readLine(), object : TypeToken<HashMap<String, Lesson>>() {}.type)
)
}
FileReader(fileSubjects).use {
TimetableController.subjectMap.putAll(
GsonBuilder().create()
.fromJson(BufferedReader(it).readLine(), HashMap<String, ArrayList<String>>().javaClass)
)
}
// add lessons to timetable
TimetableController.lessonMap.forEach { (_, lesson) ->
TimetableController.addLessonToTimetable(lesson)
}
}
}

65
app/src/main/java/org/mosad/seil0/projectlaogai/controller/TimetableController.kt → app/src/main/java/org/mosad/seil0/projectlaogai/controller/cache/TimetableController.kt vendored

@ -20,18 +20,21 @@
*
*/
package org.mosad.seil0.projectlaogai.controller
package org.mosad.seil0.projectlaogai.controller.cache
import android.content.Context
import kotlinx.coroutines.Job
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.util.Lesson
import org.mosad.seil0.projectlaogai.util.TimetableWeek
/**
* TODO this controller contains
* * a list with all additional lessons
* * the main course and additional info for timetables
* * all concrete objects
* The TimetableController contains the timetable, subjectMap
* and lessonMap objects. It also contains the additional subjects logic.
* All functions ro read or update cache files are located in the CacheController.
*
* TODO add configurable week to addSubject() and removeSubject()
* TODO add configurable week to addSubject() and removeSubject(), updateAdditionalLessons()
*/
class TimetableController {
@ -40,13 +43,24 @@ class TimetableController {
val lessonMap = HashMap<String, Lesson>() // the key is courseName-subject-lessonID
val subjectMap = HashMap<String, ArrayList<String>>() // the key is courseName
/**
* update the main timetable and all additional subjects, async
*/
fun update(context: Context): List<Job> {
return listOf(
CacheController.updateTimetable(cCourse.courseName, 0, context),
CacheController.updateTimetable(cCourse.courseName, 1, context),
CacheController.updateAdditionalLessons(context)
)
}
/**
* add a subject to the subjectMap and all it's lessons
* to the lessonMap
* @param courseName course to which the subject belongs
* @param subject the subjects name
*/
fun addSubject(courseName: String, subject: String) {
fun addSubject(courseName: String, subject: String, context: Context) {
// add subject
if (subjectMap.containsKey(courseName)) {
subjectMap[courseName]?.add(subject)
@ -55,16 +69,11 @@ class TimetableController {
}
// add concrete lessons
TCoRAPIController.getLessons(courseName, subject, 0).forEach {lesson ->
//the courseName, subject and lessonID, separator: -
val key = "$courseName-$subject-${lesson.lessonID}"
lessonMap[key] = lesson
// add lesson to the timetable
val id = lesson.lessonID.split(".")
if(id.size == 3)
timetable[0].days[id[0].toInt()].timeslots[id[1].toInt()].add(lesson)
TCoRAPIController.getLessons(courseName, subject, 0).forEach { lesson ->
addLesson(courseName, subject, lesson)
}
CacheController.saveAdditionalSubjects(context)
}
/**
@ -73,7 +82,7 @@ class TimetableController {
* @param courseName course to which the subject belongs
* @param subject the subjects name
*/
fun removeSubject(courseName: String, subject: String) {
fun removeSubject(courseName: String, subject: String, context: Context) {
// remove subject
subjectMap[courseName]?.remove(subject)
@ -91,6 +100,28 @@ class TimetableController {
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)
}
}

4
app/src/main/java/org/mosad/seil0/projectlaogai/fragments/HomeFragment.kt

@ -33,8 +33,8 @@ import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaMenu
import org.mosad.seil0.projectlaogai.controller.TimetableController
import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.mensaMenu
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
import org.mosad.seil0.projectlaogai.util.Meal
import org.mosad.seil0.projectlaogai.util.TimetableDay
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView

5
app/src/main/java/org/mosad/seil0/projectlaogai/fragments/MensaFragment.kt

@ -33,10 +33,9 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.CacheController
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaMenu
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.mensaMenu
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cShowBuffet
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.util.MensaWeek
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.MealLinearLayout

8
app/src/main/java/org/mosad/seil0/projectlaogai/fragments/SettingsFragment.kt

@ -47,14 +47,14 @@ 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.CacheController
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.coursesList
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
import org.mosad.seil0.projectlaogai.controller.cache.CacheController.Companion.coursesList
import org.mosad.seil0.projectlaogai.controller.PreferencesController
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorAccent
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorPrimary
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cShowBuffet
import org.mosad.seil0.projectlaogai.controller.TimetableController
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
import org.mosad.seil0.projectlaogai.util.DataTypes
import java.util.*
@ -168,7 +168,7 @@ class SettingsFragment : Fragment() {
listItemsMultiChoice(items = lessons) { _, _, items ->
items.forEach {
val list = it.split(" - ")
TimetableController.removeSubject(list[0], list[1])
TimetableController.removeSubject(list[0], list[1], context)
// TODO save
}
}

34
app/src/main/java/org/mosad/seil0/projectlaogai/fragments/TimeTableFragment.kt

@ -35,11 +35,9 @@ 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.CacheController
import org.mosad.seil0.projectlaogai.controller.PreferencesController
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
import org.mosad.seil0.projectlaogai.controller.TimetableController
import org.mosad.seil0.projectlaogai.controller.TimetableController.Companion.timetable
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController.Companion.timetable
import org.mosad.seil0.projectlaogai.uicomponents.AddSubjectDialog
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
@ -82,7 +80,8 @@ class TimeTableFragment : Fragment() {
private fun initActions() = GlobalScope.launch(Dispatchers.Main) {
refreshLayout_Timetable.setOnRefreshListener {
updateTimetableScreen()
runBlocking { TimetableController.update(context!!).joinAll() }
reloadTimetableUI()
}
// show the AddLessonDialog if the ftaBtn is clicked
@ -115,16 +114,14 @@ class TimeTableFragment : Fragment() {
}
private fun addTimetableWeek(dayBegin: Int, dayEnd: Int, week: Int) = GlobalScope.launch(Dispatchers.Main) {
val timetable = timetable[week]
for (dayIndex in dayBegin..dayEnd) {
val dayCardView = DayCardView(context!!)
// some wired calendar magic, calculate the correct date to be shown
// ((timetable week - current week * 7) + (dayIndex - dayIndex of current week)
val daysToAdd = ((TimetableController.timetable[week].weekNumberYear - NotRetardedCalendar.getWeekOfYear())
val daysToAdd = ((timetable[week].weekNumberYear - NotRetardedCalendar.getWeekOfYear())
* 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex()))
dayCardView.addTimetableDay(timetable.days[dayIndex], daysToAdd)
dayCardView.addTimetableDay(timetable[week].days[dayIndex], daysToAdd)
// if there are no lessons don't show the dayCardView
if (dayCardView.getLinLayoutDay().childCount > 1)
@ -133,21 +130,12 @@ class TimeTableFragment : Fragment() {
}
}
private fun updateTimetableScreen() = GlobalScope.launch(Dispatchers.Default) {
// update the cache
val threads = listOf(
CacheController.updateTimetable(cCourse.courseName, 0, context!!),
CacheController.updateTimetable(cCourse.courseName, 1, context!!)
)
threads.joinAll() // blocking since we want the new data
refreshUI()
}
// TODO rename
fun refreshUI() = GlobalScope.launch(Dispatchers.Default) {
/**
* clear linLayout_Timetable, add the updated timetable
*/
fun reloadTimetableUI() = GlobalScope.launch(Dispatchers.Default) {
withContext(Dispatchers.Main) {
// remove all menus from the layout
// remove all lessons from the layout
linLayout_Timetable.removeAllViews()
// add the refreshed timetables

12
app/src/main/java/org/mosad/seil0/projectlaogai/uicomponents/AddSubjectDialog.kt

@ -35,9 +35,9 @@ import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.customview.getCustomView
import kotlinx.coroutines.runBlocking
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.CacheController
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.controller.TimetableController
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
import org.mosad.seil0.projectlaogai.fragments.TimeTableFragment
import org.mosad.seil0.projectlaogai.util.Course
import java.util.stream.Collectors
@ -71,12 +71,8 @@ class AddSubjectDialog(_context: Context) {
println("add lesson \"$selectedCourse: $selectedSubject\"")
println(lessons.toString())
TimetableController.addSubject(selectedCourse, selectedSubject)
// TODO refresh timetable (add a function to show additional lessons)
runBlocking {
ttf.refreshUI()
}
// TODO save
TimetableController.addSubject(selectedCourse, selectedSubject,context)
runBlocking { ttf.reloadTimetableUI() }
}
.negativeButton(R.string.cancel)

BIN
gradle/wrapper/gradle-wrapper.jar vendored

Binary file not shown.

2
gradle/wrapper/gradle-wrapper.properties vendored

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

2
gradlew vendored

@ -82,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@ -129,6 +130,7 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath

1
gradlew.bat vendored

@ -84,6 +84,7 @@ set CMD_LINE_ARGS=%*
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 %CMD_LINE_ARGS%

Loading…
Cancel
Save

Du besuchst diese Seite mit einem veralteten IPv4-Internetzugang. Möglicherweise treten in Zukunft Probleme mit der Erreichbarkeit und Performance auf. Bitte frage deinen Internetanbieter oder Netzwerkadministrator nach IPv6-Unterstützung.
You are visiting this site with an outdated IPv4 internet access. You may experience problems with accessibility and performance in the future. Please ask your ISP or network administrator for IPv6 support.
Weitere Infos | More Information
Klicke zum schließen | Click to close