Browse Source

anko code removed, coroutines are now used for asynchronous functions

* constraintlayout 2.0.0-beta3 -> 2.0.0-beta4
* material-components-android 1.0.0 -> 1.1.0
* this is the first release candidate for version 0.5.1
pull/38/head
Jannik 2 years ago
parent
commit
2cb4b72369
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
  1. 12
      app/build.gradle
  2. 1
      app/src/main/java/org/mosad/seil0/projectlaogai/MainActivity.kt
  3. 47
      app/src/main/java/org/mosad/seil0/projectlaogai/controller/CacheController.kt
  4. 34
      app/src/main/java/org/mosad/seil0/projectlaogai/controller/TCoRAPIController.kt
  5. 25
      app/src/main/java/org/mosad/seil0/projectlaogai/fragments/HomeFragment.kt
  6. 34
      app/src/main/java/org/mosad/seil0/projectlaogai/fragments/MensaFragment.kt
  7. 15
      app/src/main/java/org/mosad/seil0/projectlaogai/fragments/SettingsFragment.kt
  8. 28
      app/src/main/java/org/mosad/seil0/projectlaogai/fragments/TimeTableFragment.kt
  9. 4
      app/src/main/res/raw/notices.xml
  10. 5
      fastlane/metadata/android/en-US/changelogs/15.txt

12
app/build.gradle

@ -1,7 +1,5 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
@ -12,8 +10,8 @@ android {
applicationId "org.mosad.seil0.projectlaogai"
minSdkVersion 23
targetSdkVersion 29
versionCode 14
versionName "0.5.0"
versionCode 15
versionName "0.5.190"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "build_time", buildTime()
setProperty("archivesBaseName", "projectlaogai-$versionName")
@ -49,12 +47,12 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.anko:anko-commons:0.10.8'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta3'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'com.google.android.material:material:1.1.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.1.1'

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

@ -52,7 +52,6 @@ import kotlin.system.measureTimeMillis
/**
* TODO save the current fragment to show it when the app is restarted
* TODO since anko is dead, we should use coroutines
*/
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {

47
app/src/main/java/org/mosad/seil0/projectlaogai/controller/CacheController.kt

@ -27,6 +27,10 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonParser
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
import org.mosad.seil0.projectlaogai.hsoparser.Course
import org.mosad.seil0.projectlaogai.hsoparser.MensaMenu
@ -53,7 +57,7 @@ class CacheController(cont: Context) {
// if a) it's monday and the last cache update was on sunday or b) the cache is older than 24hr, update blocking
if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - mensaMenu.meta.updateTime) > 86400) {
println("update mensa blocking")
TCoRAPIController.getMensa(context).get()
GlobalScope.launch(Dispatchers.Default) { TCoRAPIController.getMensa(context).join() }
}
// check if we need to update the timetables before displaying them
@ -61,23 +65,29 @@ class CacheController(cont: Context) {
cal.time = Date(timetables[0].meta.updateTime * 1000)
// if a) it`s monday and the last cache update was not on a sunday or b) the cache is older than 24hr, update blocking
if((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - timetables[0].meta.updateTime) > 86400) {
if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - timetables[0].meta.updateTime) > 86400) {
println("updating timetable after sunday!")
val jobA = TCoRAPIController.getTimetable(cCourse.courseName, 0, context)
val jobB = TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
jobA.get()
jobB.get()
GlobalScope.launch(Dispatchers.Default) {
val threads = listOf(
TCoRAPIController.getTimetable(cCourse.courseName, 0, context),
TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
)
threads.joinAll()
}
TCoRAPIController.getTimetable(cCourse.courseName, 0, context)
TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
}
// check if an update is necessary, not blocking
if(currentTime - PreferencesController.coursesCacheTime > 86400)
if (currentTime - PreferencesController.coursesCacheTime > 86400)
TCoRAPIController.getCoursesList(context)
if(currentTime - PreferencesController.mensaCacheTime > 10800)
if (currentTime - PreferencesController.mensaCacheTime > 10800)
TCoRAPIController.getMensa(context)
if(currentTime - PreferencesController.timetableCacheTime > 10800) {
if (currentTime - PreferencesController.timetableCacheTime > 10800) {
TCoRAPIController.getTimetable(cCourse.courseName, 0, context)
TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
}
@ -99,13 +109,16 @@ class CacheController(cont: Context) {
// make sure the file exists
if (!file.exists())
TCoRAPIController.getCoursesList(context).get()
GlobalScope.launch(Dispatchers.Default) { TCoRAPIController.getCoursesList(context).join() }
val fileReader = FileReader(file)
val bufferedReader = BufferedReader(fileReader)
val coursesObject = JsonParser.parseString(bufferedReader.readLine()).asJsonObject
coursesList = Gson().fromJson(coursesObject.getAsJsonArray("courses"), object : TypeToken<List<Course>>() {}.type)
coursesList = Gson().fromJson(
coursesObject.getAsJsonArray("courses"),
object : TypeToken<List<Course>>() {}.type
)
}
/**
@ -116,9 +129,8 @@ class CacheController(cont: Context) {
val file = File(context.filesDir, "mensa.json")
// make sure the file exists
if (!file.exists()) {
TCoRAPIController.getMensa(context).get()
}
if (!file.exists())
GlobalScope.launch(Dispatchers.Default) { TCoRAPIController.getMensa(context).join() }
val fileReader = FileReader(file)
val bufferedReader = BufferedReader(fileReader)
@ -136,8 +148,11 @@ class CacheController(cont: Context) {
val file = File(context.filesDir, "timetable-$courseName-$week.json")
// make sure the file exists
if (!file.exists())
TCoRAPIController.getTimetable(courseName, week, context).get()
if (!file.exists()) {
GlobalScope.launch(Dispatchers.Default) {
TCoRAPIController.getTimetable(courseName, week, context).join()
}
}
val fileReader = FileReader(file)
val bufferedReader = BufferedReader(fileReader)

34
app/src/main/java/org/mosad/seil0/projectlaogai/controller/TCoRAPIController.kt

@ -24,7 +24,7 @@ package org.mosad.seil0.projectlaogai.controller
import android.content.Context
import android.util.Log
import org.jetbrains.anko.doAsync
import kotlinx.coroutines.*
import org.json.JSONObject
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.coursesCacheTime
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.mensaCacheTime
@ -44,7 +44,7 @@ class TCoRAPIController {
/**
* get the json object from tcor api and write it as file (cache)
*/
fun getCoursesList(context: Context) = doAsync {
fun getCoursesList(context: Context) = GlobalScope.launch {
try {
val url = URL("$tcorBaseURL/courseList")
val file = File(context.filesDir, "courses.json")
@ -53,9 +53,11 @@ class TCoRAPIController {
val coursesObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
// write the json object to a file
val writer = BufferedWriter(FileWriter(file))
writer.write(coursesObject.toString())
writer.close()
withContext(Dispatchers.IO) {
val writer = BufferedWriter(FileWriter(file))
writer.write(coursesObject.toString())
writer.close()
}
// update cache time
coursesCacheTime = System.currentTimeMillis() / 1000
@ -69,7 +71,7 @@ class TCoRAPIController {
/**
* get the json object from tcor api and write it as file (cache)
*/
fun getMensa(context: Context) = doAsync {
fun getMensa(context: Context) = GlobalScope.launch {
try {
val url = URL("$tcorBaseURL/mensamenu")
val file = File(context.filesDir, "mensa.json")
@ -78,9 +80,11 @@ class TCoRAPIController {
val mensaObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
// write the json object to a file
val writer = BufferedWriter(FileWriter(file))
writer.write(mensaObject.toString())
writer.close()
withContext(Dispatchers.IO) {
val writer = BufferedWriter(FileWriter(file))
writer.write(mensaObject.toString())
writer.close()
}
// update cache time
mensaCacheTime = System.currentTimeMillis() / 1000
@ -94,18 +98,20 @@ class TCoRAPIController {
/**
* get the json object from tcor api and write it as file (cache)
*/
fun getTimetable(courseName: String, week: Int, context: Context) = doAsync {
fun getTimetable(courseName: String, week: Int, context: Context) = GlobalScope.launch {
try {
val url = URL("$tcorBaseURL/timetable?courseName=$courseName&week=$week")
val file = File(context.filesDir, "timetable-$courseName-$week.json")
// read data from the API
val mensaObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
val timetableObject = JSONObject(url.readText()) //JSONObject(inReader.readLine())
// write the json object to a file
val writer = BufferedWriter(FileWriter(file))
writer.write(mensaObject.toString())
writer.close()
withContext(Dispatchers.IO) {
val writer = BufferedWriter(FileWriter(file))
writer.write(timetableObject.toString())
writer.close()
}
// update cache time
timetableCacheTime = System.currentTimeMillis() / 1000

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

@ -31,8 +31,10 @@ import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_home.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
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.CacheController.Companion.mensaMenu
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.timetables
@ -54,11 +56,10 @@ class HomeFragment : Fragment() {
private val formatter = SimpleDateFormat("E dd.MM", Locale.getDefault())
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(R.layout.fragment_home, container, false)
addMensaMenu().get()
addTimeTable().get()
addMensaMenu()
addTimeTable()
// Inflate the layout for this fragment
return view
@ -67,13 +68,13 @@ class HomeFragment : Fragment() {
/**
* add the current mensa meal to the home screens
*/
private fun addMensaMenu() = doAsync {
private fun addMensaMenu() = GlobalScope.launch(Dispatchers.Default) {
var dayMeals: ArrayList<Meal>
val cal = Calendar.getInstance()
val mensaCardView = DayCardView(context!!)
uiThread {
withContext(Dispatchers.Main) {
if (isAdded) {
if (cal.get(Calendar.HOUR_OF_DAY) < 15) {
@ -117,9 +118,9 @@ class HomeFragment : Fragment() {
/**
* add the current timetable to the home screen
*/
private fun addTimeTable() = doAsync {
private fun addTimeTable() = GlobalScope.launch(Dispatchers.Default) {
uiThread {
withContext(Dispatchers.Main) {
if (isAdded && timetables.isNotEmpty()) {
try {
@ -140,7 +141,7 @@ class HomeFragment : Fragment() {
* @param startDayIndex the day index you want to start searching
* @return a DayCardView with all lessons added
*/
private fun findNextDay(startDayIndex: Int) : DayCardView {
private fun findNextDay(startDayIndex: Int): DayCardView {
val dayCardView = DayCardView(context!!)
var dayTimetable: TimetableDay? = null
var dayIndexSearch = startDayIndex
@ -151,7 +152,7 @@ class HomeFragment : Fragment() {
dayTimetable = timetables[weekIndexSearch].timetable.days[dayIndex]
// some wired calendar magic, calculate the correct date to be shown ((timetable week - current week * 7) + (dayIndex - dayIndex of current week)
val daysToAdd =(timetables[weekIndexSearch].meta.weekNumberYear - NotRetardedCalendar.getWeekOfYear()) * 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex())
val daysToAdd = (timetables[weekIndexSearch].meta.weekNumberYear - NotRetardedCalendar.getWeekOfYear()) * 7 + (dayIndex - NotRetardedCalendar.getDayOfWeekIndex())
dayCardView.addTimetableDay(dayTimetable, daysToAdd)
// if there are no lessons don't show the dayCardView
@ -179,7 +180,7 @@ class HomeFragment : Fragment() {
noLesson.textSize = 18.0F
noLesson.setTypeface(null, Typeface.BOLD)
noLesson.textAlignment = View.TEXT_ALIGNMENT_CENTER
noLesson.setPadding(7,7,7,7)
noLesson.setPadding(7, 7, 7, 7)
return noLesson
}

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

@ -28,8 +28,10 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_mensa.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
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.CacheController
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.mensaMenu
@ -53,12 +55,14 @@ class MensaFragment : Fragment() {
// init actions
refreshAction()
// add the current week (week starts on sunday)
val dayCurrent = if(NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
addWeek(mensaMenu.currentWeek, dayCurrent).get()
GlobalScope.launch(Dispatchers.Default) {
val dayCurrent = if(NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
addWeek(mensaMenu.currentWeek, dayCurrent).join()
// add the next week
addWeek(mensaMenu.nextWeek, 0)
}
// add the next week
addWeek(mensaMenu.nextWeek, 0)
// TODO should we show a info if there is no more food this & next week?
@ -70,9 +74,9 @@ 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) = doAsync {
private fun addWeek(menusWeek: MensaWeek, dayStart: Int) = GlobalScope.launch(Dispatchers.Default) {
uiThread {
withContext(Dispatchers.Main) {
// only add the days dayStart to Fri since the mensa is closed on Sat/Sun
for (dayIndex in dayStart..4) {
@ -105,8 +109,8 @@ class MensaFragment : Fragment() {
/**
* initialize the refresh action
*/
private fun refreshAction() = doAsync {
uiThread {
private fun refreshAction() = GlobalScope.launch(Dispatchers.Default) {
withContext(Dispatchers.Main) {
// set the refresh listener
refreshLayout_Mensa.setOnRefreshListener {
updateMensaScreen()
@ -118,18 +122,18 @@ class MensaFragment : Fragment() {
/**
* refresh the mensa cache and update the mensa screen
*/
private fun updateMensaScreen() = doAsync {
private fun updateMensaScreen() = GlobalScope.launch(Dispatchers.Default) {
// update the cache
TCoRAPIController.getMensa(context!!).get() // blocking since we want the new data
TCoRAPIController.getMensa(context!!).join() // blocking since we want the new data
CacheController.readMensa(context!!)
uiThread {
withContext(Dispatchers.Main) {
// remove all menus from the layout
linLayout_Mensa.removeAllViews()
// add the refreshed menus
val dayCurrent = if (NotRetardedCalendar.getDayOfWeekIndex() == 6) 0 else NotRetardedCalendar.getDayOfWeekIndex()
addWeek(mensaMenu.currentWeek, dayCurrent).get()
addWeek(mensaMenu.currentWeek, dayCurrent).join()
// add the next week
addWeek(mensaMenu.nextWeek, 0)

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

@ -39,8 +39,7 @@ import com.afollestad.materialdialogs.list.listItems
import com.afollestad.materialdialogs.list.listItemsSingleChoice
import de.psdev.licensesdialog.LicensesDialog
import kotlinx.android.synthetic.main.fragment_settings.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.BuildConfig
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.CacheController
@ -54,7 +53,6 @@ import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.hsoparser.DataTypes
import java.util.*
/**
* The settings controller class
* contains all needed parts to display and the settings screen
@ -245,17 +243,20 @@ class SettingsFragment : Fragment() {
.customView(R.layout.dialog_loading)
dialog.show()
doAsync {
GlobalScope.launch(Dispatchers.Default) {
PreferencesController.saveCourse(context, coursesList[index]) // save the course
// update current & next weeks timetable
TCoRAPIController.getTimetable(cCourse.courseName, 0, context).get() // blocking since we want the new data
TCoRAPIController.getTimetable(cCourse.courseName, 1, context).get() // blocking since we want the new data
val threads = listOf(
TCoRAPIController.getTimetable(cCourse.courseName, 0, context),
TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
)
threads.joinAll() // blocking since we want the new data
CacheController.readTimetable(cCourse.courseName, 0, context)
CacheController.readTimetable(cCourse.courseName, 1, context)
uiThread {
withContext(Dispatchers.Main) {
dialog.dismiss()
txtView_Course.text = cCourse.courseName // update txtView
}

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

@ -31,8 +31,7 @@ import androidx.fragment.app.Fragment
import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.android.synthetic.main.fragment_timetable.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.CacheController
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.timetables
@ -74,9 +73,9 @@ class TimeTableFragment : Fragment() {
/**
* initialize the actions
*/
private fun initActions() = doAsync {
private fun initActions() = GlobalScope.launch(Dispatchers.Default) {
uiThread {
withContext(Dispatchers.Main) {
refreshLayout_Timetable.setOnRefreshListener {
updateTimetableScreen()
}
@ -104,18 +103,18 @@ class TimeTableFragment : Fragment() {
/**
* add the current and next weeks lessons
*/
private fun addInitWeeks() = doAsync {
private fun addInitWeeks() = GlobalScope.launch(Dispatchers.Default) {
val currentDayIndex = NotRetardedCalendar.getDayOfWeekIndex()
addWeek(currentDayIndex, 5, 0).get() // add current week
addWeek(currentDayIndex, 5, 0).join() // add current week
addWeek(0, currentDayIndex - 1, 1) // add next week
}
private fun addWeek(startDayIndex: Int, dayEndIndex: Int, weekIndex: Int) = doAsync {
private fun addWeek(startDayIndex: Int, dayEndIndex: Int, weekIndex: Int) = GlobalScope.launch(Dispatchers.Default) {
val timetable = timetables[weekIndex].timetable
val timetableMeta = timetables[weekIndex].meta
uiThread {
withContext(Dispatchers.Main) {
for (dayIndex in startDayIndex..dayEndIndex) {
val dayCardView = DayCardView(context!!)
@ -132,15 +131,18 @@ class TimeTableFragment : Fragment() {
}
}
private fun updateTimetableScreen() = doAsync {
private fun updateTimetableScreen() = GlobalScope.launch(Dispatchers.Default) {
// update the cache
TCoRAPIController.getTimetable(cCourse.courseName, 0, context!!).get() // blocking since we want the new data
TCoRAPIController.getTimetable(cCourse.courseName, 1, context!!).get() // blocking since we want the new data
val threads = listOf(
TCoRAPIController.getTimetable(cCourse.courseName, 0, context!!),
TCoRAPIController.getTimetable(cCourse.courseName, 1, context!!)
)
threads.joinAll() // blocking since we want the new data
CacheController.readTimetable(cCourse.courseName, 0, context!!)
CacheController.readTimetable(cCourse.courseName, 1, context!!)
uiThread {
withContext(Dispatchers.Main) {
// remove all menus from the layout
linLayout_Timetable.removeAllViews()
@ -148,7 +150,7 @@ class TimeTableFragment : Fragment() {
val dayIndex = NotRetardedCalendar.getDayOfWeekIndex()
// add current week
addWeek(dayIndex, 5, 0).get()
addWeek(dayIndex, 5, 0).join()
// add next week
addWeek(0, dayIndex - 1, 1)

4
app/src/main/res/raw/notices.xml

@ -19,8 +19,8 @@
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>anko</name>
<url>https://github.com/Kotlin/anko</url>
<name>kotlinx.coroutines</name>
<url>https://github.com/Kotlin/kotlinx.coroutines</url>
<copyright>Copyright JetBrains</copyright>
<license>Apache Software License 2.0</license>
</notice>

5
fastlane/metadata/android/en-US/changelogs/15.txt

@ -0,0 +1,5 @@
This release 0.5.1 is called "artistical Apollon".
* new: it's now possible to use App shortcuts for the timetable, mensa and moodle screen
* change: updated some libs, updated kotlin to 1.3.61
* change: the app was updated to use kotlin coroutines instead of anko for asynchronous work loads
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