-
-
Save shameerahamed/5fcf545625bd52cc2bf878de7c2a18bd 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: orange; icon-glyph: magic; | |
| // Variables used by Scriptable. | |
| // These must be at the very top of the file. Do not edit. | |
| // icon-color: deep-brown icon-glyph: magic | |
| // Variables used by Scriptable. | |
| // These must be at the very top of the file. Do not edit. | |
| // icon-color: red icon-glyph: magic | |
| // Variables used by Scriptable. | |
| // These must be at the very top of the file. Do not edit. | |
| // icon-color: deep-purple icon-glyph: magic | |
| // Variables used by Scriptable. | |
| // These must be at the very top of the file. Do not edit. | |
| // icon-color: light-gray icon-glyph: magic | |
| // To use, add a parameter to the widget with a format of: image.png|padding-top|text-color | |
| // The image should be placed in the iCloud Scriptable folder (case-sensitive). | |
| // The padding-top spacing parameter moves the text down by a set amount. | |
| // The text color parameter should be a hex value. | |
| // For example, to use the image bkg_fall.PNG with a padding of 40 and a text color of red, | |
| // the parameter should be typed as: bkg_fall.png|30|#ff0000|name | |
| // All parameters are required and separated with "|" | |
| // Parameters allow different settings for multiple widget instances. | |
| let backgroundColor = "#ffffff" | |
| let currentDayColor = "#0FB5EE" | |
| let textColor = "#ffffff" | |
| // opacity value for weekends and times | |
| const opacity = 0.7 | |
| var dark = Device.isUsingDarkAppearance() | |
| if (dark) { | |
| backgroundColor = "#1C1C1E" | |
| currentDayColor = "#EB6E4E" | |
| textColor = "#ffffcc" | |
| } | |
| var today = new Date() | |
| var widgetInputRAW = args.widgetParameter | |
| try { | |
| widgetInputRAW.toString() | |
| } catch(e) { | |
| if (config.runsInWidget) | |
| throw new Error("Please long press the widget and add a parameter.") | |
| else | |
| widgetInputRAW = "pics/IMG6.jpg|30|#ffffff|Shameer" | |
| } | |
| var widgetInput = widgetInputRAW.toString() | |
| var inputArr = widgetInput.split("|") | |
| // iCloud file path | |
| var fm = FileManager.iCloud() | |
| var scriptableFilePath = "/var/mobile/Library/Mobile Documents/iCloud~dk~simonbs~Scriptable/Documents/" | |
| // var scriptableFilePath = fm.documentsDirectory() | |
| log(scriptableFilePath) | |
| var removeSpaces1 = inputArr[0].split(" ") // Remove spaces from file name | |
| var removeSpaces2 = removeSpaces1.join('') | |
| var tempPath = removeSpaces2.split(".") | |
| var backgroundImageURLRAW = scriptableFilePath + tempPath[0] | |
| var backgroundImageURL = scriptableFilePath + tempPath[0] + "." | |
| if(Device.isUsingDarkAppearance()) { | |
| log("dark") | |
| removeSpaces2 += "-dark" | |
| } | |
| var backgroundImageURLInput = scriptableFilePath + removeSpaces2 | |
| log(backgroundImageURLInput) | |
| // For users having trouble with extensions | |
| // Uses user-input file path is the file is found | |
| // Checks for common file format extensions if the file is not found | |
| if (fm.fileExists(backgroundImageURLInput) == false) { | |
| var fileTypes = ['png', 'jpg', 'jpeg', 'tiff', 'webp', 'gif'] | |
| fileTypes.forEach(function(item) { | |
| if (fm.fileExists((backgroundImageURL + item.toLowerCase())) == true) { | |
| backgroundImageURL = backgroundImageURLRAW + "." + item.toLowerCase() | |
| } else if (fm.fileExists((backgroundImageURL + item.toUpperCase())) == true) { | |
| backgroundImageURL = backgroundImageURLRAW + "." + item.toUpperCase() | |
| } | |
| }) | |
| } else { | |
| backgroundImageURL = scriptableFilePath + removeSpaces2 | |
| } | |
| var spacing = parseInt(inputArr[1]) | |
| //API_KEY | |
| let API_WEATHER = "" //get your key from openweathermap | |
| let CITY_WEATHER = "1880252" | |
| //Get storage | |
| var base_path = "/var/mobile/Library/Mobile Documents/iCloud~dk~simonbs~Scriptable/Documents/weather/" | |
| var fm = FileManager.iCloud() | |
| // Fetch Image from Url | |
| async function fetchimageurl(url) { | |
| const request = new Request(url) | |
| var res = await request.loadImage() | |
| return res | |
| } | |
| // Get formatted Date | |
| function getformatteddate(){ | |
| var months = ['January','February','March','April','May','June','July','August','September','October','November','December'] | |
| return months[today.getMonth()] + " " + today.getDate() | |
| } | |
| // Long-form days and months | |
| var days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'] | |
| var months = ['January','February','March','April','May','June','July','August','September','October','November','December'] | |
| // Load image from local drive | |
| async function fetchimagelocal(path){ | |
| var finalPath = base_path + path + ".png" | |
| if(fm.fileExists(finalPath)){ | |
| console.log("file exists: " + finalPath) | |
| return finalPath | |
| } else { | |
| //throw new Error("Error file not found: " + path) | |
| if(!fm.fileExists(base_path)){ | |
| console.log("Directry not exist creating one.") | |
| fm.createDirectory(base_path) | |
| } | |
| console.log("Downloading file: " + finalPath) | |
| await downloadimg(path) | |
| if(fm.fileExists(finalPath)){ | |
| console.log("file exists after download: " + finalPath) | |
| return finalPath | |
| } else{ | |
| throw new Error("Error file not found: " + path) | |
| } | |
| } | |
| } | |
| async function downloadimg(path){ | |
| const url = "http://a.animedlweb.ga/weather/weathers25_2.json" | |
| const data = await fetchWeatherData(url) | |
| log(data) | |
| var dataimg = null | |
| var name = null | |
| if(path.includes("bg")){ | |
| dataimg = data.background | |
| name = path.replace("_bg","") | |
| }else{ | |
| dataimg = data.icon | |
| name = path.replace("_ico","") | |
| } | |
| var imgurl=null | |
| switch (name){ | |
| case "01d": | |
| imgurl = dataimg._01d | |
| break | |
| case "01n": | |
| imgurl = dataimg._01n | |
| break | |
| case "02d": | |
| imgurl = dataimg._02d | |
| break | |
| case "02n": | |
| imgurl = dataimg._02n | |
| break | |
| case "03d": | |
| imgurl = dataimg._03d | |
| break | |
| case "03n": | |
| imgurl = dataimg._03n | |
| break | |
| case "04d": | |
| imgurl = dataimg._04d | |
| break | |
| case "04n": | |
| imgurl = dataimg._04n | |
| break | |
| case "09d": | |
| imgurl = dataimg._09d | |
| break | |
| case "09n": | |
| imgurl = dataimg._09n | |
| break | |
| case "10d": | |
| imgurl = dataimg._10d | |
| break | |
| case "10n": | |
| imgurl = dataimg._10n | |
| break | |
| case "11d": | |
| imgurl = dataimg._11d | |
| break | |
| case "11n": | |
| imgurl = dataimg._11n | |
| break | |
| case "13d": | |
| imgurl = dataimg._13d | |
| break | |
| case "13n": | |
| imgurl = dataimg._13n | |
| break | |
| case "50d": | |
| imgurl = dataimg._50d | |
| break | |
| case "50n": | |
| imgurl = dataimg._50n | |
| break | |
| } | |
| const image = await fetchimageurl(imgurl) | |
| console.log("Downloaded Image") | |
| fm.writeImage(base_path+path+".png",image) | |
| } | |
| //get Json weather | |
| async function fetchWeatherData(url) { | |
| const request = new Request(url) | |
| const res = await request.loadJSON() | |
| return res | |
| } | |
| // Get Location | |
| /*Location.setAccuracyToBest() | |
| let curLocation = await Location.current() | |
| console.log(curLocation.latitude) | |
| console.log(curLocation.longitude)*/ | |
| let wetherurl = "http://api.openweathermap.org/data/2.5/weather?id=" + CITY_WEATHER + "&APPID=" + API_WEATHER + "&units=metric" | |
| //"http://api.openweathermap.org/data/2.5/weather?lat=" + curLocation.latitude + "&lon=" + curLocation.longitude + "&appid=" + API_WEATHER + "&units=metric" | |
| //"http://api.openweathermap.org/data/2.5/weather?id=" + CITY_WEATHER + "&APPID=" + API_WEATHER + "&units=metric" | |
| const weatherJSON = await fetchWeatherData(wetherurl) | |
| log(weatherJSON) | |
| const cityName = weatherJSON.name | |
| const weatherarry = weatherJSON.weather | |
| const iconData = weatherarry[0].icon | |
| const weathername = weatherarry[0].main | |
| const curTempObj = weatherJSON.main | |
| const curTemp = curTempObj.temp | |
| const highTemp = curTempObj.temp_max | |
| const lowTemp = curTempObj.temp_min | |
| const feel_like = weatherarry[0].description | |
| //Completed loading weather data | |
| // Greetings arrays per time period. | |
| var greetingsMorning = [ | |
| `Good morning, ${inputArr[3]}.` | |
| ] | |
| var greetingsAfternoon = [ | |
| `Good afternoon, ${inputArr[3]}.` | |
| ] | |
| var greetingsEvening = [ | |
| `Good evening, ${inputArr[3]}.` | |
| ] | |
| var greetingsNight = [ | |
| `Good night, ${inputArr[3]}.` | |
| ] | |
| var greetingsLateNight = [ | |
| `Good night, ${inputArr[3]}.` | |
| ] | |
| // Holiday customization | |
| var holidaysByKey = { | |
| // month,week,day: datetext | |
| "11,4,4": "Happy Thanksgiving!" | |
| } | |
| var holidaysByDate = { | |
| // month,date: greeting | |
| "1,1": "Happy " + (today.getFullYear()).toString() + "!", | |
| "10,31": "Happy Halloween!", | |
| "12,25": "Merry Christmas!" | |
| } | |
| var holidayKey = (today.getMonth() + 1).toString() + "," + (Math.ceil(today.getDate() / 7)).toString() + "," + (today.getDay()).toString() | |
| var holidayKeyDate = (today.getMonth() + 1).toString() + "," + (today.getDate()).toString() | |
| // Date Calculations | |
| var weekday = days[ today.getDay() ] | |
| var month = months[ today.getMonth() ] | |
| var date = today.getDate() | |
| var hour = today.getHours() | |
| // Append ordinal suffix to date | |
| function ordinalSuffix(input) { | |
| if (input % 10 == 1 && date != 11) { | |
| return input.toString() + "st" | |
| } else if (input % 10 == 2 && date != 12) { | |
| return input.toString() + "nd" | |
| } else if (input % 10 == 3 && date != 13) { | |
| return input.toString() + "rd" | |
| } else { | |
| return input.toString() + "th" | |
| } | |
| } | |
| // Generate date string | |
| var datefull = weekday + ", " + month + " " + ordinalSuffix(date) | |
| // Support for multiple greetings per time period | |
| function randomGreeting(greetingArray) { | |
| return Math.floor(Math.random() * greetingArray.length) | |
| } | |
| var greeting = new String("Howdy.") | |
| if (hour < 5 && hour >= 1) { // 1am - 5am | |
| greeting = greetingsLateNight[randomGreeting(greetingsLateNight)] | |
| } else if (hour >= 23 || hour < 1) { // 11pm - 1am | |
| greeting = greetingsNight[randomGreeting(greetingsNight)] | |
| } else if (hour < 12) { // Before noon (5am - 12pm) | |
| greeting = greetingsMorning[randomGreeting(greetingsMorning)] | |
| } else if (hour >= 12 && hour <= 17) { // 12pm - 5pm | |
| greeting = greetingsAfternoon[randomGreeting(greetingsAfternoon)] | |
| } else if (hour > 17 && hour < 23) { // 5pm - 11pm | |
| greeting = greetingsEvening[randomGreeting(greetingsEvening)] | |
| } | |
| // Overwrite greeting if calculated holiday | |
| if (holidaysByKey[holidayKey]) { | |
| greeting = holidaysByKey[holidayKey] | |
| } | |
| // Overwrite all greetings if specific holiday | |
| if (holidaysByDate[holidayKeyDate]) { | |
| greeting = holidaysByDate[holidayKeyDate] | |
| } | |
| // Try/catch for color input parameter | |
| try { | |
| inputArr[2].toString() | |
| } catch(e) { | |
| throw new Error("Please long press the widget and add a parameter.") | |
| } | |
| let themeColor = new Color(inputArr[2].toString()) | |
| /* --------------- */ | |
| /* Assemble Widget */ | |
| /* --------------- */ | |
| async function createWidget() { | |
| let globalStack = new ListWidget() | |
| //Top spacing | |
| globalStack.addSpacer(10) | |
| await helloWidget(globalStack) | |
| await weatherWidget(globalStack) | |
| globalStack.addSpacer(10) | |
| let cStack = globalStack.addStack() | |
| await eventWidget(cStack) | |
| await calendarWidget(cStack) | |
| globalStack.addSpacer(null) | |
| let pStack = globalStack.addStack() | |
| await prayerWidget(pStack) | |
| globalStack.addSpacer(10) | |
| await quotesWidget(globalStack) | |
| // Bottom Spacer | |
| globalStack.addSpacer() | |
| globalStack.setPadding(0, 0, 0, 0) | |
| // Background image | |
| globalStack.backgroundImage = Image.fromFile(backgroundImageURL) | |
| return globalStack | |
| } | |
| async function helloWidget(globalStack) { | |
| let helloStack = globalStack.addStack() | |
| helloStack.layoutHorizontally() | |
| helloStack.addSpacer(0) | |
| // Greeting label | |
| addWidgetTextLine(helloStack, greeting, { | |
| color: textColor, | |
| opacity, | |
| font: Font.boldSystemFont(25), | |
| align: "center", | |
| }) | |
| } | |
| async function weatherWidget(globalStack) { | |
| let hStack = globalStack.addStack() | |
| hStack.layoutHorizontally() | |
| // Centers weather line | |
| hStack.addSpacer(0) | |
| //image | |
| var img = Image.fromFile(await fetchimagelocal(iconData + "_ico")) | |
| //image in stack | |
| let widgetimg = hStack.addImage(img) | |
| widgetimg.imageSize = new Size(20, 20) | |
| widgetimg.centerAlignImage() | |
| //tempeture label in stack | |
| addWidgetTextLine(hStack, '\xa0\xa0'+ Math.round(curTemp).toString()+"\u2103", { | |
| color: textColor, | |
| opacity, | |
| font: Font.regularSystemFont(15), | |
| align: "center", | |
| }) | |
| addWidgetTextLine(hStack, "\xa0\xa0" + feel_like, { | |
| color: textColor, | |
| opacity, | |
| font: Font.regularSystemFont(15), | |
| align: "center", | |
| }) | |
| } | |
| async function eventWidget(globalStack) { | |
| const leftStack = globalStack.addStack() | |
| // space between the two halves | |
| globalStack.addSpacer(null) | |
| leftStack.layoutVertically() | |
| const date = new Date() | |
| // Find future events that aren't all day and aren't canceled | |
| const events = await CalendarEvent.today([]) | |
| log(events) | |
| let futureEvents = [] | |
| // load the reminders | |
| futureEvents = await remindersDue() | |
| for (const event of events) { | |
| if ( | |
| event.startDate.getTime() > date.getTime() && | |
| !event.isAllDay && | |
| !event.title.startsWith("Canceled:") | |
| ) { | |
| futureEvents.push(event) | |
| } | |
| } | |
| // center the whole left part of the widget | |
| leftStack.addSpacer(null) | |
| leftStack.topAlignContent() | |
| // if we have events today else if we don't | |
| if (futureEvents.length !== 0) { | |
| // show the next 3 events at most | |
| const numEvents = futureEvents.length > 4 ? 4 : futureEvents.length | |
| // log(futureEvents) | |
| for (let i = 0; i < numEvents; i += 1) { | |
| formatEvent(leftStack, futureEvents[i], textColor, opacity) | |
| // don't add a spacer after the last event | |
| if (i < numEvents - 1) { | |
| leftStack.addSpacer(8) | |
| } | |
| } | |
| } else { | |
| addWidgetTextLine(leftStack, "No more events today", { | |
| color: textColor, | |
| opacity, | |
| font: Font.regularSystemFont(15), | |
| align: "left", | |
| }) | |
| } | |
| leftStack.url = "x-apple-reminderkit://" | |
| // for centering | |
| leftStack.addSpacer(null) | |
| } | |
| async function remindersDue() { | |
| let dues = [] | |
| let reminders = await Reminder.allIncomplete() | |
| reminders.forEach(r => { | |
| if (r.dueDate != null && r.dueDate <= new Date()) { | |
| let item = { | |
| title : r.title, | |
| dueDate : formatDate(r.dueDate) | |
| } | |
| dues.push(item) | |
| } | |
| }) | |
| return dues | |
| } | |
| async function calendarWidget(globalStack) { | |
| // right half | |
| const rightStack = globalStack.addStack() | |
| rightStack.layoutVertically() | |
| rightStack.url = "calshow:" | |
| const date = new Date() | |
| const dateFormatter = new DateFormatter() | |
| dateFormatter.dateFormat = "EEEE" | |
| dateFormatter.dateFormat = "MMMM" | |
| // Current month line | |
| const monthLine = rightStack.addStack() | |
| monthLine.addSpacer(4) | |
| addWidgetTextLine(monthLine, dateFormatter.string(date), { | |
| color: textColor, | |
| textSize: 12, | |
| font: Font.boldSystemFont(13), | |
| }) | |
| // between the month name and the week calendar | |
| rightStack.addSpacer(5) | |
| const calendarStack = rightStack.addStack() | |
| calendarStack.spacing = 2 | |
| const month = buildMonthVertical() | |
| for (let i = 0; i < month.length;i += 1) { | |
| let weekdayStack = calendarStack.addStack() | |
| weekdayStack.layoutVertically() | |
| for (let j = 0;j < month[i].length;j += 1) { | |
| let dayStack = weekdayStack.addStack() | |
| dayStack.size = new Size(20, 20) | |
| dayStack.centerAlignContent() | |
| if (month[i][j] === date.getDate().toString()) { | |
| log(month[i][j] + ',' + date.getDate().toString()) | |
| const highlightedDate = getHighlightedDate( | |
| date.getDate().toString(), | |
| currentDayColor | |
| ) | |
| dayStack.addImage(highlightedDate) | |
| } else { | |
| addWidgetTextLine(dayStack, `${month[i][j]}`, { | |
| color: textColor, | |
| opacity: i > 4 ? opacity : 1, | |
| font: Font.lightRoundedSystemFont(10), | |
| align: "center", | |
| }) | |
| } | |
| } | |
| } | |
| } | |
| let icons = ["sun.dust.fill", "sunrise.fill", "sun.max.fill", "sun.haze.fill", "sunset.fill", "moon.stars.fill"] | |
| async function prayerWidget(stack) { | |
| let pStack = stack.addStack() | |
| pStack.layoutHorizontally() | |
| stack.addSpacer(null) | |
| let times = await prayerTimes() | |
| for (var i=0;i < times.length; i++) { | |
| if(i == 1) continue; | |
| let symbol = SFSymbol.named(icons[i]) | |
| let sysElem = pStack.addImage(symbol.image) | |
| sysElem.imageSize = new Size(15, 15) | |
| sysElem.tintColor = Color.white() | |
| sysElem.imageOpacity = opacity | |
| let localTime = formatTime24(new Date(times[i])) | |
| addWidgetTextLine(pStack, "\xa0" + localTime + "\xa0", { | |
| color: textColor, | |
| opacity, | |
| font: Font.regularSystemFont(12), | |
| align: "left", | |
| }) | |
| pStack.addSpacer(null) | |
| } | |
| } | |
| async function quotesWidget(stack) { | |
| let qStack = stack.addStack() | |
| qStack.layoutHorizontally() | |
| stack.addSpacer(null) | |
| let quote = await dailyQuotes() | |
| let sysElem = qStack.addImage(SFSymbol.named("book").image) | |
| sysElem.imageSize = new Size(15, 15) | |
| sysElem.tintColor = Color.white() | |
| sysElem.imageOpacity = opacity | |
| addWidgetTextLine(qStack, "\xa0" + quote.text, { | |
| color: textColor, | |
| opacity, | |
| font: Font.regularSystemFont(15), | |
| align: "left", | |
| }) | |
| qStack.addSpacer(null) | |
| } | |
| async function prayerTimes() { | |
| let fm = FileManager.iCloud(); | |
| // cached from https://github.com/ruqqq/prayertimes-database/blob/master/data/SG/1/2020.json | |
| let fp = fm.joinPath(fm.documentsDirectory(), "data/2020.json") | |
| let json = await fm.readString(fp) | |
| // log(json) | |
| json = JSON.parse(json) | |
| let cur = new Date() | |
| let thisMonthData = json[cur.getMonth()] | |
| return thisMonthData[cur.getDate()-1].times | |
| } | |
| async function dailyQuotes() { | |
| let fm = FileManager.iCloud(); | |
| // cached from https://gist.github.com/shameerahamed/9cbc087195d0b9f7febe2c55190830d7/raw/6953393cd13560ce84e429b1c54a251e8e615245/quotes.json | |
| let fp = fm.joinPath(fm.documentsDirectory(), "data/quotes.json") | |
| let json = await fm.readString(fp) | |
| // log(json) | |
| let docs = JSON.parse(json) | |
| let titles = Object.keys(docs) | |
| let title = titles[getRandom(titles.length)] | |
| let obj = docs[title] | |
| let itemsArr = obj?.items | |
| return { | |
| title : title, | |
| subtitle : obj.subtitle, | |
| text : itemsArr[getRandom(itemsArr.length)] | |
| } | |
| } | |
| function getRandom(length) { | |
| return Math.round(Math.random() * length) | |
| } | |
| /** | |
| * Creates an array of arrays, where the inner arrays include the same weekdays | |
| * along with an identifier in 0 position | |
| * [ | |
| * [ 'M', ' ', '7', '14', '21', '28' ], | |
| * [ 'T', '1', '8', '15', '22', '29' ], | |
| * [ 'W', '2', '9', '16', '23', '30' ], | |
| * ... | |
| * ] | |
| * | |
| * @returns {Array<Array<string>>} | |
| */ | |
| function buildMonthVertical() { | |
| const date = new Date() | |
| const firstDayStack = new Date(date.getFullYear(), date.getMonth(), 1) | |
| const lastDayStack = new Date(date.getFullYear(), date.getMonth() + 1, 0) | |
| const month = [["M"], ["T"], ["W"], ["T"], ["F"], ["S"], ["S"]] | |
| let dayStackCounter = 0 | |
| for (let i = 1;i < firstDayStack.getDay();i += 1) { | |
| month[i - 1].push(" ") | |
| dayStackCounter = (dayStackCounter + 1) % 7 | |
| } | |
| for (let date = 1;date <= lastDayStack.getDate();date += 1) { | |
| month[dayStackCounter].push(`${date}`) | |
| dayStackCounter = (dayStackCounter + 1) % 7 | |
| } | |
| const length = month.reduce( | |
| (acc, dayStacks) => (dayStacks.length > acc ? dayStacks.length : acc), | |
| 0 | |
| ) | |
| month.forEach((dayStacks, index) => { | |
| while (dayStacks.length < length) { | |
| month[index].push(" ") | |
| } | |
| }) | |
| return month | |
| } | |
| /** | |
| * Draws a circle with a date on it for highlighting in calendar view | |
| * | |
| * @param {string} date to draw into the circle | |
| * | |
| * @returns {Image} a circle with the date | |
| */ | |
| function getHighlightedDate(date) { | |
| const drawing = new DrawContext() | |
| drawing.respectScreenScale = true | |
| const size = 50 | |
| drawing.size = new Size(size, size) | |
| drawing.opaque = false | |
| drawing.setFillColor(new Color(currentDayColor)) | |
| drawing.fillEllipse(new Rect(1, 1, size - 2, size - 2)) | |
| drawing.setFont(Font.boldSystemFont(25)) | |
| drawing.setTextAlignedCenter() | |
| drawing.setTextColor(new Color("#ffffff")) | |
| drawing.drawTextInRect(date, new Rect(0, 10, size, size)) | |
| const currentDayImg = drawing.getImage() | |
| return currentDayImg | |
| } | |
| /** | |
| * formats the event times into just hours | |
| * | |
| * @param {Date} date | |
| * | |
| * @returns {string} time | |
| */ | |
| function formatTime(date) { | |
| let dateFormatter = new DateFormatter() | |
| dateFormatter.useNoDateStyle() | |
| dateFormatter.useShortTimeStyle() | |
| return dateFormatter.string(date) | |
| } | |
| function formatDate(date) { | |
| let dateFormatter = new DateFormatter() | |
| dateFormatter.useShortDateStyle() | |
| dateFormatter.useNoTimeStyle() | |
| return dateFormatter.string(date) | |
| } | |
| function formatTime24(date) { | |
| let dateFormatter = new DateFormatter() | |
| dateFormatter.dateFormat = "HH:mm" | |
| return dateFormatter.string(date) | |
| } | |
| /** | |
| * Adds a event name along with start and end times to widget stack | |
| * | |
| * @param {WidgetStack} stack - onto which the event is added | |
| * @param {CalendarEvent} event - an event to add on the stack | |
| * @param {number} opacity - text opacity | |
| */ | |
| function formatEvent(stack, event, color, opacity) { | |
| let timeStack = stack.addStack() | |
| timeStack.layoutHorizontally() | |
| addWidgetTextLine(timeStack, event.title, { | |
| color, | |
| font: Font.mediumSystemFont(14), | |
| lineLimit: 1, | |
| align : 'left' | |
| }) | |
| if (typeof event == CalendarEvent) { | |
| // create line for event start and end times | |
| let timeStack = stack.addStack() | |
| const time = `${formatTime(event.startDate)} - ${formatTime(event.endDate)}` | |
| addWidgetTextLine(timeStack, time, { | |
| color, | |
| opacity, | |
| font: Font.regularSystemFont(14), | |
| align : 'right' | |
| }) | |
| } else { //reminder event | |
| addWidgetTextLine(timeStack, "\xa0\xa0" + event.dueDate , { | |
| color, | |
| opacity, | |
| font: Font.regularSystemFont(14), | |
| align : 'right' | |
| }) | |
| } | |
| } | |
| function addWidgetTextLine( | |
| widget, | |
| text, | |
| { | |
| color = "#ffffff", | |
| textSize = 12, | |
| opacity = 1, | |
| align, | |
| font = "", | |
| lineLimit = 0, | |
| } | |
| ) { | |
| let textLine = widget.addText(text) | |
| textLine.textColor = new Color(color) | |
| if (typeof font === "string") { | |
| textLine.font = new Font(font, textSize) | |
| } else { | |
| textLine.font = font | |
| } | |
| console.log(`${text}`) | |
| console.log(`${typeof opacity}`) | |
| textLine.textOpacity = opacity | |
| switch (align) { | |
| case "left": | |
| textLine.leftAlignText() | |
| break | |
| case "center": | |
| textLine.centerAlignText() | |
| break | |
| case "right": | |
| textLine.rightAlignText() | |
| break | |
| default: | |
| textLine.leftAlignText() | |
| break | |
| } | |
| } | |
| function getImageUrl(name) { | |
| let fm = FileManager.iCloud() | |
| let dir = fm.documentsDirectory() | |
| return fm.joinPath(dir, `${name}`) | |
| } | |
| function setWidgetBackground(widget, imageName) { | |
| const imageUrl = getImageUrl(imageName) | |
| console.log(imageUrl) | |
| widget.backgroundImage = Image.fromFile(imageUrl) | |
| } | |
| // Set widget | |
| let widget = await createWidget() | |
| if (config.runsInWidget) { | |
| Script.setWidget(widget) | |
| } else { | |
| widget.presentLarge() | |
| } | |
| Script.complete() |
pjmosquera
commented
Nov 29, 2020

homescreen-widget.js
Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-blue; icon-glyph: book;
// This script shows a random Scriptable API in a widget. The script is meant to be used with a widget configured on the Home Screen.
// You can run the script in the app to preview the widget or you can go to the Home Screen, add a new Scriptable widget and configure the widget to run this script.
// You can also try creating a shortcut that runs this script. Running the shortcut will show widget.
let api = await randomAPI()
let widget = await createWidget(api)
if (config.runsInWidget) {
// The script runs inside a widget, so we pass our instance of ListWidget to be shown inside the widget on the Home Screen.
Script.setWidget(widget)
} else {
// The script runs inside the app, so we preview the widget.
widget.presentMedium()
}
// Calling Script.complete() signals to Scriptable that the script have finished running.
// This can speed up the execution, in particular when running the script from Shortcuts or using Siri.
Script.complete()
async function createWidget(api) {
let appIcon = await loadAppIcon()
let title = "Random Scriptable API"
let widget = new ListWidget()
// Add background gradient
let gradient = new LinearGradient()
gradient.locations = [0, 1]
gradient.colors = [
new Color("141414"),
new Color("13233F")
]
widget.backgroundGradient = gradient
// Show app icon and title
let titleStack = widget.addStack()
let appIconElement = titleStack.addImage(appIcon)
appIconElement.imageSize = new Size(15, 15)
appIconElement.cornerRadius = 4
titleStack.addSpacer(4)
let titleElement = titleStack.addText(title)
titleElement.textColor = Color.white()
titleElement.textOpacity = 0.7
titleElement.font = Font.mediumSystemFont(13)
widget.addSpacer(12)
// Show API
let nameElement = widget.addText(api.name)
nameElement.textColor = Color.white()
nameElement.font = Font.boldSystemFont(18)
widget.addSpacer(2)
let descriptionElement = widget.addText(api.description)
descriptionElement.minimumScaleFactor = 0.5
descriptionElement.textColor = Color.white()
descriptionElement.font = Font.systemFont(18)
// UI presented in Siri ans Shortcuta is non-interactive, so we only show the footer when not running the script from Siri.
if (!config.runsWithSiri) {
widget.addSpacer(8)
// Add button to open documentation
let linkSymbol = SFSymbol.named("arrow.up.forward")
let footerStack = widget.addStack()
let linkStack = footerStack.addStack()
linkStack.centerAlignContent()
linkStack.url = api.url
let linkElement = linkStack.addText("Read more")
linkElement.font = Font.mediumSystemFont(13)
linkElement.textColor = Color.blue()
linkStack.addSpacer(3)
let linkSymbolElement = linkStack.addImage(linkSymbol.image)
linkSymbolElement.imageSize = new Size(11, 11)
linkSymbolElement.tintColor = Color.blue()
footerStack.addSpacer()
// Add link to documentation
let docsSymbol = SFSymbol.named("book")
let docsElement = footerStack.addImage(docsSymbol.image)
docsElement.imageSize = new Size(20, 20)
docsElement.tintColor = Color.white()
docsElement.imageOpacity = 0.5
docsElement.url = "https://docs.scriptable.app"
}
return widget
}
async function randomAPI() {
let docs = await loadDocs()
let apiNames = Object.keys(docs)
let num = Math.round(Math.random() * apiNames.length)
let apiName = apiNames[num]
let api = docs[apiName]
return {
name: apiName,
description: api["!doc"],
url: api["!url"]
}
}
async function loadDocs() {
let url = "https://docs.scriptable.app/scriptable.json"
let req = new Request(url)
return await req.loadJSON()
}
async function loadAppIcon() {
let url = "https://is5-ssl.mzstatic.com/image/thumb/Purple124/v4/21/1e/13/211e13de-2e74-4221-f7db-d6d2c53b4323/AppIcon-1x_U007emarketing-0-7-0-85-220.png/540x540sr.jpg"
let req = new Request(url)
return req.loadImage()
}
[// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-blue; icon-glyph: book;
// This script shows a random Scriptable API in a widget. The script is meant to be used with a widget configured on the Home Screen.
// You can run the script in the app to preview the widget or you can go to the Home Screen, add a new Scriptable widget and configure the widget to run this script.
// You can also try creating a shortcut that runs this script. Running the shortcut will show widget.
let api = await randomAPI()
let widget = await createWidget(api)
if (config.runsInWidget) {
// The script runs inside a widget, so we pass our instance of ListWidget to be shown inside the widget on the Home Screen.
Script.setWidget(widget)
} else {
// The script runs inside the app, so we preview the widget.
widget.presentMedium()
}
// Calling Script.complete() signals to Scriptable that the script have finished running.
// This can speed up the execution, in particular when running the script from Shortcuts or using Siri.
Script.complete()
async function createWidget(api) {
let appIcon = await loadAppIcon()
let title = "Random Scriptable API"
let widget = new ListWidget()
// Add background gradient
let gradient = new LinearGradient()
gradient.locations = [0, 1]
gradient.colors = [
new Color("141414"),
new Color("13233F")
]
widget.backgroundGradient = gradient
// Show app icon and title
let titleStack = widget.addStack()
let appIconElement = titleStack.addImage(appIcon)
appIconElement.imageSize = new Size(15, 15)
appIconElement.cornerRadius = 4
titleStack.addSpacer(4)
let titleElement = titleStack.addText(title)
titleElement.textColor = Color.white()
titleElement.textOpacity = 0.7
titleElement.font = Font.mediumSystemFont(13)
widget.addSpacer(12)
// Show API
let nameElement = widget.addText(api.name)
nameElement.textColor = Color.white()
nameElement.font = Font.boldSystemFont(18)
widget.addSpacer(2)
let descriptionElement = widget.addText(api.description)
descriptionElement.minimumScaleFactor = 0.5
descriptionElement.textColor = Color.white()
descriptionElement.font = Font.systemFont(18)
// UI presented in Siri ans Shortcuta is non-interactive, so we only show the footer when not running the script from Siri.
if (!config.runsWithSiri) {
widget.addSpacer(8)
// Add button to open documentation
let linkSymbol = SFSymbol.named("arrow.up.forward")
let footerStack = widget.addStack()
let linkStack = footerStack.addStack()
linkStack.centerAlignContent()
linkStack.url = api.url
let linkElement = linkStack.addText("Read more")
linkElement.font = Font.mediumSystemFont(13)
linkElement.textColor = Color.blue()
linkStack.addSpacer(3)
let linkSymbolElement = linkStack.addImage(linkSymbol.image)
linkSymbolElement.imageSize = new Size(11, 11)
linkSymbolElement.tintColor = Color.blue()
footerStack.addSpacer()
// Add link to documentation
let docsSymbol = SFSymbol.named("book")
let docsElement = footerStack.addImage(docsSymbol.image)
docsElement.imageSize = new Size(20, 20)
docsElement.tintColor = Color.white()
docsElement.imageOpacity = 0.5
docsElement.url = "https://docs.scriptable.app"
}
return widget
}
async function randomAPI() {
let docs = await loadDocs()
let apiNames = Object.keys(docs)
let num = Math.round(Math.random() * apiNames.length)
let apiName = apiNames[num]
let api = docs[apiName]
return {
name: apiName,
description: api["!doc"],
url: api["!url"]
}
}
async function loadDocs() {
let url = "https://docs.scriptable.app/scriptable.json"
let req = new Request(url)
return await req.loadJSON()
}
async function loadAppIcon() {
let url = "https://is5-ssl.mzstatic.com/image/thumb/Purple124/v4/21/1e/13/211e13de-2e74-4221-f7db-d6d2c53b4323/AppIcon-1x_U007emarketing-0-7-0-85-220.png/540x540sr.jpg"
let req = new Request(url)
return req.loadImage()
}](url)
async function randomAPI() {
let docs = await loadDocs()
let apiNames = Object.keys(docs)
// FIXED: Changed Math.round to Math.floor to prevent out-of-bounds errors
let num = Math.floor(Math.random() * apiNames.length)
let apiName = apiNames[num]
let api = docs[apiName]
return {
name: apiName,
description: api["!doc"],
url: api["!url"]
}
}