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.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 = {
|
||||
activeTask : null, taskTimeout: null, responseTimeout: null, interrupt: () => {
|
||||
if (this.taskTimeout) clearTimeout(this.taskTimeout);
|
||||
|
@ -16,7 +33,8 @@ const nudgeManager = {
|
|||
if (this.responseTimeout) clearTimeout(this.responseTimeout);
|
||||
if (this.taskTimeout) clearTimeout(this.taskTimeout);
|
||||
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);
|
||||
}, queueResponseTimeout: (defaultFn) => {
|
||||
// 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) {
|
||||
g.clearRect(Bangle.appRect);
|
||||
g.reset();
|
||||
currentMenu = menu;
|
||||
menu.render();
|
||||
menu.setUI();
|
||||
}
|
||||
|
||||
let keyboardAlpha, keyboardNum;
|
||||
let keyboardAlpha;
|
||||
if (textInput.generateKeyboard) {
|
||||
const charSet = textInput.createCharSet("ABCDEFGHIJKLMNOPQRSTUVWXYZ", ["spc", "ok", "del"]);
|
||||
keyboardAlpha = textInput.generateKeyboard(charSet);
|
||||
// keyboardNum = textInput.generateKeyboard([["1", "2", "3", "4", "5", "6", "7", "8", "9"], ["0"], "del", "ok"]);
|
||||
keyboardAlpha = textInput.generateKeyboard(charSet)
|
||||
}
|
||||
|
||||
function newTask(initialText) {
|
||||
nudgeManager.interrupt();
|
||||
initialText = initialText || "";
|
||||
textInput.input({text: initialText, keyboardMain: keyboardAlpha})
|
||||
.then(text => {
|
||||
const task = createTask(text)
|
||||
allTasks.unshift(task);
|
||||
save();
|
||||
startTask(task);
|
||||
})
|
||||
}
|
||||
|
@ -290,8 +310,8 @@ function nudge(task) {
|
|||
});
|
||||
const nudgeMenu = createMenu({
|
||||
title : "Are you on task?", titleFont: "6x8", items: [
|
||||
{text: task.text, size: 1}, {text: "On Task", size: 2, callback: () => affirmOnTask(task)}, {
|
||||
text: "Distracted", size: 2, callback: () => affirmDistracted(task)
|
||||
{text: task.text, size: 1}, {text: "On Task", size: 1, callback: () => affirmOnTask(task)}, {
|
||||
text: "Distracted", size: 1, callback: () => affirmDistracted(task)
|
||||
}
|
||||
], isHorizontal: false
|
||||
});
|
||||
|
@ -312,7 +332,8 @@ function affirmDistracted(task) {
|
|||
}
|
||||
|
||||
function concludeUnresponsive(task) {
|
||||
Bangle.buzz(250, 1).then(() => Bangle.setLCDPower(true));
|
||||
Bangle.buzz(250, 1)
|
||||
.then(() => Bangle.setLCDPower(true));
|
||||
task.unresponsiveCount++;
|
||||
task.backoffIndex = Math.max(0, task.backoffIndex - 1);
|
||||
nudgeManager.queueResponseTimeout(() => concludeUnresponsive(task))
|
||||
|
@ -335,6 +356,7 @@ function completeTask(task) {
|
|||
task.complete = true;
|
||||
removeTask(task, allTasks);
|
||||
allTasks.push(task);
|
||||
save();
|
||||
setMenu(getTaskMenu(task));
|
||||
}
|
||||
|
||||
|
@ -342,6 +364,7 @@ function restartTask(task) {
|
|||
task.complete = false;
|
||||
removeTask(task, allTasks);
|
||||
allTasks.unshift(task);
|
||||
save();
|
||||
startTask(task);
|
||||
}
|
||||
|
||||
|
@ -367,18 +390,27 @@ function createTask(text) {
|
|||
interval : 30,
|
||||
backoffIndex : 1,
|
||||
incrementalBackoffSet,
|
||||
complete : false
|
||||
complete : false,
|
||||
useBackoff: true
|
||||
};
|
||||
}
|
||||
|
||||
function getTaskMenu(task) {
|
||||
const d = new Date();
|
||||
const h = d.getHours(), m = d.getMinutes();
|
||||
const time = h + ":" + m.toString().padStart(2,0);
|
||||
const taskSwipeControls = [
|
||||
createSwipeControl(SWIPE.LEFT, "Menu", () => setMenu(mainMenu)),
|
||||
createSwipeControl(SWIPE.RIGHT, "New Task", newTask),
|
||||
createSwipeControl(SWIPE.LEFT, "Menu", () => {
|
||||
setMenu(mainMenu);
|
||||
nudgeManager.interrupt();
|
||||
}), createSwipeControl(SWIPE.RIGHT, "New Task", newTask),
|
||||
];
|
||||
const items = [];
|
||||
if (task.complete) {
|
||||
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});
|
||||
const nextTask = getNextTask(task, allTasks);
|
||||
if (nextTask) {
|
||||
|
@ -390,11 +422,12 @@ function getTaskMenu(task) {
|
|||
});
|
||||
}
|
||||
} else {
|
||||
items.push({text: task.text})
|
||||
taskSwipeControls.push(createSwipeControl(SWIPE.DOWN, "Complete", () => completeTask(task)))
|
||||
items.push({text: task.text, size: 2})
|
||||
taskSwipeControls.push(createSwipeControl(SWIPE.UP, "Complete", () => completeTask(task)))
|
||||
taskSwipeControls.push(createSwipeControl(SWIPE.DOWN, "Edit Task", () => editTask(task, () => startTask(task))))
|
||||
}
|
||||
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) {
|
||||
nudgeManager.interrupt();
|
||||
let editMenu = [];
|
||||
editMenu.push({title: "Rename", onchange: st5(() => renameTask(task, () => editTask(task, backFn)))});
|
||||
if (task.complete) {
|
||||
|
@ -427,6 +461,8 @@ function editTask(task, backFn) {
|
|||
} else {
|
||||
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};
|
||||
E.showMenu(editMenu);
|
||||
}
|
||||
|
@ -435,6 +471,7 @@ function renameTask(task, backFn) {
|
|||
return textInput.input({text: task.text, keyboardMain: keyboardAlpha})
|
||||
.then(text => {
|
||||
task.text = text
|
||||
save();
|
||||
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 |