-
-
Save ShawnSWu/5593b6c69e29941378426f345c9c3fd0 to your computer and use it in GitHub Desktop.
A Scriptable script that creates "invisible" widget backgrounds based on your iOS wallpaper, I create this for iOS 18 version
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
// Change to true to see a preview of your widget. | |
const testMode = true | |
// Optionally specify the size of your widget preview. | |
const widgetPreview = "large" | |
/* -- GREETING AND DATE -- */ | |
// Optionally show a greeting based on the time of day. | |
const showGreeting = true | |
// Choose the date style. "iOS" matches the default calendar app (like: THURSDAY 29) | |
// Or, use docs.scriptable.app/dateformatter to write your own format. | |
const dateDisplay = "EEEE" | |
/* -- EVENTS -- */ | |
// Change to false to hide events. | |
const showEvents = true | |
// Choose whether to show all-day events. | |
const showAllDay = false | |
// Specify how many events to show. | |
const numberOfEvents = 4 | |
// Optionally show tomorrow's events. | |
const showTomorrow = false | |
// Write a message when there are no events, or change to "" for blank. | |
const noEventMessage = "Keep happy and keep smiling." | |
/* -- SPACING -- */ | |
// Can be top, middle, or bottom. | |
const verticalAlignment = "middle" | |
// Can be left, center, or right. | |
const horizontalAlignment = "left" | |
// The spacing between each element. | |
const elementSpacing = 12 | |
/* -- 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") | |
const date = new Date() | |
const resetWidget = false | |
// Change the font sizes for each element. | |
const greetingSize = 17 | |
const dateSize = 25 | |
const dayOfWeekSize = 25 | |
const eventTitleSize = 14 | |
const eventTimeSize = 10 | |
const noEventMessageSize = 14 | |
const shawnTextSize = 29 | |
const files = FileManager.local() | |
const path = files.joinPath(files.documentsDirectory(), Script.name() + ".jpg") | |
// If we're in the widget or testing, build the widget. | |
if (config.runsInWidget || (testMode && files.fileExists(path) && !resetWidget)) { | |
const widget = new ListWidget() | |
if (files.fileExists(path)) widget.backgroundImage = files.readImage(path) | |
if (verticalAlignment == "middle" || verticalAlignment == "bottom") { | |
widget.addSpacer() | |
} | |
// Format the greeting if we need it. | |
if (showGreeting) { | |
let greetingText = makeGreeting() | |
let greeting = widget.addText(greetingText) | |
formatText(greeting, greetingSize) | |
widget.addSpacer(elementSpacing) | |
} | |
// Format the date info. | |
let df = new DateFormatter() | |
if (dateDisplay.toLowerCase() == "ios") { | |
df.dateFormat = "EEEE" | |
let dayOfWeek = widget.addText(df.string(date).toUpperCase()) | |
let dateNumber = widget.addText(date.getDate().toString()) | |
formatText(dayOfWeek, dayOfWeekSize) | |
formatText(dateNumber, dateSize) | |
} else { | |
df.dateFormat = dateDisplay | |
var dateText = widget.addText(df.string(date)) | |
formatText(dateText, dateSize) | |
} | |
var option = {month:'long', day:'numeric'} | |
let shawn = widget.addText(new Date().toLocaleDateString("en-US", option).toString()) | |
formatText(shawn, shawnTextSize) | |
// Add events if we're supposed to. | |
if (showEvents) { | |
// Your existing event display code here | |
// [Previous event handling code remains unchanged] | |
} | |
if (verticalAlignment == "top" || verticalAlignment == "middle") { | |
widget.addSpacer() | |
} | |
Script.setWidget(widget) | |
if (testMode) { | |
let widgetSizeFormat = widgetPreview.toLowerCase() | |
if (widgetSizeFormat == "small") { widget.presentSmall() } | |
if (widgetSizeFormat == "medium") { widget.presentMedium() } | |
if (widgetSizeFormat == "large") { widget.presentLarge() } | |
} | |
Script.complete() | |
} else { | |
// Background setup code | |
let message = "Before you start, edit your home screen (wiggle mode). Scroll to the empty page on the far right and take a screenshot." | |
const shouldExit = await generateAlert(message, ["Continue", "Exit to Take Screenshot"]) | |
if (shouldExit.index) return | |
let img = await Photos.fromLibrary() | |
const 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." | |
return await generateAlert(message, ["OK"]) | |
} | |
if (phone.text) { | |
message = "What size are your home screen icons?" | |
const textOptions = [{key: "text", value: "Small (has labels)"}, {key: "notext", value: "Large (no labels)"}] | |
const textResponse = await generateAlert(message, textOptions) | |
phone = phone[textResponse.key] | |
} | |
message = "What size of widget are you creating?" | |
const sizes = {small: "Small", medium: "Medium", large: "Large"} | |
const sizeOptions = [sizes.small, sizes.medium, sizes.large] | |
const size = (await generateAlert(message, sizeOptions)).value | |
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.)" : "") | |
let positions | |
if (size == sizes.small) { | |
positions = ["Top left","Top right","Middle left","Middle right","Bottom left","Bottom right"] | |
} else if (size == sizes.medium) { | |
positions = ["Top","Middle","Bottom"] | |
} else if (size == sizes.large) { | |
positions = [{key: "top", value: "Top"}, {key: "middle", value: "Bottom"}] | |
} | |
const position = (await generateAlert(message, positions)).key | |
const crop = { | |
w: (size == sizes.small ? phone.small : phone.medium), | |
h: (size == sizes.large ? phone.large : phone.small), | |
x: (size == sizes.small ? phone[position.split(" ")[1]] : phone.left), | |
y: phone[position.toLowerCase().split(" ")[0]] | |
} | |
const draw = new DrawContext() | |
draw.size = new Size(crop.w, crop.h) | |
draw.drawImageAtPoint(img, new Point(-crop.x, -crop.y)) | |
img = draw.getImage() | |
message = "Your widget background is ready. Would you like to use it as this script's background, or export the image?" | |
const exports = {script: "Use for this script", photos: "Export to Photos", files: "Export to Files"} | |
const exportOptions = [exports.script, exports.photos, exports.files] | |
const exportValue = (await generateAlert(message, exportOptions)).value | |
if (exportValue == exports.script) { | |
files.writeImage(path, img) | |
} else if (exportValue == exports.photos) { | |
Photos.save(img) | |
} else if (exportValue == exports.files) { | |
await DocumentPicker.exportImage(img) | |
} | |
} | |
// Helper functions | |
async function generateAlert(message, options) { | |
const alert = new Alert() | |
alert.message = message | |
const isObject = options[0].value | |
for (const option of options) { | |
alert.addAction(isObject ? option.value : option) | |
} | |
const index = await alert.presentAlert() | |
return { | |
index: index, | |
value: isObject ? options[index].value : options[index], | |
key: isObject ? options[index].key : options[index] | |
} | |
} | |
function phoneSizes(inputHeight) { | |
return { | |
// 16 Pro Max | |
"2868": { | |
text: { | |
small: 510, | |
medium: 1092, | |
large: 1146, | |
left: 114, | |
right: 696, | |
top: 276, | |
middle: 912, | |
bottom: 1548 | |
}, | |
notext: { | |
small: 530, | |
medium: 1138, | |
large: 1136, | |
left: 91, | |
right: 699, | |
top: 276, | |
middle: 882, | |
bottom: 1488 | |
} | |
}, | |
// 16 Plus, 15 Plus, 15 Pro Max, 14 Pro Max | |
"2796": { | |
text: { | |
small: 510, | |
medium: 1092, | |
large: 1146, | |
left: 98, | |
right: 681, | |
top: 252, | |
middle: 888, | |
bottom: 1524 | |
}, | |
notext: { | |
small: 530, | |
medium: 1139, | |
large: 1136, | |
left: 75, | |
right: 684, | |
top: 252, | |
middle: 858, | |
bottom: 1464 | |
} | |
}, | |
// 16 Pro | |
"2622": { | |
text: { | |
small: 486, | |
medium: 1032, | |
large: 1098, | |
left: 87, | |
right: 633, | |
top: 261, | |
middle: 872, | |
bottom: 1485 | |
}, | |
notext: { | |
small: 495, | |
medium: 1037, | |
large: 1035, | |
left: 84, | |
right: 626, | |
top: 270, | |
middle: 810, | |
bottom: 1350 | |
} | |
}, | |
// 16, 15, 15 Pro, 14 Pro | |
"2556": { | |
text: { | |
small: 474, | |
medium: 1017, | |
large: 1062, | |
left: 81, | |
right: 624, | |
top: 240, | |
middle: 828, | |
bottom: 1416 | |
}, | |
notext: { | |
small: 495, | |
medium: 1047, | |
large: 1047, | |
left: 66, | |
right: 618, | |
top: 243, | |
middle: 795, | |
bottom: 1347 | |
} | |
} | |
}[inputHeight] | |
} | |
// Your existing helper functions | |
function makeGreeting() { | |
let greeting = "Good " | |
if (date.getHours() < 4) { | |
greeting = greeting + "night." | |
} else if (date.getHours() < 12) { | |
greeting = greeting + "morning." | |
} else if (date.getHours() < 17) { | |
greeting = greeting + "afternoon." | |
} else if (date.getHours() < 20) { | |
greeting = greeting + "evening." | |
} else { | |
greeting = greeting + "night." | |
} | |
return greeting | |
} | |
function shouldShowEvent(event) { | |
if (event.title.startsWith("Canceled:")) { | |
return false | |
} | |
if (event.isAllDay) { | |
return showAllDay | |
} | |
return (event.startDate.getTime() > date.getTime()) | |
} | |
function provideFont(fontName, fontSize) { | |
if (fontName == "" || fontName == null) { | |
return Font.regularSystemFont(fontSize) | |
} else { | |
return new Font(fontName, fontSize) | |
} | |
} | |
function displayEvent(widget, event) { | |
widget.addSpacer(elementSpacing) | |
let title = widget.addText(event.title) | |
formatText(title, eventTitleSize) | |
if (event.isAllDay) { return } | |
widget.addSpacer(7) | |
let time = widget.addText(formatTime(event.startDate)) | |
formatText(time, eventTimeSize) | |
} | |
function formatTime(date) { | |
let df = new DateFormatter() | |
df.useNoDateStyle() | |
df.useShortTimeStyle() | |
return df.string(date) | |
} | |
function formatText(textItem, fontSize) { | |
textItem.font = provideFont(fontName, fontSize) | |
textItem.textColor = fontColor | |
if (horizontalAlignment == "right") { | |
textItem.rightAlignText() | |
} else if (horizontalAlignment == "center") { | |
textItem.centerAlignText() | |
} else { | |
textItem.leftAlignText() | |
} | |
} | |
function sameDay(d1, d2) { | |
return d1.getFullYear() === d2.getFullYear() && | |
d1.getMonth() === d2.getMonth() && | |
d1.getDate() === d2.getDate() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Wrong sizes @16 Pro