|
// Variables used by Scriptable. |
|
// These must be at the very top of the file. Do not edit. |
|
// icon-color: blue; icon-glyph: magic; |
|
// Concentric circle V4 |
|
|
|
let today = new Date(); |
|
let dayNumber = Math.ceil((today - new Date(today.getFullYear(),0,1)) / 86400000); |
|
let thisDayDate = today.getDate() |
|
let thisMonth = today.getMonth() |
|
let thisYear = today.getFullYear() |
|
let daysYear = (leapYear(today.getFullYear())) ? 366 : 365; |
|
let daysThisMonth = daysInMonth(thisMonth+1, thisYear) |
|
const dateFormatter = new DateFormatter() |
|
dateFormatter.dateFormat = "MMM" |
|
|
|
const canvSize = 282; |
|
const canvTextSize = 14; |
|
const canvas = new DrawContext(); |
|
canvas.opaque = false |
|
const batteryRemainColor = new Color('A8C686'); //Battery remaining color (green) |
|
const batteryDepletedColor = new Color('E4572E'); //Battery depleted color (red) |
|
const widgetBGColor = new Color('000'); //Widget background color |
|
const circleTextColor = new Color('#fff'); //Widget text color |
|
|
|
const bgCircleColor = new Color('#ccc') // bg circle color, full circle |
|
const monthCircleColor = new Color('669BBC') |
|
const dayCircleColor = new Color('F3A712') |
|
const dayNCircleColor = new Color('bba420') |
|
|
|
const canvWidth = 24; // circle thickness |
|
const canvRadius = 120; // circle radius |
|
|
|
canvas.size = new Size(canvSize, canvSize); |
|
canvas.respectScreenScale = true; |
|
|
|
const batteryLevel = Device.batteryLevel(); |
|
let monthDegree = Math.floor(((thisMonth+1)/12) * 100 * 3.6) |
|
let dayDegree = Math.floor((thisDayDate/daysThisMonth) * 100 * 3.6) |
|
let dayNDegree = Math.floor((dayNumber/daysYear) * 100 * 3.6) |
|
|
|
/* |
|
BEGIN Widget Layout |
|
*/ |
|
|
|
let widget = new ListWidget(); |
|
widget.setPadding(0,5,1,0); |
|
|
|
let batteryDegree = Math.floor(batteryLevel * 100 * 3.6) |
|
makeCircle(0, batteryDepletedColor, batteryRemainColor, batteryDegree, circleTextColor) |
|
|
|
drawMyText( |
|
(Math.floor(batteryLevel * 100)).toString(), |
|
circleTextColor, |
|
258 |
|
) |
|
let monthRadiusOffset = 27 |
|
makeCircle(monthRadiusOffset, bgCircleColor, monthCircleColor, monthDegree, circleTextColor) |
|
|
|
drawMyText( |
|
// dateFormatter.string(today), // Like Jan, Feb, from Scriptable dateFormatter |
|
(thisMonth+1).toString(), |
|
circleTextColor, |
|
232 |
|
) |
|
|
|
let dayRadiusOffset = 54 |
|
makeCircle(dayRadiusOffset, bgCircleColor, dayCircleColor, dayDegree, circleTextColor) |
|
|
|
drawMyText( |
|
thisDayDate.toString(), |
|
circleTextColor, |
|
205 |
|
) |
|
|
|
/* |
|
END Widget Layout |
|
*/ |
|
|
|
function makeCircle (radiusOffset, bgCircleColor, fgCircleColor, degree, txtColor) { |
|
let ctr = new Point(canvSize / 2, canvSize / 2) |
|
// Outer circle, usually full and in the background |
|
CoordOffset = 0 |
|
RadiusOffset = 0 |
|
bgx = ctr.x - (canvRadius - radiusOffset); |
|
bgy = ctr.y - (canvRadius - radiusOffset); |
|
bgd = 2 * (canvRadius - radiusOffset); |
|
bgr = new Rect( |
|
bgx + CoordOffset, |
|
bgy + CoordOffset, |
|
bgd, |
|
bgd |
|
); |
|
|
|
canvas.setStrokeColor(bgCircleColor); |
|
canvas.setLineWidth(canvWidth); |
|
canvas.strokeEllipse(bgr); |
|
|
|
// Inner circle, usually filling and in the foreground |
|
canvas.setFillColor(fgCircleColor); |
|
for (t = 0; t < degree; t++) { |
|
rect_x = ctr.x + (canvRadius - radiusOffset) * sinDeg(t) - canvWidth / 2; |
|
rect_y = ctr.y - (canvRadius - radiusOffset) * cosDeg(t) - canvWidth / 2; |
|
rect_r = new Rect( |
|
rect_x, |
|
rect_y, |
|
canvWidth, |
|
canvWidth |
|
); |
|
canvas.fillEllipse(rect_r); |
|
} |
|
} |
|
|
|
function drawMyText(txt, txtColor, txtOffset) { |
|
const txtRect = new Rect( |
|
canvTextSize / 2 - 10, |
|
txtOffset - (canvTextSize / 2), |
|
canvSize, |
|
canvTextSize |
|
); |
|
canvas.setTextColor(txtColor); |
|
canvas.setFont(Font.boldSystemFont(canvTextSize)); |
|
canvas.setTextAlignedCenter() |
|
canvas.drawTextInRect(txt, txtRect) |
|
} |
|
|
|
function sinDeg(deg) { |
|
return Math.sin((deg * Math.PI) / 180); |
|
} |
|
|
|
function cosDeg(deg) { |
|
return Math.cos((deg * Math.PI) / 180); |
|
} |
|
|
|
function leapYear(year) { |
|
return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); |
|
} |
|
|
|
// Month here is 1-indexed (January is 1, February is 2, etc). This is |
|
// because we're using 0 as the day so that it returns the last day |
|
// of the last month, so you have to add 1 to the month number |
|
// so it returns the correct amount of days |
|
function daysInMonth (month, year) { |
|
return new Date(year, month, 0).getDate(); |
|
} |
|
|
|
const roundedGraph = true |
|
// roundedTemp : true|false > true (Displays the temps rounding the values (29.8 = 30 | 29.3 = 29). |
|
|
|
// 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" : "YOUR_LAT" , "LON" : "YOUR_LONG" , "LOC_NAME" : "YOUR_CITY" }') |
|
|
|
// 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 = "YOUR_API_KEY" |
|
|
|
// Hardcoded Location, type in your latitude/longitude values and location name |
|
var LAT = widgetParams.LAT // 12.34 |
|
var LON = widgetParams.LON // 12.34 |
|
var LOCATION_NAME = widgetParams.LOC_NAME // "Your place" |
|
|
|
// Set up cache. File located in the Scriptable iCloud folder |
|
let fm = FileManager.iCloud(); |
|
|
|
// cache folder name changed to avoid conflict with the original author's script |
|
let cachePath = fm.joinPath(fm.documentsDirectory(), "weatherCacheCC"); |
|
if(!fm.fileExists(cachePath)){ |
|
fm.createDirectory(cachePath) |
|
} |
|
|
|
let weatherData; |
|
let usingCachedData = false; |
|
let units = 'metric' |
|
let locale = 'en' |
|
|
|
try { |
|
weatherData = await new Request("https://api.openweathermap.org/data/2.5/onecall?lat=" + LAT + "&lon=" + LON + "&exclude=minutely,alerts&units=" + units + "&lang=" + locale + "&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") |
|
} |
|
} |
|
|
|
// 'Night' boolean for line graph and SFSymbols |
|
var night = (today.getHours() > 17 || today.getHours() < 7) |
|
|
|
let temp = weatherData.current.temp |
|
const condition = weatherData.current.weather[0].id |
|
|
|
// SFSymbol function |
|
function symbolForCondition(cond){ |
|
let symbols = { |
|
// Thunderstorm |
|
"2": function(){ |
|
return "cloud.bolt.rain.fill" |
|
}, |
|
// Drizzle |
|
"3": function(){ |
|
return "cloud.drizzle.fill" |
|
}, |
|
// Rain |
|
"5": function(){ |
|
return (cond == 511) ? "cloud.sleet.fill" : "cloud.rain.fill" |
|
}, |
|
// Snow |
|
"6": function(){ |
|
return (cond >= 611 && cond <= 613) ? "cloud.snow.fill" : "snow" |
|
}, |
|
// Atmosphere |
|
"7": function(){ |
|
if (cond == 781) { return "tornado" } |
|
if (cond == 701 || cond == 741) { return "cloud.fog.fill" } |
|
return night ? "cloud.fog.fill" : "sun.haze.fill" |
|
}, |
|
// Clear and clouds |
|
"8": function(){ |
|
if (cond == 800) { return night ? "moon.stars.fill" : "sun.max.fill" } |
|
if (cond == 802 || cond == 803) { return night ? "cloud.moon.fill" : "cloud.sun.fill" } |
|
return "cloud.fill" |
|
} |
|
} |
|
// Get first condition digit. |
|
let conditionDigit = Math.floor(cond / 100) |
|
// Style and return the symbol. |
|
let sfs = SFSymbol.named(symbols[conditionDigit]()) |
|
sfs.applyFont(Font.systemFont(25)) |
|
return sfs.image |
|
} |
|
|
|
function shouldRound(should, value){ |
|
return ((should) ? Math.round(value) : value) |
|
} |
|
|
|
// additional script required to make transparent background image |
|
const nobg = importModule('no-background.js') |
|
widget.backgroundImage = await nobg.getSlice('small-top-left') |
|
|
|
// Draw weather icon |
|
let weatherIconXOffset = 27 |
|
let weatherIconYOffset = 20 |
|
let weatherIconCoord = new Point(canvSize/3+weatherIconXOffset, canvSize/3+weatherIconYOffset) |
|
canvas.drawImageAtPoint(symbolForCondition(condition), weatherIconCoord) |
|
|
|
// Draw temperature |
|
let temp2 = shouldRound(roundedGraph, temp) |
|
drawMyText(temp2+'°', Color.white(), 165) |
|
|
|
// Presents the Final Image from Canvas |
|
widget.addImage(canvas.getImage()) |
|
Script.setWidget(widget); |
|
widget.presentSmall(); |
|
Script.complete(); |