Skip to content

Instantly share code, notes, and snippets.

@marcelrebmann
Forked from kevinkub/incidence.js
Last active April 18, 2022 14:39
Show Gist options
  • Star 57 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save marcelrebmann/64494e453deaf26cab3c14c9ff711e1e to your computer and use it in GitHub Desktop.
Save marcelrebmann/64494e453deaf26cab3c14c9ff711e1e to your computer and use it in GitHub Desktop.
COVID-19 Inzidenz-Widget für iOS innerhalb Deutschlands 🇩🇪
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-gray; icon-glyph: magic;
// Licence: Robert Koch-Institut (RKI), dl-de/by-2-0
const locationApi = (location) => `https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_Landkreisdaten/FeatureServer/0/query?where=1%3D1&outFields=OBJECTID,cases7_per_100k,cases7_bl_per_100k,cases,GEN,county,BL,last_update&geometry=${location.longitude.toFixed(3)}%2C${location.latitude.toFixed(3)}&geometryType=esriGeometryPoint&inSR=4326&spatialRel=esriSpatialRelWithin&returnGeometry=false&outSR=4326&f=json`
const serverApi = (landkreisId) => `https://cdn.marcelrebmann.de/corona/?id=${landkreisId}`
const VACCINATION_IMG_URL = `https://cdn.marcelrebmann.de/img/vaccine-64.png`
/**
* User specific configuration.
* Change the parameters, as you prefer.
*
* - isInfectionsWidgetCentered:
* Controls the content alignment of the "INF" widgetmode
* true -> The widget content is displayed centered
* false -> The widget content is displayed left aligned
*
* - location_cache_filename:
* This specifies the file where the widget caches the last retrieved location data.
* This is only relevant, if the location is not fixed via widget parameters.
*/
const CONFIG = {
isInfectionsWidgetCentered: true,
location_cache_filename: "corona_location.txt",
data_cache_filename: "corona_widget_data.txt",
vaccination_image_filename: "vaccine-64.png"
}
const WIDGET_MODE = {
INCIDENCE: "INCIDENCE",
INFECTIONS: "INFECTIONS"
}
const WIDGET_SIZE_MEDIUM = "medium"
const INCIDENCE_YELLOW = 35
const INCIDENCE_RED = 50
const INCIDENCE_MAGENTA = 200
const COLOR_MAGENTA = new Color("#db0080")
const COLOR_DARK_BG = new Color("#1c1c1d")
const COLOR_VACCINATION = new Color("#2196f3")
const INCIDENCE_HEADER = `🦠 INZIDENZ`
const INFECTIONS_HEADER = `🦠 INFEKTIONEN`
const BUNDESLAENDER_SHORT = {
'Baden-Württemberg': 'BW',
'Bayern': 'BY',
'Berlin': 'BE',
'Brandenburg': 'BB',
'Bremen': 'HB',
'Hamburg': 'HH',
'Hessen': 'HE',
'Mecklenburg-Vorpommern': 'MV',
'Niedersachsen': 'NI',
'Nordrhein-Westfalen': 'NRW',
'Rheinland-Pfalz': 'RP',
'Saarland': 'SL',
'Sachsen': 'SN',
'Sachsen-Anhalt': 'ST',
'Schleswig-Holstein': 'SH',
'Thüringen': 'TH'
}
class Cache {
static init() {
Cache.fileManager = FileManager.local()
Cache.dataCacheFilePath = Cache.fileManager.joinPath(Cache.fileManager.documentsDirectory(), CONFIG.data_cache_filename)
Cache.locationCacheFilePath = Cache.fileManager.joinPath(Cache.fileManager.documentsDirectory(), CONFIG.location_cache_filename)
Cache.vaccinationImageFilePath = Cache.fileManager.joinPath(Cache.fileManager.documentsDirectory(), CONFIG.vaccination_image_filename)
}
static getLocationHash(location) {
return `${location.latitude.toFixed(3)}-${location.longitude.toFixed(3)}`
}
/**
* Loads cached data for a location (lat-lon)
* @param location
*/
static get(location) {
const locationHash = Cache.getLocationHash(location)
const cacheExists = Cache.fileManager.fileExists(Cache.dataCacheFilePath)
if (!cacheExists) {
return null
}
const fileContents = Cache.fileManager.readString(Cache.dataCacheFilePath)
try {
const cachedData = JSON.parse(fileContents)
return cachedData[locationHash]
} catch {
return null
}
}
/**
* Updates the cached data for a given location (lat-lon)
*/
static update(location, data) {
if (!location || !location.latitude || !location.longitude || !data) {
return;
}
const locationHash = Cache.getLocationHash(location)
const cacheExists = Cache.fileManager.fileExists(Cache.dataCacheFilePath)
let fileContents;
let cachedData = {}
if (cacheExists) {
fileContents = Cache.fileManager.readString(Cache.dataCacheFilePath)
}
if (fileContents) {
cachedData = JSON.parse(fileContents)
}
cachedData[locationHash] = data
Cache.fileManager.writeString(Cache.dataCacheFilePath, JSON.stringify(cachedData))
}
static async loadVaccinationImage() {
if (Cache.fileManager.fileExists(Cache.vaccinationImageFilePath)) {
return Cache.fileManager.readImage(Cache.vaccinationImageFilePath)
}
try {
const image = await new Request(VACCINATION_IMG_URL).loadImage()
Cache.fileManager.writeImage(Cache.vaccinationImageFilePath, image)
return loadedImage
} catch {
console.log("[CACHE] could not load vaccination image")
return;
}
}
}
Cache.init()
class Utils {
static isNumericValue(number) {
return number || number === 0
}
static shortFormatNumber(number) {
if (number < 10000) {
return `${number}`
} else if (number < 1000000) {
return `${number % 1000 >= 100 ? (number / 1000).toFixed(1) : Math.floor(number / 1000)}k`.replace(".", ",")
} else {
return `${number % 1000000 >= 100000 ? (number / 1000000).toFixed(1) : Math.floor(number / 1000000)}M`.replace(".", ",")
}
}
static parseRkiDate(dateString) {
const match = dateString.match(/^([0-9]{2}).([0-9]{2}).([0-9]{4})/);
if (!match || !match.length) {
return null;
}
const day = match[1];
const month = match[2];
const year = match[3];
return new Date(year, `${parseInt(month) - 1}`, day);
}
static getNextUpdate(data) {
if (!data || !data.rki_updated || !data.landkreis || !data.landkreis.cases7_per_100k_trend || !data.vaccination.last_updated) {
return null
}
const last_updated = new Date(data.rki_updated)
const vaccination_last_updated = new Date(data.vaccination.last_updated)
last_updated.setDate(last_updated.getDate() + 1)
last_updated.setHours(0)
vaccination_last_updated.setDate(vaccination_last_updated.getDate() + 1)
vaccination_last_updated.setHours(0)
vaccination_last_updated.setMinutes(0)
vaccination_last_updated.setSeconds(0)
const moreRecent = Math.min(last_updated.getTime(), vaccination_last_updated.getTime())
return moreRecent > Date.now() ? new Date(moreRecent) : null
}
}
class UiHelpers {
static getIncidenceColor(incidence) {
if (incidence >= INCIDENCE_MAGENTA) {
return COLOR_MAGENTA
} else if (incidence >= INCIDENCE_RED) {
return Color.red()
} else if (incidence >= INCIDENCE_YELLOW) {
return Color.orange()
} else {
return Color.green()
}
}
static getInfectionTrend(slope) {
if (slope >= 1) {
return "▲"
} else if (slope >= 0) {
return "▶︎"
} else if (slope < 0) {
return "▼"
} else {
return "-"
}
}
static getTrendColor(slope) {
if (slope > 4) {
return Color.red()
} else if (slope >= 1) {
return Color.orange()
} else if (slope < 0) {
return Color.green()
} else {
return Color.gray()
}
}
static generateLandkreisName(data, customLandkreisName) {
if (customLandkreisName) {
return customLandkreisName
}
return data.landkreis.county.match(/^SK \w+$/) ? `${data.landkreis.GEN} (SK)` : data.landkreis.GEN
}
static generateDataState(data) {
if (!data.rki_updated) {
return `Stand: ${(data.landkreis.last_update || "").substr(0, 10)}`
}
const date = new Date(data.rki_updated)
const day = date.getDate()
const month = date.getMonth() + 1
const year = date.getFullYear()
return `Stand: ${day < 10 ? '0' : ''}${day}.${month < 10 ? '0' : ''}${month}.${year}`
}
static generateFooter(widget, incidence, predictedIncidenceSlope, labelText, isCentered) {
const footer = widget.addStack()
footer.layoutHorizontally()
footer.useDefaultPadding()
footer.centerAlignContent()
if (isCentered) {
footer.addSpacer()
}
const incidenceLabel = footer.addText(Utils.isNumericValue(incidence) ? `${incidence.toFixed(1).replace(".", ",")}` : "-")
incidenceLabel.font = Font.boldSystemFont(12)
incidenceLabel.textColor = UiHelpers.getIncidenceColor(incidence)
const trendIconLabel = footer.addText(` ${UiHelpers.getInfectionTrend(predictedIncidenceSlope)}`)
trendIconLabel.font = Font.systemFont(12)
trendIconLabel.textColor = UiHelpers.getTrendColor(predictedIncidenceSlope)
const label = footer.addText(labelText)
label.font = Font.systemFont(12)
label.textColor = Color.gray()
if (isCentered) {
footer.addSpacer()
}
}
static generateVaccinationInfo(widget, vaccinationImage, vaccinationQuote, vaccinationDelta, lastUpdated, isCentered) {
const vaccinationInfo = widget.addStack()
vaccinationInfo.centerAlignContent()
if (isCentered) {
vaccinationInfo.addSpacer()
}
const vaccPercent = vaccinationInfo.addText(`${Utils.isNumericValue(vaccinationQuote) ? `${vaccinationQuote.toFixed(1).replace(".", ",")}` : "-"}% `)
vaccPercent.font = Font.boldSystemFont(12)
vaccPercent.textColor = COLOR_VACCINATION
if (vaccinationImage) {
const vaccIcon = vaccinationInfo.addImage(vaccinationImage)
vaccIcon.imageSize = new Size(10, 10)
}
const vaccDelta = vaccinationInfo.addText(Utils.isNumericValue(vaccinationDelta) ? ` (+${Utils.shortFormatNumber(vaccinationDelta)})` : "")
vaccDelta.font = Font.systemFont(12)
vaccDelta.textColor = Color.gray()
const dataTime = new Date(lastUpdated)
dataTime.setDate(dataTime.getDate() + 1)
dataTime.setHours(0)
dataTime.setMinutes(0)
dataTime.setSeconds(0)
if (Date.now() > dataTime.getTime()) {
vaccinationInfo.addText(" ")
const icon = SFSymbol.named("exclamationmark.arrow.circlepath")
const outdatedIndicator = vaccinationInfo.addImage(icon.image)
outdatedIndicator.imageSize = new Size(12, 12)
outdatedIndicator.tintColor = Color.gray()
}
if (isCentered) {
vaccinationInfo.addSpacer()
}
}
}
async function loadData(location, isLocationFlexible) {
const cachedData = isLocationFlexible ? undefined : Cache.get(location)
let rkiObjectId;
let rkiData;
if (!cachedData || !cachedData.landkreis) {
rkiData = await new Request(locationApi(location)).loadJSON()
const isRkiDataValid = rkiData && rkiData.features && rkiData.features.length && rkiData.features[0].attributes
if (!isRkiDataValid) {
return null
}
rkiObjectId = rkiData.features[0].attributes.OBJECTID
} else {
rkiObjectId = cachedData.landkreis.OBJECTID
}
const apiData = await new Request(serverApi(rkiObjectId)).loadJSON()
try {
if (!apiData && !cachedData) {
throw "No data - use RKI fallback data without trend"
}
if (!apiData && !!cachedData) {
return cachedData
}
const isCacheUpdateNeeded = !isLocationFlexible && (!cachedData || apiData.rki_updated > (cachedData.rki_updated || 0))
if (isCacheUpdateNeeded) {
Cache.update(location, apiData)
}
return apiData
} catch {
return {
landkreis: {
...rkiData.features[0].attributes,
cases7_per_100k_trend: {},
cases7_bl_per_100k_trend: {}
},
country: {
cases7_de_per_100k_trend: {}
}
}
}
}
async function loadAbsoluteCases() {
const data = await new Request(serverApi(1)).loadJSON()
if (!data) {
return null
}
return data
}
async function loadSavedLocation() {
const doesCachedFileExist = Cache.fileManager.fileExists(Cache.locationCacheFilePath)
if (!doesCachedFileExist) {
return null
}
const fileContents = Cache.fileManager.readString(Cache.locationCacheFilePath)
try {
const savedLoc = JSON.parse(fileContents)
if (!savedLoc || !savedLoc.latitude || !savedLoc.longitude) {
return null
}
return savedLoc
} catch {
return null
}
}
async function loadLocation() {
const lastKnownLocation = await loadSavedLocation()
try {
Location.setAccuracyToThreeKilometers()
const location = await Location.current()
if (!location) {
throw "No data from fetching location"
}
if (!lastKnownLocation || location.latitude !== lastKnownLocation.latitude || location.longitude !== lastKnownLocation.longitude) {
Cache.fileManager.writeString(Cache.locationCacheFilePath, JSON.stringify(location))
}
return location
} catch {
return {
...lastKnownLocation,
isCached: true
}
}
}
const createIncidenceWidget = (widget, data, customLandkreisName, isLocationFlexible, isCached, isMediumSizedWidget, vaccinationImage) => {
const headerStack = widget.addStack()
headerStack.layoutHorizontally()
headerStack.centerAlignContent()
const header = headerStack.addText(INCIDENCE_HEADER)
header.font = Font.mediumSystemFont(13)
if (isLocationFlexible) {
headerStack.addSpacer(isMediumSizedWidget ? 10 : null)
const icon = SFSymbol.named(isCached ? "bolt.horizontal.circle" : "location")
const flexibleLocationIndicator = headerStack.addImage(icon.image)
flexibleLocationIndicator.imageSize = new Size(14, 14)
flexibleLocationIndicator.tintColor = isCached ? Color.gray() : Color.blue()
}
widget.addSpacer()
if (!data) {
widget.addText("Keine Ergebnisse für den aktuellen Ort gefunden.")
return;
}
const mainContent = widget.addStack()
mainContent.layoutHorizontally()
mainContent.useDefaultPadding()
mainContent.centerAlignContent()
const isLandkreisIncidenceToBeShortened = Utils.isNumericValue(data.landkreis.cases7_per_100k) && data.landkreis.cases7_per_100k >= 1000;
const landkreisIncidence = data.landkreis.cases7_per_100k.toFixed(isLandkreisIncidenceToBeShortened ? 0 : 1)
const incidenceLabel = mainContent.addText(Utils.isNumericValue(data.landkreis.cases7_per_100k) ? `${landkreisIncidence.replace(".", ",")}` : "-")
incidenceLabel.font = Font.boldSystemFont(24)
incidenceLabel.textColor = UiHelpers.getIncidenceColor(data.landkreis.cases7_per_100k)
const landkreisTrendIconLabel = mainContent.addText(` ${UiHelpers.getInfectionTrend(data.landkreis.cases7_per_100k_trend.slope)}`)
landkreisTrendIconLabel.font = Font.systemFont(14)
landkreisTrendIconLabel.textColor = UiHelpers.getTrendColor(data.landkreis.cases7_per_100k_trend.slope)
const casesLandkreisIncrease = Utils.isNumericValue(data.landkreis.cases) && Utils.isNumericValue(data.landkreis.cases_previous_day) ? data.landkreis.cases - data.landkreis.cases_previous_day : undefined
const casesLandkreisLabel = mainContent.addText(` (${Utils.isNumericValue(casesLandkreisIncrease) ? `${casesLandkreisIncrease >= 0 ? "+" : ""}${casesLandkreisIncrease.toLocaleString()}` : "-"})`)
casesLandkreisLabel.font = Font.systemFont(data.landkreis.cases7_per_100k >= 100 && Math.abs(casesLandkreisIncrease) >= 100 ? 10 : 14)
casesLandkreisLabel.textColor = Color.gray()
const landkreisNameLabel = widget.addText(UiHelpers.generateLandkreisName(data, customLandkreisName))
landkreisNameLabel.minimumScaleFactor = 0.7
widget.addSpacer()
UiHelpers.generateFooter(widget, data.landkreis.cases7_bl_per_100k, data.landkreis.cases7_bl_per_100k_trend.slope, ` ${BUNDESLAENDER_SHORT[data.landkreis.BL]}`)
UiHelpers.generateVaccinationInfo(
widget,
vaccinationImage,
data.vaccination.state.vacc_quote,
data.vaccination.state.vacc_delta,
data.vaccination.last_updated,
false)
const stateInfo = widget.addText(UiHelpers.generateDataState(data))
stateInfo.font = Font.systemFont(10)
stateInfo.textColor = Color.gray()
}
const createInfectionsWidget = (widget, data, vaccinationImage) => {
const headerLabel = widget.addText(INFECTIONS_HEADER)
headerLabel.font = Font.mediumSystemFont(13)
if (!data) {
widget.addText("Keine Fallzahlen verfügbar.")
return;
}
const countryData = data.country
const infectionsDiff = countryData.new_cases - countryData.new_cases_previous_day
widget.addSpacer()
widget.addSpacer(1)
const casesStack = widget.addStack()
if (CONFIG.isInfectionsWidgetCentered) {
casesStack.addSpacer()
}
const casesLabel = casesStack.addText(`${Utils.isNumericValue(countryData.new_cases) ? countryData.new_cases.toLocaleString() : "-"}`)
casesLabel.font = Font.boldSystemFont(24)
casesLabel.minimumScaleFactor = 0.8
if (CONFIG.isInfectionsWidgetCentered) {
casesStack.addSpacer()
}
const casesDifferenceStack = widget.addStack()
if (CONFIG.isInfectionsWidgetCentered) {
casesDifferenceStack.addSpacer()
}
const casesTrendIcon = casesDifferenceStack.addText(UiHelpers.getInfectionTrend(countryData.new_cases - countryData.new_cases_previous_day))
casesTrendIcon.font = Font.systemFont(14)
casesTrendIcon.textColor = UiHelpers.getTrendColor(infectionsDiff)
const casesDiffLabel = casesDifferenceStack.addText(Utils.isNumericValue(infectionsDiff) ? ` (${infectionsDiff >= 0 ? '+' : ''}${infectionsDiff.toLocaleString()})` : "-")
casesDiffLabel.font = Font.systemFont(14)
casesDiffLabel.textColor = Color.gray()
if (CONFIG.isInfectionsWidgetCentered) {
casesDifferenceStack.addSpacer()
}
widget.addSpacer()
const deTrendSlope = countryData.cases7_de_per_100k_trend ? countryData.cases7_de_per_100k_trend.slope : countryData.cases7_de_per_100k_trend
UiHelpers.generateFooter(widget, countryData.cases7_de_per_100k, deTrendSlope, " DE", CONFIG.isInfectionsWidgetCentered)
UiHelpers.generateVaccinationInfo(
widget,
vaccinationImage,
data.vaccination.country.vacc_quote,
data.vaccination.country.vacc_delta,
data.vaccination.last_updated,
CONFIG.isInfectionsWidgetCentered)
widget.addSpacer(2)
const stateInfo = widget.addStack()
if (CONFIG.isInfectionsWidgetCentered) {
stateInfo.addSpacer()
}
const updateLabel = stateInfo.addText(UiHelpers.generateDataState(data))
updateLabel.font = Font.systemFont(10)
updateLabel.textColor = Color.gray()
if (CONFIG.isInfectionsWidgetCentered) {
stateInfo.addSpacer()
}
}
let widget = await createWidget(config.widgetFamily)
if (!config.runsInWidget) {
await widget.presentMedium()
}
Script.setWidget(widget)
Script.complete()
async function createWidget(size) {
const isMediumSizedWidget = size === WIDGET_SIZE_MEDIUM
let location = {};
let customLandkreisName;
let widgetMode = WIDGET_MODE.INCIDENCE;
const params = args.widgetParameter ? args.widgetParameter.split(",") : undefined
// const params = ["49.89", "10.855"] // BA
// const params = ["48.6406978", "9.1391464"] // Böblingen
const widget = new ListWidget()
widget.backgroundColor = Color.dynamic(Color.white(), COLOR_DARK_BG)
if (!params) {
location = await loadLocation()
if (!location) {
widget.addText("Standort konnte nicht ermittelt werden.")
return widget
}
}
if (params && params[0] === "INF") {
widgetMode = WIDGET_MODE.INFECTIONS
}
if (params && params[0] !== "INF") {
location = {
latitude: parseFloat(params[0]),
longitude: parseFloat(params[1])
}
customLandkreisName = params[2]
}
const isLocationFlexible = !params
const vaccinationImage = await Cache.loadVaccinationImage()
if (isMediumSizedWidget) {
if (widgetMode === WIDGET_MODE.INFECTIONS) {
const infectionData = await loadAbsoluteCases()
createInfectionsWidget(widget, infectionData, vaccinationImage)
if (infectionData) {
widget.refreshAfterDate = Utils.getNextUpdate(infectionData)
}
return widget
}
const data = await loadData(location, isLocationFlexible)
CONFIG.isInfectionsWidgetCentered = false
const main = widget.addStack()
const l = main.addStack()
l.layoutVertically()
createIncidenceWidget(l, data, customLandkreisName, isLocationFlexible, location.isCached, isMediumSizedWidget, vaccinationImage)
main.addSpacer()
main.addSpacer(40)
main.addSpacer()
const r = main.addStack()
r.layoutVertically()
createInfectionsWidget(r, data, vaccinationImage)
if (data) {
widget.refreshAfterDate = Utils.getNextUpdate(data)
}
return widget
}
switch (widgetMode) {
case WIDGET_MODE.INCIDENCE:
const data = await loadData(location, isLocationFlexible)
createIncidenceWidget(widget, data, customLandkreisName, isLocationFlexible, location.isCached, isMediumSizedWidget, vaccinationImage)
if (data) {
widget.refreshAfterDate = Utils.getNextUpdate(data)
}
break;
case WIDGET_MODE.INFECTIONS:
const infectionData = await loadAbsoluteCases()
createInfectionsWidget(widget, infectionData, vaccinationImage)
if (infectionData) {
widget.refreshAfterDate = Utils.getNextUpdate(infectionData)
}
break;
default:
widget.addText("Keine Daten.")
}
return widget
}
@trigger64
Copy link

👍

@kingmath
Copy link

kingmath commented Nov 5, 2020

Kann man ggf. einen Parameter einfügen, welcher die INF Anzeige linksbündig setzt?

Tolle Arbeit! Vielen Dank 😊

@marcelrebmann
Copy link
Author

@kingmath stelle ich im nächsten Update bereit. Danke für dein Feedback 👍

@marcelrebmann
Copy link
Author

Update 07.11.2020

  • Das Infektionen (INF) Widget kann wahlweise zentriert oder linksbündig angezeigt werden.
    Einstellbar über die Konfigurations-Variable CONFIG. isInfectionsWidgetCentered
  • Die Hintergrundfarbe im Dark Mode ist jetzt nicht mehr komplett schwarz, sondern gleicht der anderer Apple-Widgets (beispielsweise dem Kalender-Widget)
  • Stadtkreise werden nun mit (SK) annotiert, um mehr Platz zu schaffen.

Infos und Screenshots, siehe erster Kommentar.

@trigger64
Copy link

@marcelrebmann
Perfekt...danke für Deine Arbeit...👍

@kingmath
Copy link

kingmath commented Nov 7, 2020

Vielen lieben Dank für deine tolle Arbeit!

Ein Vorschlag zur Variable „ isInfectionsWidgetCentered“. Eine Abfrage in Scriptable ob INF oder INFleft genutzt wird. INFleft bewirkt isInfectionsWidgetCentered false. Dies hätte den Charm, dass man bei einem Update den Quellcode nur kopieren und nicht editieren muss. Die Einstellungen kommen dann nur über den Parameter. Nur ein Vorschlag für ein Update. Tolle Arbeit!

@cl4udiu5
Copy link

cl4udiu5 commented Nov 9, 2020

@marcelrebmann
Danke für die tolle Arbeit. Wirklich ein cooles Widget.
Mir würde eine Erweiterung noch gut gefallen.
Und zwar die Neuinfektionen des Bundeslandes, welches ja sowieso schon angezeigt wird.
Vielleicht findest du da ja noch etwas Platz im Widget.

SK wird ja auch schon angezeigt (z.B. bei Stuttgart). Schön wäre auch noch LK für Landkreis (z.B. Neu-Ulm).

@Rehner
Copy link

Rehner commented Nov 14, 2020

Sorry für die wahrscheinlich blöde Frage, aber wo und wie gebe ich dem Script denn den Parameter inf mit um die Infektionszahlen gezeigt zu bekommen?

Edith sagt: Wer schaut der findet. Habe es rausgefunden.

@marcelrebmann
Copy link
Author

marcelrebmann commented Dec 8, 2020

Update 08.12.2020

Ein kleines Update aus gegebenen Anlass:

  • Neue Farbe (dunkles Magenta) bei Inzidenzen über dem 200er Grenzwert.
  • Die Medium-Größe des Scriptable Widgets wird nun auch unterstützt.
    IMG_5A63D940C0D6-1

@cl4udiu5
Copy link

cl4udiu5 commented Dec 8, 2020

B7331B9F-4EF4-45F6-96BA-E7F27DD7F85B

Update 08.12.2020

Ein kleines Update aus gegebenen Anlass:

  • Neue Farbe (dunkles Magenta) bei Inzidenzen über dem 200er Grenzwert.
  • Die Medium-Größe des Scriptable Widgets wird nun auch unterstützt.
    IMG_5A63D940C0D6-1

Danke für das Update.
Ich nutze vorallem den INF Parameter.
Bekomme aber eine Fehlermeldung, wenn ich das Update einspiele.

Edit:
Kann es sein, dass der INF Parameter nur noch in einem Small Widget geht?
Wäre super, wenn das im Medium Widget auch gehen würde wie bisher.
Also nur die Infektionen für DE zentriert anzeigen.

@marcelrebmann
Copy link
Author

@cl4udiu5 An die Möglichkeit mit Medium-Widget + nur Infektionszahlen hatte ich tatsächlich nicht gedacht. Das sollte nun auch wieder möglich sein, habe den Code entsprechend angepasst. Danke für den Hinweis!

@marcelrebmann
Copy link
Author

Update 28.12.2020

Behebt ein Problem, bei dem gelegentlich (oftmals morgens) keine Daten angezeigt wurden - vermutlich aufgrund starker Auslastung der RKI API.

  • Caching der zuletzt abgefragten Daten lokal auf dem Gerät. Falls keine Daten geladen werden können, werden diese angezeigt.
  • Verbesserung der Effizienz durch weniger Aktualisierungen im Hintergrund.

@user858753257
Copy link

Hi @marcelrebmann

wäre es möglich in Zukunft auch die Impfquoten anzuzeigen ?
Diese sind seit heute verfügbar.

https://www.rki.de/DE/Content/Infekt/Impfen/Impfstatus/impfstatus_node.html

@user858753257
Copy link

Hier gibt es schon ein Beispiel Skript dazu 👍

https://gist.github.com/marco79cgn/b5f291d6242a2c530e56c748f1ae7f2c

@marcelrebmann
Copy link
Author

Update 11.01.2021

Impfquoten für Bundesländer und Deutschland werden angezeigt (Datenquelle: RKI, in der Regel werktäglich aktualisiert).

  • Prozentuale Anzeige der Impfungen im jeweiligen Bundesland bzw. in Deutschland (INF-Modus)
  • Zunahme im Vergleich zum Vortag
  • Indikator (Ausrufezeichen mit Pfeil) zur Info, falls nur "veraltete" Impfdaten zur Verfügung stehen (Die Daten werden vom RKI nur werktags aktualisiert).

IMG_9D54F3D9D555-1 IMG_5DF9BB37DC0A-1
IMG_0324

@marcelrebmann
Copy link
Author

WICHTIG: MIGRATION ZU GITHUB REPO
Zur besseren Verwaltung habe ich das Skript in ein GitHub-Repository umgezogen: https://github.com/marcelrebmann/corona-widget-ios. Zukünftige Updates und alles andere werden ab nun dort bereitgestellt! 🙃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment