Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions apps/harvester/ChangeLog
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1: first release
65 changes: 65 additions & 0 deletions apps/harvester/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Time Harvester ![](app.png)

>Digital clock with large segmented ring gauges to show accumulated fruitful time
>spent by category, and “fallow” rest time available, as well as over-rested time

The concept of this clock is to give an always-visible summary of the fruitfulness
of your day so far in a few broad categories, as well as time spent in balanced
or excessive rest. At any moment, it’s accumulating time spent in one category or
another, whether one of the configured fruitful categories, [centering/self-limiting
rest](https://intend.do/articles/centering-distractions-are-good-for-focus), or
divergent distraction. Fruitful time also accumulates a buffer
of some fraction (1/3 by default) for later resting, which can be spent as you
see fit (like Pomodoro, but more flexible). Running out of this “fallow” buffer
while doing something centering just leaves it empty (so you can eat a meal or
go to sleep), but running out while doing something divergent will count up the
deficit and show a red gauge segment for the total.

The outer ring shows fruitful categories, with dimmed colors filling out segments
for the targets you haven’t yet reached. (There are six fruitful categories below.)

![](fruitful-partial.png)

If you surpass a target for a particular fruitful category, or you run over the
fallow buffer in a divergent mode, those will appear in a ring inside the outer
one, starting from the top center and going clockwise for fruitful, and
counter-clockwise for divergent. These have no fixed duration and will be run
together with small dim margins between. (Below, you can see a few minutes in
each of three divergent categories I set up.)

![](divergent.png)

Switch modes by using the three corner buttons. If you realize you should have
switched sooner, tap the correct button again and scroll through the menu if
needed to find the last option, `(Fix start...)`. This will let you select the
number of minutes to retroactively move from the previous mode to the current,
if there’s a way to make that work.

The clock will buzz with increasing urgency as you run down the fallow buffer in
a divergent mode, and also every few minutes after that. It will also buzz in a
hopefully more pleasant way when you've hit the target for a given category of
fruitfulness.

There is one slot in the upper middle for a clock-info gauge, which you can choose
by tapping on it to highlight and using swipes left and right for lists, up and
down for items (standard behavior). It will not include any non-gauge items.

All times reset at the end of the day, which is currently assumed to be 3 AM local.

Written by: [Nathan Tuggy](https://github.com/tuggyne). For support and discussion,
please post in [this fork’s issues](https://github.com/TuggyNE/BangleApps/issues).

* Based on the [Daisy Clock](https://banglejs.com/apps/?id=daisy) and thus also [The Ring](https://banglejs.com/apps/?id=thering) proof of concept and the [Pastel clock](https://banglejs.com/apps/?q=pastel)
* Fallow time calculation based on [Third Time](https://www.lesswrong.com/posts/RWu8eZqbwgB9zaerh/third-time-a-better-way-to-work)
* Divergent/centering distinction from [Malcolm Ocean](https://intend.do/articles/centering-distractions-are-good-for-focus)
* Uses the [BloggerSansLight](https://www.1001fonts.com/rounded-fonts.html?page=3) font, which is free for commercial use

## Future Development
* Record statistics for later viewing/summaries
* Add per-weekday scheduling for different targets
* Remove triangle buttons in favor of something less visually noisy
* Support fast loading
* Allow configuring buzz patterns
* Show tick marks between the rings to scale hours
* Improve hour coloring, perhaps by configuration
* Configure coloring for fallow buffer, clock-info gauge
1 change: 1 addition & 0 deletions apps/harvester/app-icon.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

772 changes: 772 additions & 0 deletions apps/harvester/app.js

Large diffs are not rendered by default.

Binary file added apps/harvester/app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/harvester/basic-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/harvester/divergent.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/harvester/fruitful-partial.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions apps/harvester/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{ "id": "harvester",
"name": "Time Harvester",
"allow_emulator": true,
"version": "0.1",
"dependencies": {"clock_info":"module"},
"description": "Digital clock with large segmented ring gauges to show accumulated fruitful time spent by category, and 'fallow' rest time available, as well as over-rested time",
"icon": "app.png",
"type": "clock",
"tags": "clock,tool,clkinfo",
"supports" : ["BANGLEJS2"],
"screenshots": [{"url":"basic-dark.png"}],
"readme": "README.md",
"storage": [
{"name":"harvester.app.js","url":"app.js"},
{"name":"harvester.img","url":"app-icon.js","evaluate":true},
{"name":"harvester.settings.js","url":"settings.js"}
],
"data": [{"name":"harvester.json"}]
}
192 changes: 192 additions & 0 deletions apps/harvester/settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
(function(back) {
// *** XXX: Ensure these are kept in sync between settings.js and app.js ***
const storage = require('Storage');
const SETTINGS_FILE = "harvester.json";
function getDefaultSettings () {
return {
fruitful: [
{},
{
color: 'Green', fg: '#0f0', gy: '#020',
title: 'Work',
target_min: 480,
},
],
hour_color: 'Green',
hour_fg: '#0f0',
cur_mode: 0,
last_reset: null,
decentering: [
{},
{
title: 'Social Media',
fg: '#f00', gy: '#200', color: 'Red',
}
],
fallow_denominator: 3,
};
}
function loadSettings () {
var def = getDefaultSettings();
var s = storage.readJSON(SETTINGS_FILE, 1) || {};
// TODO: Add per-item normalizer fns
s.fruitful = s.fruitful || def.fruitful;
s.decentering = s.decentering || def.decentering;

s.hour_color = s.hour_color || def.hour_color;
s.hour_fg = s.hour_fg || def.hour_fg;
s.fallow_denominator = s.fallow_denominator || def.fallow_denominator;
// Converts from JSON or supplies size
s.total_sec_by_cat = new Uint16Array(s.total_sec_by_cat || 16);
s.cur_mode = s.cur_mode || def.cur_mode;
return s;
}
function saveSettings (s) {
delete s.hr_12; // TODO: Allow setting this independently
storage.write(SETTINGS_FILE, s);
}
// *** End manual sync area ***

const color_options = [
'Lavender', 'Purple', 'Deep Blue', 'Medium Blue', 'Cyan', 'Dark Green', 'Green',
'Yellow', 'Orange', 'Red', 'Brick', 'Gray', 'Blk/Wht' ];
const fg_code = [
'#f0f', '#80f', '#00f', '#08f', '#0ff', '#080', '#0f0',
'#ff0', '#f80', '#f00', '#800', '#888', null ];
const gy_code = [
'#202', '#202', '#002', '#022', '#022', '#020', '#020',
'#220', '#220', '#200', '#200', '#222', null ];

function showFruitfulMenu(curCategories) {
let submenu = { '': { title: 'Fruitful Modes', back: showMainMenu } };
let reshow = () => E.showMenu(submenu);

function categoryMenu(category) {
return {
'': { title: category.title, back: reshow },
'Color': {
value: 0 | color_options.indexOf(category.color),
min: 0, max: color_options.length - 1,
format: v => color_options[v],
onchange: v => {
category.color = color_options[v];
category.fg = fg_code[v];
category.gy = gy_code[v];
saveSettings(settings);
}
},
'Target': {
value: 0 | category.target_min, min: 15, max: 600, step: 15, wrap: true,
onchange: v => {
category.target_min = v;
saveSettings(settings);
},
},
};
}

for (let category of curCategories) {
if (!category.title) continue;
let menuCat = categoryMenu(category);
menuCat['Delete'] = () => {
E.showPrompt('Delete this category?', { title: category.title }).then(v => {
if (v) {
settings.fruitful = settings.fruitful.filter(c => c.title != category.title);
saveSettings(settings);
}
});
};
submenu[category.title] = () => E.showMenu(menuCat);
}
let iLastColor = 0 | color_options.indexOf(curCategories[curCategories.length - 1].color);
// TODO: Allow editing category titles here
let defaultTitle = `Category ${curCategories.length}`;
let newCat = { color: color_options[iLastColor + 1], title: defaultTitle };
let addMenu = categoryMenu(newCat);
addMenu['Save'] = () => {
settings.fruitful.push(newCat);
saveSettings(settings);
showFruitfulMenu(settings.fruitful);
};
submenu['(Add...)'] = () => E.showMenu(addMenu);
E.showMenu(submenu);
}

function showDivergentMenu(curCategories) {
let submenu = { '': { title: 'Divergent Modes', back: showMainMenu } };
let reshow = () => E.showMenu(submenu);

function categoryMenu(category) {
return {
'': { title: category.title, back: reshow },
'Color': {
value: 0 | color_options.indexOf(category.color),
min: 0, max: color_options.length - 1,
format: v => color_options[v],
onchange: v => {
category.color = color_options[v];
category.fg = fg_code[v];
category.gy = gy_code[v];
saveSettings(settings);
}
},
};
}

for (let category of curCategories) {
if (!category.title) continue;
let menuCat = categoryMenu(category);
menuCat['Delete'] = () => {
E.showPrompt('Delete this category?', { title: category.title }).then(v => {
if (v) {
settings.decentering = settings.decentering.filter(c => c.title != category.title);
saveSettings(settings);
}
});
};
submenu[category.title] = () => E.showMenu(menuCat);
}
let iLastColor = 0 | color_options.indexOf(curCategories[0].color);
// TODO: Allow editing category titles here
let defaultTitle = `Category ${curCategories.length}`;
let newCat = { color: color_options[iLastColor - 1], title: defaultTitle };
let addMenu = categoryMenu(newCat);
addMenu['Save'] = () => {
settings.decentering.push(newCat);
saveSettings(settings);
showDivergentMenu(settings.decentering);
};
submenu['(Add...)'] = () => E.showMenu(addMenu);
E.showMenu(submenu);
}

var settings = loadSettings();
function showMainMenu() {
let appMenu = {
'': { title: 'Time Harvester', back: back },
'Fruitful...': () => showFruitfulMenu(settings.fruitful),
'Divergent...': () => showDivergentMenu(settings.decentering),
'Hour Color': {
value: 0 | color_options.indexOf(settings.color),
min: 0, max: color_options.length - 1,
format: v => color_options[v],
onchange: v => {
settings.hour_color = color_options[v];
settings.hour_fg = fg_code[v];
saveSettings(settings);
},
},
'Fallow Ratio': {
value: 0 | settings.fallow_denominator, min: 2, max: 6, step: 0.5,
format: v => `1:${v}`,
onchange: v => {
settings.fallow_denominator = v;
saveSettings(settings);
},
},
};
E.showMenu(appMenu);
}

showMainMenu();
})
Loading