Adding transparency to a widget

I have the widget below which basically shows reminders. I wanted to have an image as a background (using the wallpaper so I can have the widget appear transparent). I am not great with javascript and don’t know how to amend the code to make it use the image as the background). Can someone help me with what to do? (Bonus: use a different image when it switches to dark mode).

// Reminders due today and tomorrow widget
var MAX_TASKS_SHOWN = 12;
const NOW = new Date();


const TITLE_FONT = Font.boldSystemFont(8);
const BODY_FONT = Font.semiboldRoundedSystemFont(9);

const today = new Date()
const tomorrow = new Date(today)
  tomorrow.setDate(tomorrow.getDate() + 1)
  tomorrow.setHours(0,0,0,0)
const tomorrow2 = new Date(today)
  tomorrow2.setDate(tomorrow2.getDate() + 2)
  tomorrow2.setHours(0,0,0,0)
  
const BACKGROUND_DARK_MODE = "system" 
// options: "yes", "no", "system"

//
// Utils
//

const compareReminderDuedates = (reminderA, reminderB) =>
  reminderA.dueDate - reminderB.dueDate;
const sortRemindersByDuedateAsc = reminders =>
  reminders.sort(compareReminderDuedates);

/** Round date down to 00:00 */
const stripTime = date => new Date(new Date(date).setHours(0, 0, 0, 0));

/** d1 - d2: will be negative if d2 > d1 */
const daysBetween = (d1, d2) => {
  const differenceMs = stripTime(d1).getTime() - stripTime(d2).getTime();
  return Math.floor(differenceMs / 86400000);
};

const getOverdueTasks = async () => {
  const all = await Reminder.allIncomplete();
  return sortRemindersByDuedateAsc(
    all.filter(task => task.dueDate && task.dueDate < today)
  );
};

const getTodayTasks = async () => {
  const all = await Reminder.allIncomplete();
  return sortRemindersByDuedateAsc(
    all.filter(task => today <= task.dueDate && task.dueDate < tomorrow)
  );
};

const getTomorrowTasks = async () => {
  const all = await Reminder.allIncomplete();
  return sortRemindersByDuedateAsc(
    all.filter(task => tomorrow <= task.dueDate && task.dueDate < tomorrow2)
  );
};

// overdue tasks

const getWidget = async () => {
  const overdueTasks = await getOverdueTasks();
  const todayTasks = await getTodayTasks();
  const tomorrowTasks = await getTomorrowTasks();
  
  const widget = new ListWidget();
  widget.backgroundColor = BG_COLOR;
  
  let isDarkMode = 
    BACKGROUND_DARK_MODE=="system" ? 
    await isUsingDarkAppearance() : 
    BACKGROUND_DARK_MODE=="yes"
    
   if (isDarkMode) {
      var BG_COLOR = new Color("#000000");
      var TITLE_COLOR = new Color("#9E9E9E");
      var OVERDUE_COLOR = new Color("#FE4639");
      var TASK_COLOR = new Color("#FFFFFF");
      var NO_OVERDUE_COLOR = new Color("#2FD15D");
      } else {
      var BG_COLOR = Color.white();
      var TITLE_COLOR = new Color("#8C8C8C");
      var OVERDUE_COLOR = new Color("#FD3F32");
      var TASK_COLOR = new Color("#000000");
      var NO_OVERDUE_COLOR = new Color("#13C759");
  }
  
  widget.addSpacer(0);
  
  const dueNum = overdueTasks.length + todayTasks.length
  if (dueNum) {
    var title = widget.addText(`Today   ${dueNum}`);
  }else{
    var title = widget.addText(`Today`);
  }
  title.textColor = TITLE_COLOR;
  title.font = TITLE_FONT;
  



  if (overdueTasks.length) {
    overdueTasks.slice(0, MAX_TASKS_SHOWN).forEach(({ title, dueDate }) => {
      const task = widget.addText(`| ${title}`);
      task.textColor = OVERDUE_COLOR;
      task.font = BODY_FONT;
      task.lineLimit = 1;
    });
  } else {
    
  }
  
  MAX_TASKS_SHOWN = MAX_TASKS_SHOWN - overdueTasks.length 
  
  if (MAX_TASKS_SHOWN > 0) {
    if (todayTasks.length) {
      todayTasks.slice(0, MAX_TASKS_SHOWN).forEach(({ title, dueDate }) => {
        const task = widget.addText(`| ${title}`);
        task.textColor = TASK_COLOR;
        task.font = BODY_FONT;
        task.lineLimit = 1;
      });
    } else {}
  } else {}
  
  if (dueNum) {
    
  } else {
    const noTasks = widget.addText("All done.");
    noTasks.textColor = NO_OVERDUE_COLOR;
    noTasks.font = BODY_FONT;
  }

// tomorrow

//  const tomorrowTasks = Reminder.allDueTomorrow();
  
  widget.addSpacer(5);

  MAX_TASKS_SHOWN = MAX_TASKS_SHOWN - todayTasks.length - 2
  
  if (MAX_TASKS_SHOWN > 0){
    if (tomorrowTasks.length) {
      const title = widget.addText(`Tomorrow   ${tomorrowTasks.length}`);
      title.textColor = TITLE_COLOR;
      title.font = TITLE_FONT;
      tomorrowTasks.slice(0, MAX_TASKS_SHOWN).forEach(({ title, dueDate }) => {
        const task = widget.addText(`| ${title}`);
        task.textColor = TASK_COLOR;
        task.font = BODY_FONT;
        task.lineLimit = 1;
      });
    } else {}
  } else {}
  
  widget.addSpacer();

  return widget;
};

async function isUsingDarkAppearance() {
  // yes there's a Device.isUsingDarkAppearance() method
  // but I find it unreliable
  const wv = new WebView()
  let js ="(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)"
  let r = await wv.evaluateJavaScript(js)
  return r
}

(async () => {
  const widget = await getWidget();
  if (config.runsInWidget) {
    Script.setWidget(widget);
    Script.complete();
  } else await widget.presentSmall();
})();```

Try the example in this thread. You will need to use the appropriate portion of your background image that corresponds with the section of the home screen where the widget is displayed.

Thanks for that. I’ve followed the instructions in the linked post here. I’ve added that code ahead of the code which I posted above. I’ve also downloaded and created the separate no-background script. I am getting the following error when running my widget after doing so: 2020-11-30 19:01:55: Error on line 286:25: Failed writing to disk.

Can you try this.

  • Run the no-background script by itself. It should present you with a menu.
  • Choose Generate Slices
  • After, that completes, run your widget script again.

I’m getting a message now (when running my widget) that “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.”. I have an iPhone 12 Pro Max. Should it work with that?

It should. Just to confirm, you are choosing a screenshot right? not the raw wallpaper image?

Correct. I’m choosing the screenshot.

Could you create a script with these lines, run it and let me know what the log says.

let wallpaper = await Photos.fromLibrary()
log(wallpaper.size.height)

If I run it off the full page screenshot it says 1146, if I run it off the background the the widget I want it says 2778 (the latter having used the script published in the forum to convert a screenshot of the background to the background of the widget based on size and position).

Curious why it’s different. The no-background module expects 2778 based on your device. Would you mind sending me your script and screenshot? DM if you must.

Thanks for looking into it. The screenshot is attached and t

he script is:

const { transparent } = importModule('no-background')

const widget = new ListWidget()
widget.backgroundImage = await transparent(Script.name())

// the rest of your widget code 

// Reminders due today and tomorrow widget
var MAX_TASKS_SHOWN = 12;
const NOW = new Date();


const TITLE_FONT = Font.boldSystemFont(8);
const BODY_FONT = Font.semiboldRoundedSystemFont(9);

const today = new Date()
const tomorrow = new Date(today)
  tomorrow.setDate(tomorrow.getDate() + 1)
  tomorrow.setHours(0,0,0,0)
const tomorrow2 = new Date(today)
  tomorrow2.setDate(tomorrow2.getDate() + 2)
  tomorrow2.setHours(0,0,0,0)
  
const BACKGROUND_DARK_MODE = "system" 
// options: "yes", "no", "system"

//
// Utils
//

const compareReminderDuedates = (reminderA, reminderB) =>
  reminderA.dueDate - reminderB.dueDate;
const sortRemindersByDuedateAsc = reminders =>
  reminders.sort(compareReminderDuedates);

/** Round date down to 00:00 */
const stripTime = date => new Date(new Date(date).setHours(0, 0, 0, 0));

/** d1 - d2: will be negative if d2 > d1 */
const daysBetween = (d1, d2) => {
  const differenceMs = stripTime(d1).getTime() - stripTime(d2).getTime();
  return Math.floor(differenceMs / 86400000);
};

const getOverdueTasks = async () => {
  const all = await Reminder.allIncomplete();
  return sortRemindersByDuedateAsc(
    all.filter(task => task.dueDate && task.dueDate < today)
  );
};

const getTodayTasks = async () => {
  const all = await Reminder.allIncomplete();
  return sortRemindersByDuedateAsc(
    all.filter(task => today <= task.dueDate && task.dueDate < tomorrow)
  );
};

const getTomorrowTasks = async () => {
  const all = await Reminder.allIncomplete();
  return sortRemindersByDuedateAsc(
    all.filter(task => tomorrow <= task.dueDate && task.dueDate < tomorrow2)
  );
};

// overdue tasks

const getWidget = async () => {
  const overdueTasks = await getOverdueTasks();
  const todayTasks = await getTodayTasks();
  const tomorrowTasks = await getTomorrowTasks();
  
  const widget = new ListWidget();
  widget.backgroundColor = BG_COLOR;
  
  let isDarkMode = 
    BACKGROUND_DARK_MODE=="system" ? 
    await isUsingDarkAppearance() : 
    BACKGROUND_DARK_MODE=="yes"
    
   if (isDarkMode) {
      var BG_COLOR = new Color("#000000");
      var TITLE_COLOR = new Color("#9E9E9E");
      var OVERDUE_COLOR = new Color("#FE4639");
      var TASK_COLOR = new Color("#FFFFFF");
      var NO_OVERDUE_COLOR = new Color("#2FD15D");
      } else {
      var BG_COLOR = Color.white();
      var TITLE_COLOR = new Color("#8C8C8C");
      var OVERDUE_COLOR = new Color("#FD3F32");
      var TASK_COLOR = new Color("#000000");
      var NO_OVERDUE_COLOR = new Color("#13C759");
  }
  
  widget.addSpacer(0);
  
  const dueNum = overdueTasks.length + todayTasks.length
  if (dueNum) {
    var title = widget.addText(`Today   ${dueNum}`);
  }else{
    var title = widget.addText(`Today`);
  }
  title.textColor = TITLE_COLOR;
  title.font = TITLE_FONT;
  



  if (overdueTasks.length) {
    overdueTasks.slice(0, MAX_TASKS_SHOWN).forEach(({ title, dueDate }) => {
      const task = widget.addText(`| ${title}`);
      task.textColor = OVERDUE_COLOR;
      task.font = BODY_FONT;
      task.lineLimit = 1;
    });
  } else {
    
  }
  
  MAX_TASKS_SHOWN = MAX_TASKS_SHOWN - overdueTasks.length 
  
  if (MAX_TASKS_SHOWN > 0) {
    if (todayTasks.length) {
      todayTasks.slice(0, MAX_TASKS_SHOWN).forEach(({ title, dueDate }) => {
        const task = widget.addText(`| ${title}`);
        task.textColor = TASK_COLOR;
        task.font = BODY_FONT;
        task.lineLimit = 1;
      });
    } else {}
  } else {}
  
  if (dueNum) {
    
  } else {
    const noTasks = widget.addText("All done.");
    noTasks.textColor = NO_OVERDUE_COLOR;
    noTasks.font = BODY_FONT;
  }

// tomorrow

//  const tomorrowTasks = Reminder.allDueTomorrow();
  
  widget.addSpacer(5);

  MAX_TASKS_SHOWN = MAX_TASKS_SHOWN - todayTasks.length - 2
  
  if (MAX_TASKS_SHOWN > 0){
    if (tomorrowTasks.length) {
      const title = widget.addText(`Tomorrow   ${tomorrowTasks.length}`);
      title.textColor = TITLE_COLOR;
      title.font = TITLE_FONT;
      tomorrowTasks.slice(0, MAX_TASKS_SHOWN).forEach(({ title, dueDate }) => {
        const task = widget.addText(`| ${title}`);
        task.textColor = TASK_COLOR;
        task.font = BODY_FONT;
        task.lineLimit = 1;
      });
    } else {}
  } else {}
  
  widget.addSpacer();

  return widget;
};

async function isUsingDarkAppearance() {
  // yes there's a Device.isUsingDarkAppearance() method
  // but I find it unreliable
  const wv = new WebView()
  let js ="(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)"
  let r = await wv.evaluateJavaScript(js)
  return r
}

(async () => {
  const widget = await getWidget();
  if (config.runsInWidget) {
    Script.setWidget(widget);
    Script.complete();
  } else await widget.presentSmall();
})();

That image screenshot actually worked for me.
I did make an adjustment your code because it’s assigning the background to the “wrong” widget.
The background image is being assigned to the widget variable that’s created on top of the script instead of the actual widget variable being used by the widget.

Try this steps:

  • Go to Files. Delete the folder /Scriptable/cache/nobg if it’s there
  • make sure your no-background is the latest version
  • Run the no-background script. Choose Generate Slices on the presented menu. Follow the on screen guides
  • Copy the script below and run it.
const { transparent } = importModule('no-background')

// const widget = new ListWidget()
// widget.backgroundImage = await transparent(Script.name()) // moved to line 74


// Reminders due today and tomorrow widget
var MAX_TASKS_SHOWN = 12;
const NOW = new Date();

const RESET_WALLPAPER = false

const TITLE_FONT = Font.boldSystemFont(8);
const BODY_FONT = Font.semiboldRoundedSystemFont(9);

const today = new Date()
const tomorrow = new Date(today)
  tomorrow.setDate(tomorrow.getDate() + 1)
  tomorrow.setHours(0,0,0,0)
const tomorrow2 = new Date(today)
  tomorrow2.setDate(tomorrow2.getDate() + 2)
  tomorrow2.setHours(0,0,0,0)
  
const BACKGROUND_DARK_MODE = "system" 
// options: "yes", "no", "system"

//
// Utils
//

const compareReminderDuedates = (reminderA, reminderB) =>
  reminderA.dueDate - reminderB.dueDate;
const sortRemindersByDuedateAsc = reminders =>
  reminders.sort(compareReminderDuedates);

/** Round date down to 00:00 */
const stripTime = date => new Date(new Date(date).setHours(0, 0, 0, 0));

/** d1 - d2: will be negative if d2 > d1 */
const daysBetween = (d1, d2) => {
  const differenceMs = stripTime(d1).getTime() - stripTime(d2).getTime();
  return Math.floor(differenceMs / 86400000);
};

const getOverdueTasks = async () => {
  const all = await Reminder.allIncomplete();
  return sortRemindersByDuedateAsc(
    all.filter(task => task.dueDate && task.dueDate < today)
  );
};

const getTodayTasks = async () => {
  const all = await Reminder.allIncomplete();
  return sortRemindersByDuedateAsc(
    all.filter(task => today <= task.dueDate && task.dueDate < tomorrow)
  );
};

const getTomorrowTasks = async () => {
  const all = await Reminder.allIncomplete();
  return sortRemindersByDuedateAsc(
    all.filter(task => tomorrow <= task.dueDate && task.dueDate < tomorrow2)
  );
};

// overdue tasks

const getWidget = async () => {
  const overdueTasks = await getOverdueTasks();
  const todayTasks = await getTodayTasks();
  const tomorrowTasks = await getTomorrowTasks();
  
  const widget = new ListWidget();
  widget.backgroundImage = await transparent(Script.name(), RESET_WALLPAPER)
  
  let isDarkMode = 
    BACKGROUND_DARK_MODE=="system" ? 
    await isUsingDarkAppearance() : 
    BACKGROUND_DARK_MODE=="yes"
    
   if (isDarkMode) {
      var BG_COLOR = new Color("#000000");
      var TITLE_COLOR = new Color("#9E9E9E");
      var OVERDUE_COLOR = new Color("#FE4639");
      var TASK_COLOR = new Color("#FFFFFF");
      var NO_OVERDUE_COLOR = new Color("#2FD15D");
      } else {
      var BG_COLOR = Color.white();
      var TITLE_COLOR = new Color("#8C8C8C");
      var OVERDUE_COLOR = new Color("#FD3F32");
      var TASK_COLOR = new Color("#000000");
      var NO_OVERDUE_COLOR = new Color("#13C759");
  }

  widget.backgroundColor = BG_COLOR;
      
  widget.addSpacer(0);
  
  const dueNum = overdueTasks.length + todayTasks.length
  if (dueNum) {
    var title = widget.addText(`Today   ${dueNum}`);
  }else{
    var title = widget.addText(`Today`);
  }
  title.textColor = TITLE_COLOR;
  title.font = TITLE_FONT;
  



  if (overdueTasks.length) {
    overdueTasks.slice(0, MAX_TASKS_SHOWN).forEach(({ title, dueDate }) => {
      const task = widget.addText(`| ${title}`);
      task.textColor = OVERDUE_COLOR;
      task.font = BODY_FONT;
      task.lineLimit = 1;
    });
  } else {
    
  }
  
  MAX_TASKS_SHOWN = MAX_TASKS_SHOWN - overdueTasks.length 
  
  if (MAX_TASKS_SHOWN > 0) {
    if (todayTasks.length) {
      todayTasks.slice(0, MAX_TASKS_SHOWN).forEach(({ title, dueDate }) => {
        const task = widget.addText(`| ${title}`);
        task.textColor = TASK_COLOR;
        task.font = BODY_FONT;
        task.lineLimit = 1;
      });
    } else {}
  } else {}
  
  if (dueNum) {
    
  } else {
    const noTasks = widget.addText("All done.");
    noTasks.textColor = NO_OVERDUE_COLOR;
    noTasks.font = BODY_FONT;
  }

// tomorrow

//  const tomorrowTasks = Reminder.allDueTomorrow();
  
  widget.addSpacer(5);

  MAX_TASKS_SHOWN = MAX_TASKS_SHOWN - todayTasks.length - 2
  
  if (MAX_TASKS_SHOWN > 0){
    if (tomorrowTasks.length) {
      const title = widget.addText(`Tomorrow   ${tomorrowTasks.length}`);
      title.textColor = TITLE_COLOR;
      title.font = TITLE_FONT;
      tomorrowTasks.slice(0, MAX_TASKS_SHOWN).forEach(({ title, dueDate }) => {
        const task = widget.addText(`| ${title}`);
        task.textColor = TASK_COLOR;
        task.font = BODY_FONT;
        task.lineLimit = 1;
      });
    } else {}
  } else {}
  
  widget.addSpacer();

  return widget;
};

async function isUsingDarkAppearance() {
  // yes there's a Device.isUsingDarkAppearance() method
  // but I find it unreliable
  const wv = new WebView()
  let js ="(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)"
  let r = await wv.evaluateJavaScript(js)
  return r
}

(async () => {
  const widget = await getWidget();
  if (config.runsInWidget) {
    Script.setWidget(widget);
    Script.complete();
  } else await widget.presentSmall();
})();

Thanks so much. That’s working now.

One question - if I run no-background and generate slices when in light mode and then do the same with a different back ground for dark mode. I’m finding in the widget it’s not automatically changing between the dark mode and light mode background. Is there an issue with my code or something else wrong?

No issue there. It’s because the widget needs to “refresh” before applying the new background. There’s no automated way to force a refresh with widgets when changing appearance modes. All you can do is wait.

Got it. Thanks. It just changed now. Appreciate all your help.