diff --git a/apps/wrkmem/Changelog b/apps/wrkmem/Changelog new file mode 100644 index 000000000..55caa0461 --- /dev/null +++ b/apps/wrkmem/Changelog @@ -0,0 +1 @@ +1.00: Implement Working Memory Helper app \ No newline at end of file diff --git a/apps/wrkmem/README.md b/apps/wrkmem/README.md new file mode 100644 index 000000000..3d57dff9a --- /dev/null +++ b/apps/wrkmem/README.md @@ -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. + +![screenshot](screenshot.png) ![screenshot](screenshot2.png) ![screenshot](screenshot3.png) ![screenshot](screenshot4.png) +![screenshot](screenshot5.png) ![screenshot](screenshot6.png) + +## 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. \ No newline at end of file diff --git a/apps/wrkmem/app-icon.js b/apps/wrkmem/app-icon.js new file mode 100644 index 000000000..edc5d96e7 --- /dev/null +++ b/apps/wrkmem/app-icon.js @@ -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=")) \ No newline at end of file diff --git a/apps/remindr/app.js b/apps/wrkmem/app.js similarity index 88% rename from apps/remindr/app.js rename to apps/wrkmem/app.js index f2003f055..4548f4f8d 100644 --- a/apps/remindr/app.js +++ b/apps/wrkmem/app.js @@ -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,14 +332,15 @@ 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)) } function showTempMessage(text, title, thenFn) { - E.showMessage(text,{title}); + E.showMessage(text, {title}); setTimeout(() => { Bangle.setLocked(true); thenFn(); @@ -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(); }) } diff --git a/apps/wrkmem/icon.png b/apps/wrkmem/icon.png new file mode 100644 index 000000000..23e1df523 Binary files /dev/null and b/apps/wrkmem/icon.png differ diff --git a/apps/wrkmem/metadata.json b/apps/wrkmem/metadata.json new file mode 100644 index 000000000..040fbc750 --- /dev/null +++ b/apps/wrkmem/metadata.json @@ -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"}] +} \ No newline at end of file diff --git a/apps/wrkmem/screenshot.png b/apps/wrkmem/screenshot.png new file mode 100644 index 000000000..7145bc8a7 Binary files /dev/null and b/apps/wrkmem/screenshot.png differ diff --git a/apps/wrkmem/screenshot2.png b/apps/wrkmem/screenshot2.png new file mode 100644 index 000000000..3a61529e3 Binary files /dev/null and b/apps/wrkmem/screenshot2.png differ diff --git a/apps/wrkmem/screenshot3.png b/apps/wrkmem/screenshot3.png new file mode 100644 index 000000000..30776d332 Binary files /dev/null and b/apps/wrkmem/screenshot3.png differ diff --git a/apps/wrkmem/screenshot4.png b/apps/wrkmem/screenshot4.png new file mode 100644 index 000000000..582c2c92b Binary files /dev/null and b/apps/wrkmem/screenshot4.png differ diff --git a/apps/wrkmem/screenshot5.png b/apps/wrkmem/screenshot5.png new file mode 100644 index 000000000..aa9165e38 Binary files /dev/null and b/apps/wrkmem/screenshot5.png differ diff --git a/apps/wrkmem/screenshot6.png b/apps/wrkmem/screenshot6.png new file mode 100644 index 000000000..fd3e1a36b Binary files /dev/null and b/apps/wrkmem/screenshot6.png differ