-
-
Save marcelrebmann/64494e453deaf26cab3c14c9ff711e1e to your computer and use it in GitHub Desktop.
| // 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 | |
| } |
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.
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
Hier gibt es schon ein Beispiel Skript dazu 👍
https://gist.github.com/marco79cgn/b5f291d6242a2c530e56c748f1ae7f2c
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).
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! 🙃



@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!