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
  • 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

Alles klar...danke...👍

@Letschi-eu
Copy link

Hallo Marcel,
erstmal ein Kompliment an deine Arbeit und das du sie uns zur Verfügung stellst.
Ich habe folgendes Problem. Innerhalt von Gebäuden bekomme ich öfters einen Error, dass mein Standort zu ungenau ist. Kann man die Genauigkeit etwas herabsetzen? Aktuell liegt diese ja bei 3 Km, wenn ich das richtig gesehen habe. Da ich oft über Landkreise hinweg arbeite, habe ich den statischen Standort ausgestellt und gehe nach GPS.

Des weiteren nutze ich beide Widget-Funktionen von dir in zwei getrennten Widgets, also Landkreis und komplett Deutschland. Ich fände es schön, wenn es die Funktion gäbe, dass aus zwei 2x2 Widgets ein 2x4 Widget wird und man links den Kreis hat und rechts ganz Deutschland.

Falls das nicht von dir umgesetzt wird, trotzdem danke für deine jetzige Arbeit.

PS: ist es möglich die Schrift (Scriptable) unter dem Widget zu entfernen?

@marcelrebmann
Copy link
Author

@Majo1999
Danke und gerne doch.

Location
Generell ist es natürlich sicherer, wenn du den Standort fix in den Parametern eingibst. Bei der Abfrage des Standorts kann immer mal etwas nicht klappen.
Ich sehe hier zwei mögliche Wege:

  • Erste Lösung:
    Ich habe das Skript um ein Caching des Standortes erweitert -> Probiere das gerne mal.
    Wenn die Standortabfrage nicht klappt, dann wird die zuletzt bekannte Position genommen und angezeigt (falls vorhanden). Die Position wird lokal auf dem Gerät als Datei abgelegt und aktualisiert. (Wichtig: Die Standortfreigabe muss für Scriptable natürlich aktiviert sein)
    Ein blaues Standortsymbol oben rechts im Widget zeigt an, dass der gezeigte Standort aktuell ist. Das graue Symbol signalisiert, dass der gezeigte Standort der zuletzt gespeicherte ist.

  • Zweite Lösung:
    Wenn du mehrere fixe Standorte/Regionen hast, zwischen denen du dich bewegst, kannst du für jeden Standort ein Widget mit statischem Standort erstellen und diese dann in einem Widget-Stapel (Apple nennt das Smart-Stapel) zusammenfassen.

Medium (2x4) Widget
Das 2x4 große Widget ist tatsächlich schon in Planung. In den letzten Updates habe ich dazu den strukturellen Grundstein gelegt.

Schrift unter Widget
Soweit ich weiß, ist es nicht möglich, das "Scriptable" zu modifizieren. iOS nimmt (zumindest am iPhone) immer den Titel der App, die das Widget bereitstellt. Auf dem iPad (iPadOS) haben die Widgets keine Titel unten.

@QWERTZ000
Copy link

@trigger64

  • Auskommentieren der Zeilen 205, 214, 221 und 238. (xyz.addSpacer())
  • In Zeile 235 true durch false ersetzen
    Dann sollte das Widget linksbündig angezeigt werden

Moin,
kann es sein, dass die o.a. Zeilen nicht mehr stimmen? Welche Zeilen muss ich aktuell auskommentieren, wenn ich den Text linksbündig haben möchte?

@trigger64
Copy link

trigger64 commented Oct 31, 2020

@QWERTZ000
Ich hab Dir mal Screenshots angehängt...bei mir sind es die Zeilen 207,211,216,223 und 240...in Zeile 237 „true“ durch „false“ ersetzen...

C0528785-8A5E-44D9-A201-18DD215C3890
A7FEF1D1-C10B-4B40-B113-10AA1DA99E77
D68B05DF-749B-4C83-A8AA-87817E45FC1D

sieht bei mir dann aus wie auf dem mittleren Screenshot...

@QWERTZ000
Copy link

@QWERTZ000
Ich hab Dir mal Screenshots angehängt...bei mir sind es die Zeilen 207,211,216,223 und 240...in Zeile 237 „true“ durch „false“ ersetzen...

C0528785-8A5E-44D9-A201-18DD215C3890
A7FEF1D1-C10B-4B40-B113-10AA1DA99E77
D68B05DF-749B-4C83-A8AA-87817E45FC1D

sieht bei mir dann aus wie auf dem mittleren Screenshot...

Danke für deine schnelle Antwort. Zwar sind es bei andere Zeilen (265, 269, 274, 281, 298 und 295 „true“ durch „false“ ersetzen) aber anhand der Screenschots habe ich es gefunden. Und es scheint zu funktionieren.

@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