Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 75 additions & 34 deletions ScriptableNextBirthdays.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @author: Michael Gerischer
* @github: https://github.com/GerMichael/ScriptableNextBirththdays
*/
const version = "1.1.3";
const version = "1.1.4";

// === Script controlled variables ===
// === DO NOT ALTER VARIABLE NAMES ===
Expand Down Expand Up @@ -127,14 +127,25 @@ function getValueForWidgetType(obj, defaultValue) {



// File Handling
// Errors
class NoCacheFileError extends Error {
constructor(path){
super(`The cache file ${path} does not exist!`);
this.path = path;
}
}
class NoContactsError extends Error {
constructor(){
super(`No contacts found!`);
}
}
class ContactsAccessDeniedError extends Error {
constructor(){
super(`No access to the contacts was permitted!`);
}
}

// File Handling
let fm = FileManager.local();

if(fm.isFileStoredIniCloud(module.filename)){
Expand Down Expand Up @@ -361,25 +372,27 @@ const inputText = Array.isArray(escapedText) ? `['${
const textLength = new TextLength();
const lightDotsLength = await textLength.computeLength(settings.textOverflowChar, settings.textSize, settings.textFontFamilies.thin)

async function computeWidget(fm, settings){
const widget = new ListWidget();
async function createWidget(fm, settings){
const recomputeBirthdays = !config.runsInWidget;

const allBirthdays = recomputeBirthdays ? await updateAndGetCache(fm) : loadCache(fm);
let allBirthdays;
try {
allBirthdays = recomputeBirthdays ? await updateAndGetCache(fm) : loadCache(fm);
} catch {
return createErrorWidget(new ContactsAccessDeniedError(), settings);
}

let nextContacts;
nextContacts = computeNextBirthdays(allBirthdays, settings);

nextContacts = nextContactsAndRelativeDates(nextContacts);

await composeWidget(widget, nextContacts, settings);
return widget;
return await createContentWidget(nextContacts, settings);
}

async function composeWidget(widget, contacts, settings) {
async function createContentWidget(contacts, settings) {
const widget = new ListWidget();

const {
verticalSpacing,
titleSize,
titleSpacing,
textSize,
Expand All @@ -404,13 +417,7 @@ async function composeWidget(widget, contacts, settings) {
settings.textFontFamilies);

if(contacts.length < 1){
renderNoContactsMsg(
widget,
(canvasSize.y - textSize) / 2 - padding[3],
textSize,
textColor,
settings.textFontFamilies);
return widget;
return createErrorWidget(new NoContactsError(), settings);
}

await renderNextBirthdays(
Expand Down Expand Up @@ -469,15 +476,6 @@ function renderTitle(widget, titleSize, title, titleColor, textFontFamilies){
}
}

function renderNoContactsMsg(widget, marginTopBottom, textSize, textColor, textFontFamilies){
widget.addSpacer(marginTopBottom);
const text = widget.addText("No contacts found.");
text.font = new Font(textFontFamilies.regular, textSize);
text.textColor = textColor;
text.centerAlignText();
widget.addSpacer(marginTopBottom);
}

function getCanvasSize(paddingTop, paddingX, verticalSpacing, titleSize){
let canvasSize = [];

Expand Down Expand Up @@ -580,21 +578,65 @@ async function renderNextBirthdays(contacts, widget, canvasSize, titleSpacing, t


// Error handling
function setupErrorWidget(widget, error){
function createErrorWidget(error, settings){
const widget = new ListWidget();

let titleSize = 26;
let titleSpacing = 10;
let verticalSpacing = 10;
let backgroundIsDark = true;
try {
widget.backgroundColor = new Color(settings.backgroundColor);
backgroundIsDark = isDark(settings.backgroundColor);
titleSize = getValueForWidgetType(settings.titleSize, 26);
verticalSpacing = getValueForWidgetType(settings.verticalTextSpacing, 0.5);
} catch (e) {
console.error("ERROR WHILE RENDERING ERROR!" + e);
}

const title = widget.addText("Oh snap!");

try {
title.font = Font.regularSystemFont(titleSize);
titleSpacing = titleSize * verticalSpacing;
} catch (e) {
console.error("ERROR WHILE RENDERING ERROR!" + e);
}

widget.addSpacer(titleSpacing);

let text;
let isCritical = false;
if (error instanceof NoCacheFileError){
text = widget.addText(`🏗 There is one last thing…
Please run this script once in the Scriptable app by clicking the „${Script.name()}“-tile.
Thanks! 🙏`);
text.textColor = Color.yellow();
Please run this script once in the Scriptable app by clicking the „${Script.name()}“-tile.
Thanks! 🙏`);
} else if (error instanceof NoContactsError){
text = widget.addText(`👀 No contacts could be found…`);
} else if (error instanceof ContactsAccessDeniedError){
text = widget.addText(`😢 Couldn't read your contacts storing the names and birthdays on your device. Make sure you gave the Scriptable app permission to read your contacts (Settings > Privacy and Security > Contacts > Check "Scriptable")`);
isCritical = true;
} else {
text = widget.addText("🫣 Unexpected error: " + (error instanceof Error ? error.message : String(error)));
text.textColor = Color.red();
isCritical = true;
}

title.centerAlignText();
text.centerAlignText();
if(isCritical) {
const color = backgroundIsDark ? Color.red() : Color.red();
title.textColor = color;
text.textColor = color;
} else {
const color = backgroundIsDark ? Color.yellow() : Color.orange();
title.textColor = color;
text.textColor = color;
}
if(config.widgetFamily === "small"){
text.font = Font.regularSystemFont(10);
}

return widget;
}


Expand Down Expand Up @@ -846,7 +888,7 @@ try{
log(`Running script version ${version}`);
const start = new Date();

const widget = await computeWidget(fm, settings);
const widget = await createWidget(fm, settings);

Script.setWidget(widget);
setNextExecution(widget);
Expand All @@ -860,8 +902,7 @@ log(`Running script version ${version}`);

} catch(e){
console.error(e);
const widget = new ListWidget();
setupErrorWidget(widget, e);
const widget = createErrorWidget(e, settings);
Script.setWidget(widget);
setNextExecution(widget);
if(config.runsInApp){
Expand Down