-
-
Save ImGamez/a8f9d77bf660d7703cc96fee87cdc4b0 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: yellow; icon-glyph: cloud; | |
| // Widget Params | |
| // Don't edit this, those are default values for debugging (location for Cupertino). | |
| // You need to give your locations parameters through the widget params, more info below. | |
| const widgetParams = JSON.parse((args.widgetParameter != null) ? args.widgetParameter : '{ "LAT" : "37.32" , "LON" : "-122.03" , "LOC_NAME" : "Cupertino, US" }') | |
| // WEATHER API PARAMETERS !important | |
| // API KEY, you need an Open Weather API Key | |
| // You can get one for free at: https://home.openweathermap.org/api_keys (account needed). | |
| const API_KEY = "" | |
| // Latitude and Longitude of the location where you get the weather of. | |
| // You can get those from the Open Weather website while searching for a city, etc. | |
| // This values are getted from the widget parameters, the widget parameters is a JSON string that looks like this: | |
| // { "LAT" : "<latitude>" , "LON" : "<longitude>" , "LOC_NAME" : "<name to display>" } | |
| // This to allow multiple instances of the widget with different locations, if you will only use one instance (1 widget), you can "hardcode" the values here. | |
| // Note: To debug the widget you need to place the values here, because when playing the script in-app the widget parameters are null (= crash). | |
| const LAT = widgetParams.LAT | |
| const LON = widgetParams.LON | |
| const LOCATION_NAME = widgetParams.LOC_NAME | |
| // Looking settings | |
| // This are settings to customize the looking of the widgets, because this was made an iPhone SE (2016) screen, I can't test for bigger screens. | |
| // So feel free to modify this to your taste. | |
| // units : string > Defines the unit used to measure the temps, for temperatures in Fahrenheit use "imperial", "metric" for Celcius and "standard" for Kelvin (Default: "metric"). | |
| const units = "metric" | |
| // twelveHours : true|false > Defines if the hours are displayed in a 12h format, use false for 24h format. (Default: true) | |
| const twelveHours = true | |
| // roundedGraph : true|false > true (Use rounded values to draw the graph) | false (Draws the graph using decimal values, this can be used to draw an smoother line). | |
| const roundedGraph = true | |
| // roundedTemp : true|false > true (Displays the temps rounding the values (29.8 = 30 | 29.3 = 29). | |
| const roundedTemp = true | |
| // hoursToShow : number > Number of predicted hours to show, Eg: 3 = a total of 4 hours in the widget (Default: 3 for the small widget and 11 for the medium one). | |
| const hoursToShow = (config.widgetFamily == "small") ? 3 : 11; | |
| // spaceBetweenDays : number > Size of the space between the temps in the graph in pixels. (Default: 60 for the small widget and 44 for the medium one). | |
| const spaceBetweenDays = (config.widgetFamily == "small") ? 60 : 44; | |
| // Widget Size !important. | |
| // Since the widget works "making" an image and displaying it as the widget background, you need to specify the exact size of the widget to | |
| // get an 1:1 display ratio, if you specify an smaller size than the widget itself it will be displayed blurry. | |
| // You can get the size simply taking an screenshot of your widgets on the home screen and measuring them in an image-proccessing software. | |
| // contextSize : number > Height of the widget in screen pixels, this depends on you screen size (for an 4 inch display the small widget is 282 * 282 pixels on the home screen) | |
| const contextSize = 282 | |
| // mediumWidgetWidth : number > Width of the medium widget in pixels, this depends on you screen size (for an 4 inch display the medium widget is 584 pixels long on the home screen) | |
| const mediumWidgetWidth = 584 | |
| // accentColor : Color > Accent color of some elements (Graph lines and the location label). | |
| const accentColor = new Color("#EB6E4E", 1) | |
| // backgroundColor : Color > Background color of the widgets. | |
| const backgroundColor = new Color("#1C1C1E", 1) | |
| // Position and size of the elements on the widget. | |
| // All coordinates make reference to the top-left of the element. | |
| // locationNameCoords : Point > Define the position in pixels of the location label. | |
| const locationNameCoords = new Point(30, 30) | |
| // locationNameFontSize : number > Size in pixels of the font of the location label. | |
| const locationNameFontSize = 24 | |
| // weatherDescriptionCoords : Point > Position of the weather description label in pixels. | |
| const weatherDescriptionCoords = new Point(30, 52) | |
| // weatherDescriptionFontSize : number > Font size of the weather description label. | |
| const weatherDescriptionFontSize = 18 | |
| //footerFontSize : number > Font size of the footer labels (feels like... and last update time). | |
| const footerFontSize = 20 | |
| //feelsLikeCoords : Point > Coordinates of the "feels like" label. | |
| const feelsLikeCoords = new Point(30, 230) | |
| //lastUpdateTimePosAndSize : Rect > Defines the coordinates and size of the last updated time label. | |
| const lastUpdateTimePosAndSize = new Rect((config.widgetFamily == "small") ? 150 : 450, 230, 100, footerFontSize+1) | |
| //From here proceed with caution. | |
| let fm = FileManager.iCloud(); | |
| let cachePath = fm.joinPath(fm.documentsDirectory(), "weatherCache"); | |
| if(!fm.fileExists(cachePath)){ | |
| fm.createDirectory(cachePath) | |
| } | |
| let weatherData; | |
| let usingCachedData = false; | |
| let drawContext = new DrawContext(); | |
| drawContext.size = new Size((config.widgetFamily == "small") ? contextSize : mediumWidgetWidth, contextSize) | |
| drawContext.opaque = false | |
| drawContext.setTextAlignedCenter() | |
| try { | |
| weatherData = await new Request("https://api.openweathermap.org/data/2.5/onecall?lat=" + LAT + "&lon=" + LON + "&exclude=minutely,alerts&units=" + units + "&lang=en&appid=" + API_KEY).loadJSON(); | |
| fm.writeString(fm.joinPath(cachePath, "lastread"+"_"+LAT+"_"+LON), JSON.stringify(weatherData)); | |
| }catch(e){ | |
| console.log("Offline mode") | |
| try{ | |
| await fm.downloadFileFromiCloud(fm.joinPath(cachePath, "lastread"+"_"+LAT+"_"+LON)); | |
| let raw = fm.readString(fm.joinPath(cachePath, "lastread"+"_"+LAT+"_"+LON)); | |
| weatherData = JSON.parse(raw); | |
| usingCachedData = true; | |
| }catch(e2){ | |
| console.log("Error: No offline data cached") | |
| } | |
| } | |
| let widget = new ListWidget(); | |
| widget.setPadding(0, 0, 0, 0); | |
| widget.backgroundColor = backgroundColor; | |
| drawText(LOCATION_NAME, locationNameFontSize, locationNameCoords.x, locationNameCoords.y, accentColor); | |
| drawText(weatherData.current.weather[0].description, weatherDescriptionFontSize, weatherDescriptionCoords.x, weatherDescriptionCoords.y, Color.white()) | |
| let min, max, diff; | |
| for(let i = 0; i<=hoursToShow ;i++){ | |
| let temp = shouldRound(roundedGraph, weatherData.hourly[i].temp); | |
| min = (temp < min || min == undefined ? temp : min) | |
| max = (temp > max || max == undefined ? temp : max) | |
| } | |
| diff = max -min; | |
| for(let i = 0; i<=hoursToShow ;i++){ | |
| let hourData = weatherData.hourly[i]; | |
| let nextHourTemp = shouldRound(roundedGraph, weatherData.hourly[i+1].temp); | |
| let hour = epochToDate(hourData.dt).getHours(); | |
| if(twelveHours) | |
| hour = (hour > 12 ? hour - 12 : (hour == 0 ? "12a" : ((hour == 12) ? "12p" : hour))) | |
| let temp = i==0?weatherData.current.temp : hourData.temp | |
| let delta = (diff>0)?(shouldRound(roundedGraph, temp) - min) / diff:0.5; | |
| let nextDelta = (diff>0)?(nextHourTemp - min) / diff:0.5 | |
| if(i < hoursToShow){ | |
| let hourDay = epochToDate(hourData.dt); | |
| for(let i2 = 0 ; i2 < weatherData.daily.length ; i2++){ | |
| let day = weatherData.daily[i2]; | |
| if(isSameDay(epochToDate(day.dt), epochToDate(hourData.dt))){ | |
| hourDay = day; | |
| break; | |
| } | |
| } | |
| drawLine(spaceBetweenDays * (i) + 50, 175 - (50 * delta),spaceBetweenDays * (i+1) + 50 , 175 - (50 * nextDelta), 4, (hourData.dt > hourDay.sunset || hourData.dt < hourDay.sunrise ? Color.gray() : accentColor)) | |
| } | |
| drawTextC(shouldRound(roundedTemp, temp)+"Β°", 20, spaceBetweenDays*i+30, 135 - (50*delta), 50, 21, Color.white()) | |
| drawImage(await loadImage(i==0?weatherData.current.weather[0].icon:hourData.weather[0].icon), spaceBetweenDays * i + 25, 150 - (50*delta)); | |
| drawTextC((i==0?"Now":hour), 18, spaceBetweenDays*i+25, 200,50, 21, Color.gray()) | |
| previousDelta = delta; | |
| } | |
| drawText("feels like " + Math.round(weatherData.current.feels_like) + "Β°", footerFontSize, feelsLikeCoords.x, feelsLikeCoords.y, Color.gray()) | |
| drawContext.setTextAlignedRight(); | |
| drawTextC(epochToDate(weatherData.current.dt).toLocaleTimeString(), footerFontSize, lastUpdateTimePosAndSize.x, lastUpdateTimePosAndSize.y, lastUpdateTimePosAndSize.width, lastUpdateTimePosAndSize.height, (usingCachedData) ? Color.yellow() : Color.gray()) | |
| if(usingCachedData) | |
| drawText("β οΈ", 32, ((config.widgetFamily == "small") ? contextSize : mediumWidgetWidth)-72,30) | |
| widget.backgroundImage = (drawContext.getImage()) | |
| widget.presentMedium() | |
| async function loadImage(imgName){ | |
| if(fm.fileExists(fm.joinPath(cachePath, imgName))){ | |
| await fm.downloadFileFromiCloud(fm.joinPath(cachePath, imgName)) | |
| return Image.fromData(Data.fromFile(fm.joinPath(cachePath, imgName))) | |
| }else{ | |
| let imgdata = await new Request("https://openweathermap.org/img/wn/"+imgName+".png").load(); | |
| let img = Image.fromData(imgdata); | |
| fm.write(fm.joinPath(cachePath, imgName), imgdata); | |
| return img; | |
| } | |
| } | |
| function epochToDate(epoch){ | |
| return new Date(epoch * 1000) | |
| } | |
| function drawText(text, fontSize, x, y, color = Color.black()){ | |
| drawContext.setFont(Font.boldSystemFont(fontSize)) | |
| drawContext.setTextColor(color) | |
| drawContext.drawText(new String(text).toString(), new Point(x, y)) | |
| } | |
| function drawImage(image, x, y){ | |
| drawContext.drawImageAtPoint(image, new Point(x, y)) | |
| } | |
| function drawTextC(text, fontSize, x, y, w, h, color = Color.black()){ | |
| drawContext.setFont(Font.boldSystemFont(fontSize)) | |
| drawContext.setTextColor(color) | |
| drawContext.drawTextInRect(new String(text).toString(), new Rect(x, y, w, h)) | |
| } | |
| function drawLine(x1, y1, x2, y2, width, color){ | |
| const path = new Path() | |
| path.move(new Point(x1, y1)) | |
| path.addLine(new Point(x2, y2)) | |
| drawContext.addPath(path) | |
| drawContext.setStrokeColor(color) | |
| drawContext.setLineWidth(width) | |
| drawContext.strokePath() | |
| } | |
| function shouldRound(should, value){ | |
| return ((should) ? Math.round(value) : value) | |
| } | |
| function isSameDay(date1, date2){ | |
| return (date1.getYear() == date2.getYear() && date1.getMonth() == date2.getMonth() && date1.getDate() == date2.getDate()) | |
| } | |
| Script.complete() |
I get error
2021-03-18 00:57:27: Error on line 108:29: TypeError: undefined is not an object (evaluating 'weatherData.current.weather')
Also, I've modified the lat/lng with the following code which allows you to get the lat/lng from the device
+ let latLong = {} + try { + latLong = await Location.current() + } catch {} + const LAT = latLong.latitude || widgetParams.LAT + const LON = latLong.longitude || widgetParams.LON - const LAT = widgetParams.LAT - const LON = widgetParams.LON
This is such a clean looking widget I'm loving it on my screen :) I added a section to automatically grab your location using reverseGeocode:
+ const geocode = await Location.reverseGeocode(LAT,LONG) + const LOCATION_NAME = geocode[0].locality || widgetParams.LOC_NAME - const LOCATION_NAME = widgetParams.LOC_NAME
Although sometimes this will throw an error in the widget that says in encountered an object when it expected a string. Simply rerunning the script fixes this so I'm not sure which part of the code is throwing it off... if anyone has any suggestions lmk!
Also, I've modified the lat/lng with the following code which allows you to get the lat/lng from the device
+ let latLong = {} + try { + latLong = await Location.current() + } catch {} + const LAT = latLong.latitude || widgetParams.LAT + const LON = latLong.longitude || widgetParams.LON - const LAT = widgetParams.LAT - const LON = widgetParams.LON
Could you post your whole script please? I tried to make the indicated changes but it's not working.
Thank you!
let latLong = {}
try {
latLong = await Location.current()
} catch {}
const LAT = latLong.latitude || widgetParams.LAT
const LON = latLong.longitude || widgetParams.LON
const geocode = await Location.reverseGeocode(LAT,LON)
const LOCATION_NAME = geocode[0].locality || widgetParams.LOC_NAME
The above replaces all the code between "// Note: To debug the widget you need to..." and "// Looking Settings"
Hi @PH1TCH,
Hmm, great suggestion!, of course is possible, I've never though about it π€, I'm going to look into it later, because I've holding the next update of the widget with a lot of new features for almost 4 months, that I want to realease first π . The script will jump from ~200 to ~700 lines!Cheers! π€π»
Hi @ImGamez, are there any news about this? Is the new version ready yet? Canβt wait π
Hi @PH1TCH,
Hmm, great suggestion!, of course is possible, I've never though about it π€, I'm going to look into it later, because I've holding the next update of the widget with a lot of new features for almost 4 months, that I want to realease first π . The script will jump from ~200 to ~700 lines!
Cheers! π€π»