iOS-Widgets/VodafoneDE/VodafoneDE.js
Benny Samir Hierl 7496cc815b
description mapping added to VF widget
Signed-off-by: Benny Samir Hierl <bennysamir@posteo.de>
2020-12-30 15:49:27 +01:00

771 lines
26 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: red; icon-glyph: broadcast-tower;
/**************
Version 2.1.0
Changelog:
v2.1.0:
- description mapping added
v2.0.0:
- Disable Dark Modus Support
- Medium & large Widget Support
- switch between used and remaining volume
- use MSISDN for Cache-Name
- show amount for prepaid cards
- show remaining days as progress-bar
- Setup assistent
v1.2.4:
- use color.dynamic
v1.2.3:
- Fix typo (thanks @CuzImStantac)
v1.2.2:
- Fix remaining Day issue
v1.2.1:
- Code '40100' added for CallYa Tariff
v1.2.0:
- Option to set colors by widget parameters removed
- Option to set MeinVodafone credentails via widget parameters added
v1.1.1:
- new CallYa Tariff added: C_DIY_Data_National
v1.1.0:
- Login via MeinVodafone Login added
- Show Remaining Days
- MegaByte Support added for tariffs with <= 1 GB
v1.0.3:
- CallYa Support
- Write more useful information in the log, so that you can add support yourself if necessary
v1.0.2:
- Enhanced logging for CallYa troubeshooting
v1.0.1:
- Better Error handling
- Better logging
- Fallback Widget screen in case of an error
If you have problems or need help, ask for support here: https://github.com/ThisIsBenny/iOS-Widgets/issues
Credits:
- Sillium@GitHub (https://gist.github.com/Sillium/f904fb89444bc8dde12cfc07b8fa8728)
- Chaeimg@Github (https://github.com/chaeimg/battCircle)
**************/
////////////////////////////////////////////////////////////////////////////////
////////////////////////// User-Config /////////////////////////
////////////////////////////////////////////////////////////////////////////////
// How many minutes should the cache be valid
const cacheMinutes = 60
// Set to false if the widget should display always in red
let darkModeSupport = true
// Switch between remaining and used contingent. If you want show the used contingent, then change the value from true to false
let showRemainingContingent = true
// To disable the progressbar for the remaining days, you have to change the value from true to false
let showRemainingDaysAsProgressbar = true
// Please add additional values to these list, in case that your contract/tarif isn't supported by these default values.
let containerList = ['Daten', 'D_EU_DATA', 'C_DIY_Data_National']
let codeList = ['-1', '-5' ,'45500', '40100']
// Please add additional values if you see technical descriptions like D_EU_DATA in your widget
const descriptionMapping = {
'Datenvolumen Inland & EU' : 'Datenvolumen',
'GigaDepot-Datenvolumen Inland & EU' : 'GigaDepot-Datenvolumen',
'D_EU_DATA': 'Datenvolumen'
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////// Dev Settings ////////////////////////
////////////////////////////////////////////////////////////////////////////////
const debug = false
config.widgetFamily = config.widgetFamily || 'small'
////////////////////////////////////////////////////////////////////////////////
////////////////////////// System-Config ///////////////////////
////////////////////////////////////////////////////////////////////////////////
// Input
let widgetInputRAW = args.widgetParameter;
let widgetInput = null;
let user, pass, number, json, cacheUUID
if (widgetInputRAW !== null) {
const parameter = widgetInputRAW.toString().split("|")
if(parameter.length > 1) {
[user, pass, number, json] = parameter;
if (!user || !pass || !number) {
throw new Error("Invalid Widget parameter. Expected format: username|password|phonenumber")
}
if (/^49[\d]{5,}/.test(number) === false) {
throw new Error("Invalid phonenumber format. Expected format: 491721234567")
}
} else {
json = parameter
}
if (json) {
try {
const c = JSON.parse(json)
cacheUUID = c.uuid || null
containerList = c.containerList || containerList
codeList = c.codeList || codeList
darkModeSupport = c.darkModeSupport !== undefined ? c.darkModeSupport : darkModeSupport
showRemainingContingent = c.showRemainingContingent !== undefined ? c.showRemainingContingent : showRemainingContingent
showRemainingDaysAsProgressbar = c.showRemainingDaysAsProgressbar !== undefined ? c.showRemainingDaysAsProgressbar : showRemainingDaysAsProgressbar
} catch (error) {
console.log('Faild to extract JSON-config. Fallback to default config')
}
}
} else if (!config.runsInWidget && config.runsInApp) {
const prompt = new Alert()
prompt.message = 'Möchtest du den Setup Assistant starten?'
prompt.addAction('Ja')
prompt.addCancelAction('Nein')
if (await prompt.presentAlert() === 0) {
await setupAssistant()
}
return Script.complete()
}
// Text sizes
const fontSizeData = 11
const lineNumberData = 1
const minimumScaleFactor = 0.8 // Value between 1.0 and 0.1
// Number of data by Size
const numberOfDisplayedDataBySize = {
small: 1,
medium: 2,
large: 4
}
// Progressbar
const h = 2
let width
if (config.widgetFamily === 'small') {
width = 120
} else {
width = 300
}
// Colors
let backColor = new Color('D32D1F')
let backColor2 = new Color('93291E')
let textColor = new Color('EDEDED')
let strokeColor = new Color('B0B0B0')
let fillColor = new Color('EDEDED')
let strokeColorProgressbar = new Color('EDEDED')
let fillColorProgressbar = new Color('B0B0B0')
if (darkModeSupport) {
backColor = Color.dynamic(backColor, new Color('111111'))
backColor2 = Color.dynamic(backColor2, new Color('222222'))
textColor = Color.dynamic(textColor, new Color('EDEDED'))
strokeColor = Color.dynamic(strokeColor, new Color('111111'))
fillColor = Color.dynamic(fillColor, new Color('EDEDED'))
strokeColorProgressbar = Color.dynamic(strokeColorProgressbar, new Color('EDEDED'))
fillColorProgressbar = Color.dynamic(fillColorProgressbar, new Color('111111'))
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
async function setupAssistant () {
let parameter = ''
const promptLoginType = new Alert()
promptLoginType.message = 'Welche Login-Methode möchtest du verwenden?'
promptLoginType.addAction('Netzwerk-Login')
promptLoginType.addAction('MeinVodafone-Login')
let cookies, msisdn
if (await promptLoginType.presentAlert() === 0) {
const promptWlanNotice = new Alert()
promptWlanNotice.title = 'Hinweis'
promptWlanNotice.message = 'Für diese Login-Methode muss das WLAN deaktiviert sein.'
promptWlanNotice.addAction('WLAN ist deaktiviert')
await promptWlanNotice.presentAlert()
try {
let { cookies: c, msisdn: m } = await getSessionCookiesViaNetworkLogin()
cookies = c
msisdn = m
} catch (error) {
const promptError = new Alert()
promptError.title = 'Login fehlgeschlagen'
promptError.message = 'Der Login ist fehlgeschlagen. Bitte prüfe ob eine Mobilfunk-Verbindung vorhanden ist. Weitere Details findest du in den Logs.'
promptError.addAction('Schließen')
await promptError.present()
throw error
}
} else {
const promptCredentails = new Alert()
promptCredentails.title = 'Zugangsdaten'
promptCredentails.message = 'Bitte gebe deine MeinVodafone-Zugangsdaten und deine Rufnummer ein:'
promptCredentails.addTextField('Benutzernamen')
promptCredentails.addSecureTextField('Passwort')
promptCredentails.addTextField('Rufnummer')
promptCredentails.addAction('Weiter')
await promptCredentails.present()
const user = promptCredentails.textFieldValue(0).trim()
const pass = promptCredentails.textFieldValue(1).trim()
const number = promptCredentails.textFieldValue(2).replace(/^0/, '49').trim()
try {
let { cookies: c } = await getSessionCookiesViaMeinVodafoneLogin(user, pass)
cookies = c
msisdn = number
} catch (error) {
console.error(error)
}
parameter = `${user}|${pass}|${msisdn}`
}
let CookieValues = cookies.map(function (v) {
return v.name + "=" + v.value
})
let req
req = new Request(`https://www.vodafone.de/api/enterprise-resources/core/bss/sub-nil/mobile/payment/service-usages/subscriptions/${msisdn}/unbilled-usage`)
req.headers = {
'x-vf-api': '1499082775305',
'Referer': 'https://www.vodafone.de/meinvodafone/services/',
'Accept': 'application/json',
'Cookies': CookieValues.join(';')
}
let res, data
try {
res = await req.loadJSON()
if(!res['serviceUsageVBO'] || !res['serviceUsageVBO']['usageAccounts'] || !res['serviceUsageVBO']['usageAccounts'][0] || !res['serviceUsageVBO']['usageAccounts'][0]['usageGroup']) {
throw new Error('invalid response: ' + JSON.stringify(res))
}
data = res['serviceUsageVBO']['usageAccounts'][0]['usageGroup']
} catch (error) {
const promptError = new Alert()
promptError.title = 'Laden der Daten fehlgeschlagen'
promptError.message = 'Das Laden der Vertragsdaten ist fehlgeschlagen. Dies kann verschiedene Gründe haben. Bitte prüfen die Logs für weitere Informationen'
promptError.addAction('Schließen')
await promptError.present()
throw error
}
const list = data.map(function (o) {
return o.usage.map(function(i) {
i.container = o.container || null
i.selected = false
return i
}).filter(x => x.code)
}).flat()
const promptBeforeTable = new Alert()
promptBeforeTable.title = 'Hinweis'
promptBeforeTable.message = 'Dein Vertrag wurde analysiert. Bitte wähle im nachfolgenden Dialog die Daten aus, die du im Widget anzeigen möchtest.'
promptBeforeTable.addAction('Weiter')
await promptBeforeTable.present()
const table = new UITable()
table.showSeparators = true
function populateTable() {
table.removeAllRows()
for (i = 0; i < list.length; i++) {
let row = new UITableRow()
row.dismissOnSelect = false
let selectedCell = row.addText((list[i].selected)? "✓" : "")
selectedCell.widthWeight = 5
let textCell = row.addText(list[i].name)
textCell.widthWeight = 70
row.onSelect = (number) => {
list[number].selected = !list[number].selected
populateTable()
table.reload()
}
table.addRow(row)
}
}
populateTable()
await QuickLook.present(table)
const selectedList = list.filter(x => x.selected)
const containerList = [...new Set(selectedList.map(x => x.container))]
const codeList = [...new Set(selectedList.map(x => x.code))]
const options = {
uuid: UUID.string(),
containerList,
codeList
}
const promptDarkMode = new Alert()
promptDarkMode.title = 'Dark Mode Unterstützung'
promptDarkMode.message = 'Möchtest du die Dark Mode Unterstützung aktivieren oder deaktivieren? Wenn die Dark Mode Unterstützung deaktiviert ist, bleibt das Widget immer rot.'
promptDarkMode.addAction('Aktivieren')
promptDarkMode.addDestructiveAction('Deaktivieren')
options.darkModeSupport = await promptDarkMode.present() === 0 ? true : false
const promptRemainingContingent = new Alert()
promptRemainingContingent.title = 'Anzeige Option'
promptRemainingContingent.message = 'Möchtest du das verbleibende oder verwendte Kontigent angezeigt bekommen?'
promptRemainingContingent.addAction('verbleibende Kontigent')
promptRemainingContingent.addAction('verwendte Kontigent')
options.showRemainingContingent = await promptRemainingContingent.present() === 0 ? true : false
const promptRemainingDaysAsProgressbar = new Alert()
promptRemainingDaysAsProgressbar.title = 'Fortschrittsbalken für verbleibenden Tage'
promptRemainingDaysAsProgressbar.message = 'Möchtest du dass ein Fortschrittsbalken für die verbleibenden Tage angezeigt wird?'
promptRemainingDaysAsProgressbar.addAction('Anzeigen')
promptRemainingDaysAsProgressbar.addDestructiveAction('Nicht anzeigen')
options.showRemainingDaysAsProgressbar = await promptRemainingDaysAsProgressbar.present() === 0 ? true : false
parameter += `|${JSON.stringify(options)}`
parameter = parameter.replace(/^\|/, '')
console.log('Config: ' + parameter)
Pasteboard.copy(parameter)
const promptSuccess = new Alert()
promptSuccess.title = 'Setup abgeschlossen'
promptSuccess.message = 'Die für dich passende Konfiguration wurde generiert und in die Zwischenablage kopiert.\nFüge diese nun in das Feld "Parameter" in den Widget Einstellungen ein.'
promptSuccess.addAction('Schließen')
await promptSuccess.present()
}
function creatProgress(total, havegone) {
const context = new DrawContext()
context.size = new Size(width, h)
context.opaque = false
context.respectScreenScale = true
// Background Path
context.setFillColor(fillColorProgressbar)
const path = new Path()
path.addRoundedRect(new Rect(0, 0, width, h), 3, 2)
context.addPath(path)
context.fillPath()
// Progress Path
context.setFillColor(strokeColorProgressbar)
const path1 = new Path()
const path1width = (width * (havegone / total) > width) ? width : width * (havegone / total)
path1.addRoundedRect(new Rect(0, 0, path1width, h), 3, 2)
context.addPath(path1)
context.fillPath()
return context.getImage()
}
function getDiagram(percentage) {
function drawArc(ctr, rad, w, deg) {
bgx = ctr.x - rad
bgy = ctr.y - rad
bgd = 2 * rad
bgr = new Rect(bgx, bgy, bgd, bgd)
canvas.setFillColor(fillColor)
canvas.setStrokeColor(strokeColor)
canvas.setLineWidth(w)
canvas.strokeEllipse(bgr)
for (t = 0; t < deg; t++) {
rect_x = ctr.x + rad * sinDeg(t) - w / 2
rect_y = ctr.y - rad * cosDeg(t) - w / 2
rect_r = new Rect(rect_x, rect_y, w, w)
canvas.fillEllipse(rect_r)
}
}
function sinDeg(deg) {
return Math.sin((deg * Math.PI) / 180)
}
function cosDeg(deg) {
return Math.cos((deg * Math.PI) / 180)
}
const canvas = new DrawContext()
const canvSize = 200
const canvTextSize = 36
const canvWidth = 10
const canvRadius = 80
canvas.opaque = false
canvas.size = new Size(canvSize, canvSize)
canvas.respectScreenScale = true
drawArc(
new Point(canvSize / 2, canvSize / 2),
canvRadius,
canvWidth,
Math.floor(percentage * 3.6)
)
const canvTextRect = new Rect(
0,
100 - canvTextSize / 2,
canvSize,
canvTextSize
)
canvas.setTextAlignedCenter()
canvas.setTextColor(textColor)
canvas.setFont(Font.boldSystemFont(canvTextSize))
canvas.drawTextInRect(`${percentage}%`, canvTextRect)
return canvas.getImage()
}
function getTimeRemaining(endtime) {
const total = Date.parse(endtime) - Date.parse(new Date())
const seconds = Math.floor((total / 1000) % 60)
const minutes = Math.floor((total / 1000 / 60) % 60)
const hours = Math.floor((total / (1000 * 60 * 60)) % 24)
const days = Math.floor(total / (1000 * 60 * 60 * 24))
return {
total,
days,
hours,
minutes,
seconds
}
}
async function getSessionCookiesViaNetworkLogin() {
let req;
req = new Request("https://www.vodafone.de/mint/rest/session/start")
req.method = "POST";
req.headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
req.body = JSON.stringify({
"authMethod": "AAA",
"byPIN": false,
"additionalParameters": {
"deviceType": "Smartphone"
}
})
try {
let res = await req.loadJSON()
return { cookies: req.response.cookies, msisdn: res.msisdn }
} catch (e) {
console.log("Login failed! Please check if Wifi is disabled.")
throw new Error(`Login failed with HTTP-Status-Code ${req.response.statusCode}`)
}
};
async function getSessionCookiesViaMeinVodafoneLogin(u, p) {
let req;
req = new Request("https://www.vodafone.de/mint/rest/session/start")
req.method = "POST";
req.headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
req.body = JSON.stringify({
"clientType": "Portal",
"username": u,
"password": p,
})
try {
let res = await req.loadJSON()
return { cookies: req.response.cookies }
} catch (e) {
console.log("Login failed!")
throw new Error(`Login failed with HTTP-Status-Code ${req.response.statusCode}`)
}
};
async function getUsage(user, pass, number) {
let cookies, msisdn
if (user && pass && number) {
console.log("Login via MeinVodafone")
let { cookies: c } = await getSessionCookiesViaMeinVodafoneLogin(user, pass)
cookies = c
msisdn = number
} else {
console.log("Login via Network")
let { cookies: c, msisdn: m } = await getSessionCookiesViaNetworkLogin()
cookies = c
msisdn = m
}
let CookieValues = cookies.map(function (v) {
return v.name + "=" + v.value
})
let req
req = new Request(`https://www.vodafone.de/api/enterprise-resources/core/bss/sub-nil/mobile/payment/service-usages/subscriptions/${msisdn}/unbilled-usage`)
req.headers = {
'x-vf-api': '1499082775305',
'Referer': 'https://www.vodafone.de/meinvodafone/services/',
'Accept': 'application/json',
'Cookies': CookieValues.join(';')
}
try {
let res = await req.loadJSON()
if(!res['serviceUsageVBO'] || !res['serviceUsageVBO']['usageAccounts'] || !res['serviceUsageVBO']['usageAccounts'][0]) {
if (debug) {
console.log(JSON.stringify(res, null, 2))
}
}
console.log("unbilled-usage loaded")
if (debug) {
console.log(JSON.stringify(res['serviceUsageVBO']['usageAccounts'][0], null, 2))
}
const marketCode = res['serviceUsageVBO']['usageAccounts'][0]['details']['marketCode']
const billDate = res['serviceUsageVBO']['usageAccounts'][0]['details']['billDate']
const amount = res['serviceUsageVBO']['usageAccounts'][0]['details']['amount']
let usage = []
// Get Main Container
let container = res['serviceUsageVBO']['usageAccounts'][0]['usageGroup'].filter(function (v) {
return containerList.includes(v.container)
})
if (container.length === 0) {
const ErrorMsg = "Can't find usageGroup with supported Container: " + containerList.join(', ') + ".";
console.log(ErrorMsg)
const listOfContainerNamesInResponse = res['serviceUsageVBO']['usageAccounts'][0]['usageGroup'].map(function (v) {
return v.container
})
console.log("Please check the following list to find the correct container name for your case and adjust the list of container names at the beginnging: " + listOfContainerNamesInResponse.join(", "))
throw new Error(ErrorMsg)
}
for(let i = 0; i < container.length; i++) {
for (let j = 0; j < container[i]['usage'].length; j++) {
if (codeList.includes(container[i]['usage'][j]['code'])) {
usage.push(container[i]['usage'][j])
}
}
}
if (usage.length === 0) {
const ErrorMsg = "Can't find Usage with supported Codes: " + codeList.join(', ') + ".";
console.log(ErrorMsg)
const listOfCodeInResponse = []
for(let i = 0; i < container.length; i++) {
for (let j = 0; j < container[i]['usage'].length; j++) {
listOfCodeInResponse.push(`Code: "${container[i]['usage'][j].code}" for "${container[i]['usage'][j].description}"`)
}
}
console.log("Please check the following list to find the correct code for your case and adjust the list of codes at the beginnging: " + listOfCodeInResponse.join(", "))
throw new Error(ErrorMsg)
}
let endDate = usage[0].endDate
if (endDate == null) {
endDate = res['serviceUsageVBO']['billDetails']['billCycleEndDate'] || null
}
return {
billDate,
endDate,
amount,
marketCode,
usage
}
} catch (e) {
console.log("Loading usage data failed")
throw e
}
};
var today = new Date()
// Set up the file manager.
const files = FileManager.local()
// Set up cache
const cacheNamePostfix = ((number) ? number.substr(number.length - 4) : 'networkLogin') + ( cacheUUID ? `-${cacheUUID}` : '')
const cachePath = files.joinPath(files.cacheDirectory(), "widget-vodafone-" + cacheNamePostfix)
const cacheExists = files.fileExists(cachePath)
const cacheDate = cacheExists ? files.modificationDate(cachePath) : 0
// Get Data
let data
let lastUpdate
try {
// If cache exists and it's been less than 30 minutes since last request, use cached data.
if (cacheExists && (today.getTime() - cacheDate.getTime()) < (cacheMinutes * 60 * 1000)) {
console.log("Get from Cache")
data = JSON.parse(files.readString(cachePath))
lastUpdate = cacheDate
} else {
console.log("Get from API")
data = await getUsage(user, pass, number)
console.log("Write Data to Cache")
try {
files.writeString(cachePath, JSON.stringify(data))
} catch (e) {
console.log("Creating Cache failed!")
console.log(e)
}
lastUpdate = today
}
} catch (e) {
console.error(e)
if (cacheExists) {
console.log("Get from Cache")
data = JSON.parse(files.readString(cachePath))
lastUpdate = cacheDate
} else {
console.log("No fallback to cache possible. Due to missing cache.")
}
}
// Create Widget
let widget = new ListWidget();
widget.setPadding(10, 10, 10, 10)
if (data !== undefined) {
if(debug) {
console.log(JSON.stringify(data, null, 2))
}
const gradient = new LinearGradient()
gradient.locations = [0, 1]
gradient.colors = [
backColor,
backColor2
]
widget.backgroundGradient = gradient
let firstLineStack = widget.addStack()
let provider = firstLineStack.addText("Vodafone")
provider.font = Font.mediumSystemFont(12)
provider.textColor = textColor
if (data.marketCode === 'MMO') {
widget.addSpacer(2)
const amount = widget.addText(`Guthaben: ${data.amount.replace('.', ',')}`)
amount.font = Font.systemFont(8)
amount.textColor = textColor
}
// Last Update
firstLineStack.addSpacer()
let lastUpdateText = firstLineStack.addDate(lastUpdate)
lastUpdateText.font = Font.systemFont(8)
lastUpdateText.rightAlignText()
lastUpdateText.applyTimeStyle()
lastUpdateText.textColor = Color.lightGray()
widget.addSpacer()
const stack = widget.addStack()
stack.layoutHorizontally()
let i = 0
let row
data.usage.slice(0, numberOfDisplayedDataBySize[config.widgetFamily]).forEach((v) => {
if (++i % 2 == 1) {
row = widget.addStack()
row.layoutHorizontally()
widget.addSpacer(5)
}
column = row.addStack()
column.layoutVertically()
column.centerAlignContent()
const percentage = (100 / v.total * (showRemainingContingent ? v.remaining : v.used)).toFixed(0);
const imageStack = column.addStack()
imageStack.layoutHorizontally()
imageStack.addSpacer()
imageStack.addImage(getDiagram(percentage));
imageStack.addSpacer()
column.addSpacer(2)
// Total Values
let totalValues;
if (v.unitOfMeasure !== 'MB') {
totalValues = `${(showRemainingContingent ? v.remaining : v.used)} ${v.unitOfMeasure} von ${v.total} ${v.unitOfMeasure}`
} else if (parseInt(v.total) < 1000) {
totalValues = `${(showRemainingContingent ? v.remaining : v.used)} MB von ${v.total} MB`
} else {
let GB = ((showRemainingContingent ? v.remaining : v.used) / 1024).toFixed(2)
let totalGB = (v.total / 1024).toFixed(2)
totalValues = `${GB} GB von ${totalGB} GB`
}
textStack = column.addStack()
textStack.layoutHorizontally()
textStack.addSpacer()
let diagramText = textStack.addText(totalValues)
diagramText.font = Font.mediumSystemFont(fontSizeData)
diagramText.minimumScaleFactor = minimumScaleFactor
diagramText.lineLimit = lineNumberData
diagramText.centerAlignText()
diagramText.textColor = textColor
textStack.addSpacer()
nameStack = column.addStack()
nameStack.layoutHorizontally()
nameStack.addSpacer()
let diagramName = nameStack.addText(descriptionMapping[v.name] ? descriptionMapping[v.name] : v.name)
diagramName.font = Font.systemFont(fontSizeData - 1)
diagramName.minimumScaleFactor = minimumScaleFactor
diagramName.lineLimit = 1
diagramName.centerAlignText()
diagramName.textColor = textColor
nameStack.addSpacer()
})
widget.addSpacer()
// Remaining Days
if (data.endDate) {
widget.addSpacer(5)
let remainingDays = getTimeRemaining(data.endDate).days + 2
if(data.billDate && showRemainingDaysAsProgressbar) {
const startDate = new Date(data.billDate)
const endDate = new Date(data.endDate)
const total = (endDate - startDate) / (1000 * 60 * 60 * 24)
const progressBarStack = widget.addStack()
progressBarStack.layoutHorizontally()
progressBarStack.addSpacer()
const progressBar = progressBarStack.addImage(creatProgress(total, total - remainingDays))
progressBar.imageSize = new Size(width, h)
progressBarStack.addSpacer()
widget.addSpacer(4)
}
let remainingDaysText = widget.addText(`${remainingDays} Tage verbleibend`)
remainingDaysText.font = Font.systemFont(8)
remainingDaysText.centerAlignText()
remainingDaysText.textColor = textColor
}
} else {
let fallbackText = widget.addText("Es ist ein Fehler aufgetreten! Bitte prüfen Sie die Logs direkt in der App.")
fallbackText.font = Font.mediumSystemFont(12)
fallbackText.textColor = textColor
}
if (!config.runsInWidget) {
switch (config.widgetFamily) {
case 'small': await widget.presentSmall(); break;
case 'medium': await widget.presentMedium(); break;
case 'large': await widget.presentLarge(); break;
}
} else {
// Tell the system to show the widget.
Script.setWidget(widget)
Script.complete()
}