-
-
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 | |
} |
- Auskommentieren der Zeilen 205, 214, 221 und 238. (
xyz.addSpacer()
)- In Zeile 235
true
durchfalse
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?
@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...
sieht bei mir dann aus wie auf dem mittleren Screenshot...
@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...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.
👍
Kann man ggf. einen Parameter einfügen, welcher die INF Anzeige linksbündig setzt?
Tolle Arbeit! Vielen Dank 😊
@kingmath stelle ich im nächsten Update bereit. Danke für dein Feedback 👍
Update 07.11.2020
- Das Infektionen (INF) Widget kann wahlweise zentriert oder linksbündig angezeigt werden.
Einstellbar über die Konfigurations-VariableCONFIG. 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.
@marcelrebmann
Perfekt...danke für Deine Arbeit...👍
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!
@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).
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.
Update 08.12.2020
Ein kleines Update aus gegebenen Anlass:
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.
@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!
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! 🙃
@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.