Tweak layouts and add data for release
|
|
@ -0,0 +1 @@
|
||||||
|
1.00: Implement Working Memory Helper app
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Working Memory Helper
|
||||||
|
Human brains keep track of what they are doing in a conceptual space known as "working memory". Older adults and people
|
||||||
|
of all ages with ADHD often struggle to maintain information in their working memories, causing them to forget what
|
||||||
|
they were doing only moments after deciding to do it. One excellent way to combat this symptom is to externalize your
|
||||||
|
working memory.
|
||||||
|
|
||||||
|
This app doesn't completely externalize and replace working memory, but it does act as a prosthesis for the task
|
||||||
|
management aspect of working memory. The workflow looks something like this:
|
||||||
|
|
||||||
|
1. Decide to do something. (If you can't get this far on your own, this app is not gonna help.)
|
||||||
|
2. Immediately enter a brief prompt in the app as a "task". For example, if you were going to take out the trash,
|
||||||
|
you might write "Trash". If you were going to take your car to the mechanic, you might write "car", or "mechanic". It
|
||||||
|
doesn't have to remind you what you were doing a week from now, only a minute or so, so it can be very simple / brief.
|
||||||
|
3. Thirty seconds after you enter the task into the app, your device will vibrate and ask you if you are on task, or if
|
||||||
|
you got distracted.
|
||||||
|
1. If you are on task, hit "On task" and the app will wait a little longer before reminding you again.
|
||||||
|
2. If you got distracted, hit "distracted" and the app will remind you a little sooner next time.
|
||||||
|
4. Continue getting reminders from your watch at various intervals until you complete the task, then tell the app the
|
||||||
|
task is complete. Repeat this process for every single thing you do until you die, basically.
|
||||||
|
|
||||||
|
   
|
||||||
|
 
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
You must have some kind of keyboard library available in order to enter task descriptions on your device. This app is
|
||||||
|
only supported on BangleJS2
|
||||||
|
|
||||||
|
## Styling
|
||||||
|
This app attempts to match whatever theme your Bangle watch is using. Styling options are not currently available
|
||||||
|
beyond that, but tweaking some things will eventually be possible, like the size and presence of swipe hints, whether
|
||||||
|
or not task text is outlined, etc.
|
||||||
|
|
||||||
|
## Task settings
|
||||||
|
You can edit the settings of any individual task. You can rename the task, restart (un-complete) the task, or change
|
||||||
|
some of the reminder cadence settings. As far as cadence, there are a couple that warrante explanation:
|
||||||
|
|
||||||
|
#### Interval
|
||||||
|
This is the base reminder interval for your task. If it is 30, your first reminder will be after 30 seconds.
|
||||||
|
|
||||||
|
#### Incremental Backoff
|
||||||
|
Incremental backoff is a strategy for timing the reminder notifications you get based on how well you stay on task.
|
||||||
|
Each time you affirm that you are "on task", incremental backoff means it will wait longer before reminding you again.
|
||||||
|
Similarly each time you affirm that you are "distracted" the incremental backoff will wait less time before reminding
|
||||||
|
you again. The exact intervals are multiples of the base interval. For a task with a base interval of 30 seconds, the
|
||||||
|
second reminder would be after 60 seconds. The third after 120 seconds, etc. Then if you got distracted it would go
|
||||||
|
back to 60, then 30, then 15. Typically the interval will never go below 1/2 of your base interval.
|
||||||
|
|
||||||
|
If you disable Incremental Backoff, you will be reminded once every base interval no matter what you do. This can be
|
||||||
|
handy if you are having trouble staying on task when the intervals get too long with incremental backoff.
|
||||||
|
|
||||||
|
## Controls
|
||||||
|
A large focus of this app was making clear affordances for the user interface. Anything that can be pressed should look
|
||||||
|
like a button, however you may notice some small arrows and text on the sides / top / bottom of the screen in some
|
||||||
|
cases. These hints are there to tell you that you can swipe across the screen to perform additional actions.
|
||||||
|
Swipe your finger anywhere on the screen in the direction the arrow is pointing to use the listed function.
|
||||||
|
|
||||||
|
## Known issues
|
||||||
|
The clock is not super-duper accurate because it only updates when the screen refreshes (which can be 30 seconds or 5
|
||||||
|
minutes apart). I put it on there, though, because it is more useful to be there and lagging by 30+ seconds than to be
|
||||||
|
not there at all. I plan to fix this problem eventually but it's secondary to the main functions of the app at the
|
||||||
|
moment.
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwgYJGgVJkmQDZt/////wRfgVP8g1OiQRByQRNyQRCoAMHgP8wEAk5GBAAPJgET/IREh//yVJCAYABkmf/8gCIc///yNIQAD/gCB8ARFAAQmBkmTA4YREGoIvCJQIABHYY1EhIHB/DFDCgItB/IREgM//x3Byg2BiUAggRHLINAgsvFAOkC4JrGCIKtBmZCC/2IR4IRFLILRBCIf/6MAgf/8gREWYUz/J8CCIMAv4REAoIRBg1CpMmv/6GQNPDoQXGHAMA54RCF4Y7HAAOvCIZTCCIRfECIp3BCLpHDCJcJNYJ9CCJdSpaPDCJUDe4LFCCJ2odIIRN9USeogRK9C/DCLDFDCIzFEdIoRHfYwFDsmESodPCIgXFqVECId/CIpNEGohTFL4WSCI8T/8kCIvyVoIRFz7jFgE//1ICIsE//5CAkAg4IBDQJrCgLbB5ARFDQI+BwEJCgNJfwIsBAAsfCQOSpMkyYFB+QQGgECv4MBAAf8YQYAFhIRFXggAGk4QD5J6FAAxHCySVBAHQ="))
|
||||||
|
|
@ -6,7 +6,24 @@ E.showMessage("Loading ... ");
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
const allTasks = [];
|
const localTaskFile = "wrkmem.json";
|
||||||
|
let savedData = require("Storage")
|
||||||
|
.readJSON(localTaskFile, true);
|
||||||
|
if (!savedData) {
|
||||||
|
savedData = {
|
||||||
|
tasks: [], keyboardAlpha: undefined
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentMenu;
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
require("Storage")
|
||||||
|
.writeJSON("wrkmem.json", savedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const allTasks = savedData.tasks;
|
||||||
const nudgeManager = {
|
const nudgeManager = {
|
||||||
activeTask : null, taskTimeout: null, responseTimeout: null, interrupt: () => {
|
activeTask : null, taskTimeout: null, responseTimeout: null, interrupt: () => {
|
||||||
if (this.taskTimeout) clearTimeout(this.taskTimeout);
|
if (this.taskTimeout) clearTimeout(this.taskTimeout);
|
||||||
|
|
@ -16,7 +33,8 @@ const nudgeManager = {
|
||||||
if (this.responseTimeout) clearTimeout(this.responseTimeout);
|
if (this.responseTimeout) clearTimeout(this.responseTimeout);
|
||||||
if (this.taskTimeout) clearTimeout(this.taskTimeout);
|
if (this.taskTimeout) clearTimeout(this.taskTimeout);
|
||||||
this.activeTask = task;
|
this.activeTask = task;
|
||||||
const time = task.incrementalBackoffSet[task.backoffIndex] * task.interval * 1000;
|
const backoffIndex = task.useBackoff ? task.backoffIndex : 1;
|
||||||
|
const time = task.incrementalBackoffSet[backoffIndex] * task.interval * 1000;
|
||||||
this.taskTimeout = setTimeout(nudgeFn, time);
|
this.taskTimeout = setTimeout(nudgeFn, time);
|
||||||
}, queueResponseTimeout: (defaultFn) => {
|
}, queueResponseTimeout: (defaultFn) => {
|
||||||
// This timeout shouldn't be set if we've queued a response timeout, but we clear it anyway.
|
// This timeout shouldn't be set if we've queued a response timeout, but we clear it anyway.
|
||||||
|
|
@ -254,23 +272,25 @@ function createMenu(options) {
|
||||||
function setMenu(menu) {
|
function setMenu(menu) {
|
||||||
g.clearRect(Bangle.appRect);
|
g.clearRect(Bangle.appRect);
|
||||||
g.reset();
|
g.reset();
|
||||||
|
currentMenu = menu;
|
||||||
menu.render();
|
menu.render();
|
||||||
menu.setUI();
|
menu.setUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
let keyboardAlpha, keyboardNum;
|
let keyboardAlpha;
|
||||||
if (textInput.generateKeyboard) {
|
if (textInput.generateKeyboard) {
|
||||||
const charSet = textInput.createCharSet("ABCDEFGHIJKLMNOPQRSTUVWXYZ", ["spc", "ok", "del"]);
|
const charSet = textInput.createCharSet("ABCDEFGHIJKLMNOPQRSTUVWXYZ", ["spc", "ok", "del"]);
|
||||||
keyboardAlpha = textInput.generateKeyboard(charSet);
|
keyboardAlpha = textInput.generateKeyboard(charSet)
|
||||||
// keyboardNum = textInput.generateKeyboard([["1", "2", "3", "4", "5", "6", "7", "8", "9"], ["0"], "del", "ok"]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function newTask(initialText) {
|
function newTask(initialText) {
|
||||||
|
nudgeManager.interrupt();
|
||||||
initialText = initialText || "";
|
initialText = initialText || "";
|
||||||
textInput.input({text: initialText, keyboardMain: keyboardAlpha})
|
textInput.input({text: initialText, keyboardMain: keyboardAlpha})
|
||||||
.then(text => {
|
.then(text => {
|
||||||
const task = createTask(text)
|
const task = createTask(text)
|
||||||
allTasks.unshift(task);
|
allTasks.unshift(task);
|
||||||
|
save();
|
||||||
startTask(task);
|
startTask(task);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -290,8 +310,8 @@ function nudge(task) {
|
||||||
});
|
});
|
||||||
const nudgeMenu = createMenu({
|
const nudgeMenu = createMenu({
|
||||||
title : "Are you on task?", titleFont: "6x8", items: [
|
title : "Are you on task?", titleFont: "6x8", items: [
|
||||||
{text: task.text, size: 1}, {text: "On Task", size: 2, callback: () => affirmOnTask(task)}, {
|
{text: task.text, size: 1}, {text: "On Task", size: 1, callback: () => affirmOnTask(task)}, {
|
||||||
text: "Distracted", size: 2, callback: () => affirmDistracted(task)
|
text: "Distracted", size: 1, callback: () => affirmDistracted(task)
|
||||||
}
|
}
|
||||||
], isHorizontal: false
|
], isHorizontal: false
|
||||||
});
|
});
|
||||||
|
|
@ -312,14 +332,15 @@ function affirmDistracted(task) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function concludeUnresponsive(task) {
|
function concludeUnresponsive(task) {
|
||||||
Bangle.buzz(250, 1).then(() => Bangle.setLCDPower(true));
|
Bangle.buzz(250, 1)
|
||||||
|
.then(() => Bangle.setLCDPower(true));
|
||||||
task.unresponsiveCount++;
|
task.unresponsiveCount++;
|
||||||
task.backoffIndex = Math.max(0, task.backoffIndex - 1);
|
task.backoffIndex = Math.max(0, task.backoffIndex - 1);
|
||||||
nudgeManager.queueResponseTimeout(() => concludeUnresponsive(task))
|
nudgeManager.queueResponseTimeout(() => concludeUnresponsive(task))
|
||||||
}
|
}
|
||||||
|
|
||||||
function showTempMessage(text, title, thenFn) {
|
function showTempMessage(text, title, thenFn) {
|
||||||
E.showMessage(text,{title});
|
E.showMessage(text, {title});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
Bangle.setLocked(true);
|
Bangle.setLocked(true);
|
||||||
thenFn();
|
thenFn();
|
||||||
|
|
@ -335,6 +356,7 @@ function completeTask(task) {
|
||||||
task.complete = true;
|
task.complete = true;
|
||||||
removeTask(task, allTasks);
|
removeTask(task, allTasks);
|
||||||
allTasks.push(task);
|
allTasks.push(task);
|
||||||
|
save();
|
||||||
setMenu(getTaskMenu(task));
|
setMenu(getTaskMenu(task));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,6 +364,7 @@ function restartTask(task) {
|
||||||
task.complete = false;
|
task.complete = false;
|
||||||
removeTask(task, allTasks);
|
removeTask(task, allTasks);
|
||||||
allTasks.unshift(task);
|
allTasks.unshift(task);
|
||||||
|
save();
|
||||||
startTask(task);
|
startTask(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,18 +390,27 @@ function createTask(text) {
|
||||||
interval : 30,
|
interval : 30,
|
||||||
backoffIndex : 1,
|
backoffIndex : 1,
|
||||||
incrementalBackoffSet,
|
incrementalBackoffSet,
|
||||||
complete : false
|
complete : false,
|
||||||
|
useBackoff: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTaskMenu(task) {
|
function getTaskMenu(task) {
|
||||||
|
const d = new Date();
|
||||||
|
const h = d.getHours(), m = d.getMinutes();
|
||||||
|
const time = h + ":" + m.toString().padStart(2,0);
|
||||||
const taskSwipeControls = [
|
const taskSwipeControls = [
|
||||||
createSwipeControl(SWIPE.LEFT, "Menu", () => setMenu(mainMenu)),
|
createSwipeControl(SWIPE.LEFT, "Menu", () => {
|
||||||
createSwipeControl(SWIPE.RIGHT, "New Task", newTask),
|
setMenu(mainMenu);
|
||||||
|
nudgeManager.interrupt();
|
||||||
|
}), createSwipeControl(SWIPE.RIGHT, "New Task", newTask),
|
||||||
];
|
];
|
||||||
const items = [];
|
const items = [];
|
||||||
if (task.complete) {
|
if (task.complete) {
|
||||||
taskSwipeControls.push(createSwipeControl(SWIPE.UP, "Restart", () => restartTask(task)));
|
taskSwipeControls.push(createSwipeControl(SWIPE.UP, "Restart", () => restartTask(task)));
|
||||||
|
taskSwipeControls.push(createSwipeControl(SWIPE.DOWN,
|
||||||
|
"Task List",
|
||||||
|
() => showTaskList(allTasks, () => startTask(task))));
|
||||||
items.push({text: task.text + " completed!", size: 1});
|
items.push({text: task.text + " completed!", size: 1});
|
||||||
const nextTask = getNextTask(task, allTasks);
|
const nextTask = getNextTask(task, allTasks);
|
||||||
if (nextTask) {
|
if (nextTask) {
|
||||||
|
|
@ -390,11 +422,12 @@ function getTaskMenu(task) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
items.push({text: task.text})
|
items.push({text: task.text, size: 2})
|
||||||
taskSwipeControls.push(createSwipeControl(SWIPE.DOWN, "Complete", () => completeTask(task)))
|
taskSwipeControls.push(createSwipeControl(SWIPE.UP, "Complete", () => completeTask(task)))
|
||||||
|
taskSwipeControls.push(createSwipeControl(SWIPE.DOWN, "Edit Task", () => editTask(task, () => startTask(task))))
|
||||||
}
|
}
|
||||||
return createMenu({
|
return createMenu({
|
||||||
items, spaceAround: 0, spaceBetween: 0, swipeControls: taskSwipeControls
|
items, spaceAround: 0, spaceBetween: 0, swipeControls: taskSwipeControls, title: time
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -419,6 +452,7 @@ function st5(fn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function editTask(task, backFn) {
|
function editTask(task, backFn) {
|
||||||
|
nudgeManager.interrupt();
|
||||||
let editMenu = [];
|
let editMenu = [];
|
||||||
editMenu.push({title: "Rename", onchange: st5(() => renameTask(task, () => editTask(task, backFn)))});
|
editMenu.push({title: "Rename", onchange: st5(() => renameTask(task, () => editTask(task, backFn)))});
|
||||||
if (task.complete) {
|
if (task.complete) {
|
||||||
|
|
@ -427,6 +461,8 @@ function editTask(task, backFn) {
|
||||||
} else {
|
} else {
|
||||||
editMenu.push({title: "Resume Task", onchange: st5(() => startTask(task))})
|
editMenu.push({title: "Resume Task", onchange: st5(() => startTask(task))})
|
||||||
}
|
}
|
||||||
|
editMenu.push({ title:"Interval", value: task.interval, min:10, step: 10, onchange: v => task.interval = v })
|
||||||
|
editMenu.push({ title:"Incremental Backoff", value: !!task.useBackoff, onchange: v => task.useBackoff = v })
|
||||||
editMenu[""] = {title: task.text, back: backFn};
|
editMenu[""] = {title: task.text, back: backFn};
|
||||||
E.showMenu(editMenu);
|
E.showMenu(editMenu);
|
||||||
}
|
}
|
||||||
|
|
@ -435,6 +471,7 @@ function renameTask(task, backFn) {
|
||||||
return textInput.input({text: task.text, keyboardMain: keyboardAlpha})
|
return textInput.input({text: task.text, keyboardMain: keyboardAlpha})
|
||||||
.then(text => {
|
.then(text => {
|
||||||
task.text = text
|
task.text = text
|
||||||
|
save();
|
||||||
backFn();
|
backFn();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"id" : "wrkmem",
|
||||||
|
"name" : "Working Memory Helper",
|
||||||
|
"version" : "1.00",
|
||||||
|
"description" : "Externalize your working memory to help stay on task.",
|
||||||
|
"dependencies" : {"textinput": "type"},
|
||||||
|
"icon" : "icon.png",
|
||||||
|
"type" : "app",
|
||||||
|
"tags" : "tool",
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"screenshots" : [
|
||||||
|
{"url": "screenshot.png"},
|
||||||
|
{"url": "screenshot2.png"},
|
||||||
|
{"url": "screenshot3.png"},
|
||||||
|
{"url": "screenshot4.png"},
|
||||||
|
{"url": "screenshot5.png"},
|
||||||
|
{"url": "screenshot6.png"}
|
||||||
|
],
|
||||||
|
"readme" : "README.md",
|
||||||
|
"allow_emulator": false,
|
||||||
|
"storage" : [
|
||||||
|
{"name": "wrkmem.app.js", "url": "app.js"},
|
||||||
|
{"name": "wrkmem.img", "url": "app-icon.js", "evaluate": true}
|
||||||
|
],
|
||||||
|
"data" : [{"name": "wrkmem.json"}]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.9 KiB |