Skip to content

Instantly share code, notes, and snippets.

@tubit
Created September 29, 2020 08:04
Show Gist options
  • Save tubit/a34d1059686f391fba7f99689a6a365f to your computer and use it in GitHub Desktop.
Save tubit/a34d1059686f391fba7f99689a6a365f 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: 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