Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save marco79cgn/fa9cd9a3423be4500a20a54cb783f4c0 to your computer and use it in GitHub Desktop.
Save marco79cgn/fa9cd9a3423be4500a20a54cb783f4c0 to your computer and use it in GitHub Desktop.
Date & Agenda & Weather Scriptable widget (german localization)
// this Scriptable Widget is coded by Slowlydev (aka r/Sl0wly-edits, r/Slowlydev) and adapted by @marco79
const DEV_MODE = false //for developer only
const DEV_PREVIEW = "medium" //for developer only (this script is specialy made for a medium sized widget)
const API_KEY = "" // enter your openweathermap.com api key
const FORECAST_HOURS = "3"
const UNITS = "metric" //metric for celsius and imperial for Fahrenheit
const CALENDAR_URL = "calshow://" //Apple Calendar App, if your favorite app does have a URL scheme feel free to change it
const WEATHER_URL = "" //there is no URL for the Apple Weather App, if your favorite app does feel free to add it
const widgetBackground = new Color("#D6D6D6") //Widget Background
const stackBackground = new Color("#FFFFFF") //Smaller Container Background
const calendarColor = new Color("#EA3323") //Calendar Color
const stackSize = new Size(0, 65) //0 means its automatic
if (config.runsInWidget || DEV_MODE) {
const date = new Date()
const dateNow = Date.now()
let df_Name = new DateFormatter()
let df_Month = new DateFormatter()
df_Name.dateFormat = "EEEE"
df_Month.dateFormat = "MMMM"
const dayName = df_Name.string(date)
const dayNumber = date.getDate().toString()
const monthName = df_Month.string(date)
// Option 1: uncomment this to use let the script locate you each time (which takes longer and needs more battery)
// let loc = await Location.current()
// let lat = loc["latitude"]
// let lon = loc["longitude"]
// Option 2: hard coded longitude/latitude
let lat = "50.95330938278102"
let lon = "6.915087611808545"
const weatherURL = `https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&exclude=current,minutely,daily,alerts&units=${UNITS}&appid=${API_KEY}`
const weatherRequest = new Request(weatherURL)
const weaterData = await weatherRequest.loadJSON()
const hourlyForecasts = weaterData.hourly
let nextForecasts = []
for (const hourlyForecast of hourlyForecasts) {
if (nextForecasts.length == FORECAST_HOURS) { break }
let dt = removeDigits(dateNow, 3)
if (hourlyForecast.dt > dt) {
nextForecasts.push(hourlyForecast)
}
}
const events = await CalendarEvent.today([])
let futureEvents = []
for (const event of events) {
if (futureEvents.length == 2) { break }
if (event.startDate.getTime() > date.getTime() && !event.isAllDay) {
futureEvents.push(event)
}
}
let widget = new ListWidget()
widget.backgroundColor = widgetBackground
widget.setPadding(10, 10, 10, 10)
//Top Row (Date & Weather)
let topRow = widget.addStack()
topRow.layoutHorizontally()
//Top Row Date
let dateStack = topRow.addStack()
dateStack.layoutHorizontally()
dateStack.centerAlignContent()
dateStack.setPadding(7, 7, 7, 7)
dateStack.backgroundColor = stackBackground
dateStack.cornerRadius = 12
dateStack.size = stackSize
dateStack.addSpacer()
let dayNumberTxt = dateStack.addText(dayNumber + ".")
dayNumberTxt.font = Font.semiboldSystemFont(32)
dayNumberTxt.textColor = Color.black()
dateStack.addSpacer(7)
let dateTextStack = dateStack.addStack()
dateTextStack.layoutVertically()
let monthNameTxt = dateTextStack.addText(monthName.toUpperCase())
monthNameTxt.font = Font.boldSystemFont(10)
monthNameTxt.textColor = Color.black()
let dayNameTxt = dateTextStack.addText(dayName)
dayNameTxt.font = Font.boldSystemFont(12)
dayNameTxt.textColor = calendarColor
dateStack.addSpacer()
topRow.addSpacer()
widget.addSpacer()
//Top Row Weather
let weatherStack = topRow.addStack()
weatherStack.layoutHorizontally()
weatherStack.centerAlignContent()
weatherStack.setPadding(7, 7, 7, 7)
weatherStack.backgroundColor = stackBackground
weatherStack.cornerRadius = 12
weatherStack.size = stackSize
weatherStack.url = WEATHER_URL
for (const nextForecast of nextForecasts) {
const iconURL = "https://openweathermap.org/img/wn/" + nextForecast.weather[0].icon + "@2x.png"
let iconRequest = new Request(iconURL);
let icon = await iconRequest.loadImage();
weatherStack.addSpacer()
//Hour Forecast Stack
let hourStack = weatherStack.addStack()
hourStack.layoutVertically()
let hourTxt = hourStack.addText(formatAMPM(nextForecast.dt))
hourTxt.centerAlignText()
hourTxt.font = Font.systemFont(10)
hourTxt.textColor = Color.black()
hourTxt.textOpacity = 0.5
let weatherIcon = hourStack.addImage(icon)
weatherIcon.centerAlignImage()
weatherIcon.size = new Size(25, 25)
let tempTxt = hourStack.addText(" " + Math.round(nextForecast.temp) + "°")
tempTxt.centerAlignText()
tempTxt.font = Font.systemFont(10)
tempTxt.textColor = Color.black()
}
weatherStack.addSpacer()
//Bottom Row Events
let eventStack = widget.addStack()
eventStack.layoutHorizontally()
eventStack.centerAlignContent()
eventStack.setPadding(7, 7, 7, 7)
eventStack.backgroundColor = stackBackground
eventStack.cornerRadius = 12
eventStack.size = stackSize
let eventInfoStack
const font = Font.lightSystemFont(20)
let calendarSymbol = SFSymbol.named("calendar")
calendarSymbol.applyFont(font)
eventStack.addSpacer(8)
let eventIcon = eventStack.addImage(calendarSymbol.image)
eventIcon.imageSize = new Size(20, 20)
eventIcon.resizable = false
eventIcon.centerAlignImage()
eventStack.addSpacer(14)
eventStack.url = CALENDAR_URL
let eventItemsStack = eventStack.addStack()
eventItemsStack.layoutVertically()
if (futureEvents.length != 0) {
for (let i = 0; i < futureEvents.length; i++) {
let futureEvent = futureEvents[i]
const time = formatTime(futureEvent.startDate) + " - " + formatTime(futureEvent.endDate)
const eventColor = new Color("#" + futureEvent.calendar.color.hex)
eventInfoStack = eventItemsStack.addStack()
eventInfoStack.layoutVertically()
let eventTitle = eventItemsStack.addText(futureEvent.title)
eventTitle.font = Font.semiboldSystemFont(12)
eventTitle.textColor = eventColor
eventTitle.lineLimit = 1
let eventTime = eventItemsStack.addText(time + " Uhr")
eventTime.font = Font.semiboldMonospacedSystemFont(10)
eventTime.textColor = Color.black()
eventTime.textOpacity = 0.5
if (i == 0) {
eventItemsStack.addSpacer(3)
}
}
} else {
let nothingText = eventStack.addText("Heute hast du keine Termine!")
nothingText.font = Font.semiboldMonospacedSystemFont(12)
nothingText.textColor = Color.black()
nothingText.textOpacity = 0.5
}
eventStack.addSpacer()
Script.setWidget(widget)
if (DEV_MODE) {
if (DEV_PREVIEW == "small") { widget.presentSmall() }
if (DEV_PREVIEW == "medium") { widget.presentMedium() }
if (DEV_PREVIEW == "large") { widget.presentLarge() }
}
Script.complete()
}
function removeDigits(x, n) { return (x - (x % Math.pow(10, n))) / Math.pow(10, n) }
function formatAMPM(UNIX_timestamp) {
var date = new Date(UNIX_timestamp * 1000)
var hours = date.getHours()
// Option 1: uncomment this for am/pm time with hours from 0-12
// var ampm = hours >= 12 ? 'PM' : 'AM'
// hours = hours % 12
// hours = hours ? hours : 12
// var strTime = hours.toString() + ampm
// Option 2: german localisation
var strTime = hours.toString() + ":00"
return strTime
}
function formatTime(date) {
let df = new DateFormatter()
df.useNoDateStyle()
df.useShortTimeStyle()
return df.string(date)
}
@laspecas
Copy link

Wird es einen automatischen Wechsel zum Dark Mode geben?

@scripomac
Copy link

@marco79cgn würdest du dir meine Frage von oben nochmal anschauen? mit den Erinnerungen unter der Wetter anzeige?

würde den selben Aufbau wie vom Kalender nehmen, Links wechselbares icon und Rechts max 2 Erinnerungen. und Klickbar zum öffnen der Erinnerungsapp oder die app seiner Wahl :D

ich bin leider gescheitert das irgendwie zu basteln...

Danke und Grüße

@steiale
Copy link

steiale commented Jan 20, 2021

Cooles Widget. Aber ich bekomme immer die Fehlermeldung TypeError: undefined is not an object (evaluating 'weaterData.hourly')
Bin leider kein Entwickler. habt ihr da eine Idee?

@marco79cgn
Copy link
Author

Cooles Widget. Aber ich bekomme immer die Fehlermeldung TypeError: undefined is not an object (evaluating 'weaterData.hourly')
Bin leider kein Entwickler. habt ihr da eine Idee?

Nachdem du einen API Key bei Openweathermap angefordert hast, dauert es i.d.R. noch mehrere Stunden, bis der dann auch wirklich aktiv ist und die API Ergebnisse liefert. Probier es später nochmal.

@steiale
Copy link

steiale commented Jan 21, 2021

Wenn ich das Telefon entsperre bekomme ich oft diese error message: Operation couldn’t be completed. KCLErrorDomain error 1. Und wenn ich aufs Wetter gehe sagt er das die Domain nicht supported ist.

@toby17780
Copy link

Wenn ich das Telefon entsperre bekomme ich oft diese error message: Operation couldn’t be completed. KCLErrorDomain error 1. Und wenn ich aufs Wetter gehe sagt er das die Domain nicht supported ist.

Du hast vermutlich die Location auf Option 1 umgestellt. Die Fehlermeldung besagt, dass er die Location nicht abrufen konnte.
Mit Option 2 (und deinen eigenen Koordinaten) ist der Fehler vermutlich weg.

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