Created
September 29, 2020 08:04
-
-
Save tubit/a34d1059686f391fba7f99689a6a365f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Variables used by Scriptable. | |
// These must be at the very top of the file. Do not edit. | |
// icon-color: deep-purple; icon-glyph: image; | |
// This widget was created by Max Zeryck @mzeryck | |
/* | |
* Change the widget settings and test out the widget in this section. | |
* =================================================================== | |
*/ | |
/* -- PREVIEW YOUR WIDGET -- */ | |
// Change to true to see a preview of your widget. | |
const testMode = true | |
// Optionally specify the size of your widget preview. | |
const widgetSize = "small" | |
/* -- FORMATTING -- */ | |
// Change to false to show the date only. | |
const showEvents = true | |
// Specify how many events to show. | |
const numberOfEvents = 2 | |
/* -- SPACING -- */ | |
// Can be top, middle, or bottom. | |
const verticalAlignment = "middle" | |
// Can be left, center, or right. | |
const horizontalAlignment = "left" | |
/* -- FONTS AND TEXT -- */ | |
// Use iosfonts.com, or change to "" for the system font. | |
const fontName = "Futura-Medium" | |
// Find colors on htmlcolorcodes.com | |
const fontColor = new Color("#ffffff") | |
// Change the font sizes for each element. | |
const dayOfWeekSize = 13 | |
const dateNumberSize = 34 | |
const eventTitleSize = 14 | |
const eventTimeSize = 12 | |
/* -- RESET YOUR WIDGET -- */ | |
// Change to true to reset the widget background. | |
const resetWidget = false | |
/* | |
* The code below this comment is the widget logic - a bit more complex. | |
* ===================================================================== | |
*/ | |
// Widgets are unique based on the name of the script. | |
const filename = Script.name() + ".jpg" | |
const files = FileManager.local() | |
const path = files.joinPath(files.documentsDirectory(), filename) | |
const fileExists = files.fileExists(path) | |
// Store current datetime | |
const date = new Date() | |
// If we're in the widget or testing, build the widget. | |
if (config.runsInWidget || (testMode && fileExists && !resetWidget)) { | |
let widget = new ListWidget() | |
widget.backgroundImage = files.readImage(path) | |
if (verticalAlignment == "middle" || verticalAlignment == "bottom") { widget.addSpacer() } | |
// Store all of the widget text. | |
let widgetText = [] | |
// Format the date info | |
let df = new DateFormatter() | |
df.dateFormat = "EEEE" | |
let dayOfWeek = widget.addText(df.string(date).toUpperCase()) | |
let dateNumber = widget.addText(date.getDate().toString()) | |
dayOfWeek.font = provideFont(fontName,dayOfWeekSize) | |
dateNumber.font = provideFont(fontName,dateNumberSize) | |
widgetText.push(dayOfWeek) | |
widgetText.push(dateNumber) | |
// Add events if we're supposed to. | |
if (showEvents) { | |
// Only show events that aren't all day or canceled. | |
const events = await CalendarEvent.today([]) | |
let shownEvents = [] | |
for (const event of events) { | |
if (shownEvents.length == numberOfEvents) { break } | |
if (event.startDate.getTime() > date.getTime() && !event.isAllDay && !event.title.startsWith("Canceled:")) { | |
shownEvents.push(event) | |
} | |
} | |
// Format the text for each event. | |
for (const shownEvent of shownEvents) { | |
widget.addSpacer(12) | |
let title = widget.addText(shownEvent.title) | |
title.font = provideFont(fontName,eventTitleSize) | |
widgetText.push(title) | |
widget.addSpacer(7) | |
let time = widget.addText(formatTime(shownEvent.startDate)) | |
time.font = provideFont(fontName,eventTimeSize) | |
widgetText.push(time) | |
} | |
} | |
// Format the text for all widget text. | |
for (const textItem of widgetText) { | |
textItem.textColor = fontColor | |
if (horizontalAlignment == "right") { textItem.rightAlignText() } | |
else if (horizontalAlignment == "center") { textItem.centerAlignText() } | |
else { textItem.leftAlignText() } | |
} | |
if (verticalAlignment == "top" || verticalAlignment == "middle") { widget.addSpacer() } | |
Script.setWidget(widget) | |
if (testMode) { | |
let widgetSizeFormat = widgetSize.toLowerCase() | |
if (widgetSizeFormat == "small") {widget.presentSmall()} | |
if (widgetSizeFormat == "medium") {widget.presentMedium()} | |
if (widgetSizeFormat == "large") {widget.presentLarge()} | |
} | |
Script.complete() | |
// If we're running normally, go to the calendar. | |
} else if (fileExists && !resetWidget) { | |
const appleDate = new Date('2001/01/01') | |
const timestamp = (date.getTime() - appleDate.getTime()) / 1000 | |
const callback = new CallbackURL("calshow:"+timestamp) | |
callback.open() | |
Script.complete() | |
// If it's the first time it's running, set up the widget background. | |
} else { | |
// Determine if user has taken the screenshot. | |
var message | |
message = "Before you start, go to your home screen and enter wiggle mode. Scroll to the empty page on the far right and take a screenshot." | |
let exitOptions = ["Continue","Exit to Take Screenshot"] | |
let shouldExit = await generateAlert(message,exitOptions) | |
if (shouldExit) return | |
// Get screenshot and determine phone size. | |
let img = await Photos.fromLibrary() | |
let height = img.size.height | |
let phone = phoneSizes()[height] | |
if (!phone) { | |
message = "It looks like you selected an image that isn't an iPhone screenshot, or your iPhone is not supported. Try again with a different image." | |
await generateAlert(message,["OK"]) | |
return | |
} | |
// Prompt for widget size and position. | |
message = "What size of widget are you creating?" | |
let sizes = ["Small","Medium","Large"] | |
let size = await generateAlert(message,sizes) | |
let widgetSize = sizes[size] | |
message = "What position will it be in?" | |
message += (height == 1136 ? " (Note that your device only supports two rows of widgets, so the middle and bottom options are the same.)" : "") | |
// Determine image crop based on phone size. | |
let crop = { w: "", h: "", x: "", y: "" } | |
if (widgetSize == "Small") { | |
crop.w = phone.small | |
crop.h = phone.small | |
let positions = ["Top left","Top right","Middle left","Middle right","Bottom left","Bottom right"] | |
let position = await generateAlert(message,positions) | |
// Convert the two words into two keys for the phone size dictionary. | |
let keys = positions[position].toLowerCase().split(' ') | |
crop.y = phone[keys[0]] | |
crop.x = phone[keys[1]] | |
} else if (widgetSize == "Medium") { | |
crop.w = phone.medium | |
crop.h = phone.small | |
// Medium and large widgets have a fixed x-value. | |
crop.x = phone.left | |
let positions = ["Top","Middle","Bottom"] | |
let position = await generateAlert(message,positions) | |
let key = positions[position].toLowerCase() | |
crop.y = phone[key] | |
} else if(widgetSize == "Large") { | |
crop.w = phone.medium | |
crop.h = phone.large | |
crop.x = phone.left | |
let positions = ["Top","Bottom"] | |
let position = await generateAlert(message,positions) | |
// Large widgets at the bottom have the "middle" y-value. | |
crop.y = position ? phone.middle : phone.top | |
} | |
// Crop image and finalize the widget. | |
let imgCrop = cropImage(img, new Rect(crop.x,crop.y,crop.w,crop.h)) | |
files.writeImage(path,imgCrop) | |
message = "Your widget background is ready. If you haven't already granted Calendar access, it will pop up next." | |
await generateAlert(message,["OK"]) | |
// Make sure we have calendar access. | |
await CalendarEvent.today([]) | |
Script.complete() | |
} | |
/* | |
* Helper functions | |
* ================ | |
*/ | |
// Provide the specified font. | |
function provideFont(fontName,fontSize) { | |
if (fontName == "" || fontName == null) { | |
return Font.regularSystemFont(fontSize) | |
} else { | |
return new Font(fontName,fontSize) | |
} | |
} | |
// Formats the times under each event | |
function formatTime(date) { | |
let df = new DateFormatter() | |
df.useNoDateStyle() | |
df.useShortTimeStyle() | |
return df.string(date) | |
} | |
// Determines if two dates occur on the same day | |
function sameDay(d1, d2) { | |
return d1.getFullYear() === d2.getFullYear() && | |
d1.getMonth() === d2.getMonth() && | |
d1.getDate() === d2.getDate() | |
} | |
// Generate an alert with the provided array of options. | |
async function generateAlert(message,options) { | |
let alert = new Alert() | |
alert.message = message | |
for (const option of options) { | |
alert.addAction(option) | |
} | |
let response = await alert.presentAlert() | |
return response | |
} | |
// Crop an image into the specified rect. | |
function cropImage(img,rect) { | |
let draw = new DrawContext() | |
draw.size = new Size(rect.width, rect.height) | |
draw.drawImageAtPoint(img,new Point(-rect.x, -rect.y)) | |
return draw.getImage() | |
} | |
// Pixel sizes and positions for widgets on all supported phones. | |
function phoneSizes() { | |
let phones = { | |
"2688": { | |
"small": 507, | |
"medium": 1080, | |
"large": 1137, | |
"left": 81, | |
"right": 654, | |
"top": 228, | |
"middle": 858, | |
"bottom": 1488 | |
}, | |
"1792": { | |
"small": 338, | |
"medium": 720, | |
"large": 758, | |
"left": 54, | |
"right": 436, | |
"top": 160, | |
"middle": 580, | |
"bottom": 1000 | |
}, | |
"2436": { | |
"small": 465, | |
"medium": 987, | |
"large": 1035, | |
"left": 69, | |
"right": 591, | |
"top": 213, | |
"middle": 783, | |
"bottom": 1353 | |
}, | |
"2208": { | |
"small": 471, | |
"medium": 1044, | |
"large": 1071, | |
"left": 99, | |
"right": 672, | |
"top": 114, | |
"middle": 696, | |
"bottom": 1278 | |
}, | |
"1334": { | |
"small": 296, | |
"medium": 642, | |
"large": 648, | |
"left": 54, | |
"right": 400, | |
"top": 60, | |
"middle": 412, | |
"bottom": 764 | |
}, | |
"1136": { | |
"small": 282, | |
"medium": 584, | |
"large": 622, | |
"left": 30, | |
"right": 332, | |
"top": 59, | |
"middle": 399, | |
"bottom": 399 | |
} | |
} | |
return phones | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment