Skip to content

New app: tev's timer #3868

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 68 commits into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
e11a905
Use Triangle Timer as a basis for new countdown timer app
ticalc-travis Feb 1, 2025
d9e0e04
Update metadata
ticalc-travis Feb 1, 2025
f5cd3e6
Initial conversion to new app and adjust display code
ticalc-travis Feb 13, 2025
37b9558
Clean up edit menus for timer-only operation
ticalc-travis Feb 15, 2025
0a0c6f4
Some first steps toward moving from TriangleTimer to PrimitiveTimer
ticalc-travis Feb 16, 2025
e8e0698
Correct display of expired (negative) timers
ticalc-travis Feb 16, 2025
b0bdd78
Try to restore timer snooze functionality
ticalc-travis Feb 19, 2025
acd92f5
Rename old tri_timer references
ticalc-travis Feb 20, 2025
1d9b902
Remove unused Triangle Timer code
ticalc-travis Feb 20, 2025
3a2b2bc
Implement swipe left/right to switch timers
ticalc-travis Feb 24, 2025
11ab414
Implement naming timers with textinput apps
ticalc-travis Mar 21, 2025
2b3ec99
Move `timeout` var to `listeners` object
ticalc-travis Mar 24, 2025
7d51581
Beginnings of display format UI
ticalc-travis Mar 27, 2025
dcfedb5
Display view mode change arrows
ticalc-travis Mar 30, 2025
4854c45
Change “view mode” terminology to “format”
ticalc-travis Mar 31, 2025
127b1b5
Finish implementation of TimerFormatView
ticalc-travis Apr 2, 2025
b6d169f
Fix bugs in UI timer update timing
ticalc-travis Apr 3, 2025
91edde0
Bit of code simplification/cleanup
ticalc-travis Apr 4, 2025
7294b04
Add accidentally omitted function (though I don't plan on using it)
ticalc-travis Apr 4, 2025
fef2f22
Adjust font sizes
ticalc-travis Apr 4, 2025
0fdd128
Simplify PrimitiveTimer.to_msec
ticalc-travis Apr 6, 2025
ac2a1c5
Switch to triple-picker for setting timers
ticalc-travis Apr 7, 2025
1a9e5db
Fix PrimitiveTimer.to_msec if value is 0
ticalc-travis Apr 8, 2025
c7a0451
Automatically assign IDs to timers so that referencing is possible
ticalc-travis Apr 9, 2025
4cbdaa2
Provision for setting chained timers in the UI
ticalc-travis Apr 12, 2025
b0ee467
Create app icon
ticalc-travis Apr 20, 2025
f14046a
Correct unintentional modifications from original sched.js
ticalc-travis Apr 22, 2025
fe04af7
Rework timer alarm handling
ticalc-travis Apr 25, 2025
931b1f6
Resolve some conceptual conflicts between chained timers and snoozing
ticalc-travis Apr 26, 2025
7a8c560
Fix further alarms not triggering while alarm alert is displayed
ticalc-travis Apr 27, 2025
bca633d
Write documentation for classes and functions; some cleanup
ticalc-travis Apr 28, 2025
c79d9fd
Display negative times in provisional names properly
ticalc-travis Apr 28, 2025
e519f18
Add widget for indicating running state of viewed timer
ticalc-travis Apr 29, 2025
36f4b79
Implement 'Current auto' display format
ticalc-travis Apr 30, 2025
391df1c
Allow horizontal font size changes for “Current auto” mode
ticalc-travis May 1, 2025
b7a4d15
Implement “Start auto” and “Time auto” formats
ticalc-travis May 1, 2025
e2b765e
Rename variable for clarity
ticalc-travis May 1, 2025
cf48e8c
Make format menu arrows easier to tap
ticalc-travis May 1, 2025
7530aa9
Framework for custom actions on phys. button and taps
ticalc-travis May 2, 2025
6fe2a36
Apparently several UIs use button for cancel rather than OK, so best …
ticalc-travis May 2, 2025
71494eb
Rework UI code so selected routines can be called from different places
ticalc-travis May 4, 2025
cd8aa69
May not be necessary but seems like a good idea?
ticalc-travis May 4, 2025
fcc9e62
Settings for quick actions (button, left/right tap)
ticalc-travis May 4, 2025
37d5677
Add some more quick actions
ticalc-travis May 4, 2025
73cd812
Fix back when accessing TimerEditMenu via quick action
ticalc-travis May 4, 2025
fcddbe1
Add options for whether to confirm reset & delete
ticalc-travis May 5, 2025
bb8403a
Oops, that does nothing
ticalc-travis May 5, 2025
2392a1b
Avoid button action trigger on long-press to exit app
ticalc-travis May 5, 2025
36b593e
Add option to return to either default app or timer on alarm
ticalc-travis May 5, 2025
48495a7
More tweaks to try to fix alarm handling
ticalc-travis May 7, 2025
bfec737
Enable auto-snooze for timer alarms
ticalc-travis May 7, 2025
b7ab9da
Replace deprecated substr() with slice()
ticalc-travis May 8, 2025
1d1e37a
Depending on setting, stop or reset timer when alarm dismissed
ticalc-travis May 8, 2025
c6afc5c
Fix issues with alarm operation, especially snoozing
ticalc-travis May 12, 2025
beeb066
Add README and app screenshot
ticalc-travis May 12, 2025
343839e
Adjust Markdown formatting
ticalc-travis May 13, 2025
3d4ba6b
Adjust app description
ticalc-travis May 13, 2025
1626615
Remove unused code
ticalc-travis May 14, 2025
7132dc8
Prototype for web interface timer editor
ticalc-travis May 17, 2025
5724b0d
Variable name & cleanup
ticalc-travis May 17, 2025
aae5857
Prepare interface.html for live usage with real data
ticalc-travis May 18, 2025
cb7204d
Defense
ticalc-travis May 18, 2025
d10d625
Clean up comments
ticalc-travis May 18, 2025
2c4432a
Merge branch 'tevtimer-interface-prototype' into tevtimer
ticalc-travis May 18, 2025
a8b27df
Fix whitespace
ticalc-travis May 18, 2025
611816f
Back out of timer app if it is running while timers are changed (so t…
ticalc-travis May 24, 2025
c07121f
Add changelog
ticalc-travis May 28, 2025
ae0b1c1
Fix ChangeLog filename
ticalc-travis May 29, 2025
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/tevtimer/ChangeLog
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.01: Official release
92 changes: 92 additions & 0 deletions apps/tevtimer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# tev's timer

This Bangle.js 2 app aims to be an ergonomic timer that features:

* Large, easy-to-read fonts
* Multiple simultaneous timer operation
* Interval and repeat timers
* A customizable three-line display

![App screenshot](screenshot.png)

## Basic usage and controls

The main timer screen appears when you start the app. The button on the lower-left of the screen starts or stops the timer. The lower-right button provides a menu for all other functions. If you have created more than one timer, you can swipe left or right above the buttons to quickly switch between them.

## Timer menu

The on-screen menu button displays the following menu items:

* **Reset:** Reset the currently displayed timer back to its starting value.
* **Timers:** Select a different timer to display. The most recently used timer is automatically moved to the top of the list.
* **Edit:** Edit the currently displayed timer
* **Format:** Customize the timer display
* **Add:** Create a new timer with the same parameters as the currently displayed one. The Edit menu will appear allowing you to adjust the newly created timer.
* **Delete:** Delete the currently displayed timer
* **Settings:** Adjust global app settings

## Editing a timer

The following parameters can be adjusted individually for each timer by displaying that timer on the main screen and then selecting **Edit** from the menu:

* **Name:** (available when a keyboard app is installed) Allows you to assign a custom name to the timer to display in menus
* **Start:** Set the starting time of the timer
* **At end:** Allows for creating interval or repeat timers. Selecting “Stop” causes the timer to simply stop when it expires and the resulting alert is dismissed. Selecting a timer here will cause the selected timer to reset and automatically begin counting down once this timer expires. See “Chained timers” below.
* **Vibrate pattern:** Choose the vibration pattern for the alert when the timer expires
* **Buzz count:** Choose the number of times the vibration pattern signals when the timer expires before silencing itself

## Chained timers

When a timer reaches its end, it can be configured to automatically start another timer, forming a chain of timers. For instance, if you create a timer A and a timer B, you can set timer A's **at end** setting to point to timer B. Then when timer A expires, timer B will automatically start. You can then edit timer B's **at end** setting to auto-start yet another timer, and so on. This procedure can be used to create an interval timer. You can also chain a timer to itself to create a repeating timer. If you set timer A's **at end** setting to timer A itself, timer A will repeat indefinitely, sounding an alert each time it expires, until you manually pause the timer. You can furthermore chain a series of timers back to itself to create a repeating set of intervals: timer A to timer B to timer C back to timer A, for instance.

## Display format

Selecting Format from the menu allows you to customize the display. The display has three lines, and each one can be set to one of the following general modes:

* **Current:** Shows the current timer position as it counts down
* **Start:** Shows the starting point of the timer
* **Time:** Shows the current time of day
* **Name:** Shows the name of the timer (if set by a keyboard app; otherwise displays an auto-generated name based on the timer's starting point and current position)

The Current, Start, and Time modes each have three subtypes allowing you to set the precision of the displayed time:

* **HMS:** Hours, minutes, and seconds
* **HM:** Hours and minutes only
* **Auto:** Displays only hours and minutes while the screen is locked; when unlocked, automatically displays the seconds too

The primary benefit to choosing a mode that hides the seconds is to reduce battery consumption when the timer is being displayed for an extended period of time.

## App settings

The Settings option in the menu contains the following options which apply to all timers or to the app as a whole.

### Button, Tap left, and Tap right

Choose a shortcut action to perform when the physical button is pressed (after the screen is unlocked), when the left side of the touch screen (above the buttons when the main time screen is displayed) is tapped, and when the right side of the touch screen is tapped, respectively. By default, pressing the button toggles the timer between running and paused, and tapping either side of the screen brings up the screen for setting the starting time of the timer. These actions can be customized:

* **Start/stop:** Toggle the timer between paused and running
* **Reset:** Reset the timer
* **Timers:** Display the timer selection menu to display a different timer
* **Edit timer:** Display the timer edit menu
* **Edit start:** Display the timer start time edit screen
* **Format:** Display the display format selection screen

### Confirm reset

Normally when you choose to reset a timer, a menu prompts you to confirm the reset if the timer has not expired yet (the Auto option). This helps protect against accidentally resetting the timer. If you prefer to always see this confirmation menu, choose Always; if you would rather reset always happen instantly, choose Never.

### Confirm delete

Likewise, to protect against accidentally deleting a timer a confirmation menu appears when you select Delete from the menu. Setting this option to Never eliminates this extra step.

### On alarm go to

If set to Clock (default), when a timer expires and its alert is displayed, dismissing the alert will return to the default app (normally the preferred clock app). Setting this option to Timer will automatically restart the Tev Timer app instead.

### Auto reset

When a timer expires, it will by default begin counting up a time that represents the amount of time passed before its alarm was acknowledged. If you check this option, the timer will be reset back to its starting point instead, saving you the trouble of doing so manually before using the timer again.

## Timer alerts

When a timer expires, it will display an alert like the ones produced by the standard Scheduler app. For a timer that is not chained, or the last timer in a chain, two buttons “OK” and “Snooze” appear when an alert fires. “OK” completely dismisses the alert, while “Snooze” temporarily dismisses it (it will recur after the snooze interval configured in the Scheduler settings). For chained timers, the options are instead “OK” and “Halt”. “OK” dismisses the individual alert while allowing the next chained timer to continue running in the background, eventually sounding its alert. “Halt” stops the timer and cancels the chaining action, and the display will return to the timer that was running at the point when the “Halt” button was tapped. If you accidentally create a repeating timer that alerts too frequently and makes it impossible to use the Bangle.js watch normally, quickly tap the “Halt” button to stop the chaining action and regain control.
156 changes: 156 additions & 0 deletions apps/tevtimer/alarm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Derived from `sched.js` from the `sched` app, with modifications
// for features unique to the `tevtimer` app.

// Chances are boot0.js got run already and scheduled *another*
// 'load(sched.js)' - so let's remove it first!
if (Bangle.SCHED) {
clearInterval(Bangle.SCHED);
delete Bangle.SCHED;
}

const tt = require('tevtimer');

function showAlarm(alarm) {
// Alert the user of the alarm and handle the response

const settings = require("sched").getSettings();
const timer = tt.TIMERS[tt.find_timer_by_id(alarm.id)];
if (timer === undefined) {
console.error("tevtimer: unable to find timer with ID " + alarm.id);
return;
}
let message = timer.display_name() + '\n' + alarm.msg;

// Altering alarms from here is tricky. Making changes to timers
// requires calling tt.update_system_alarms() to update the system
// alarm list to reflect the new timer state. But that means we need
// to retrieve the alarms again from sched.getAlarms() before
// changing them ourselves or else we risk overwriting the changes.
// Likewise, after directly modifying alarms, we need to write them
// back with sched.setAlarms() before doing anything that will call
// tt.update_system_alarms(), or else the latter will work with an
// outdated list of alarms.

// If there's a timer chained from this one, start it (only for
// alarms not in snoozed status)
var isChainedTimer = false;
var chainTimer = null;
if (timer.chain_id !== null && alarm.ot === undefined) {
chainTimer = tt.TIMERS[tt.find_timer_by_id(timer.chain_id)];
if (chainTimer !== undefined) {
chainTimer.reset();
chainTimer.start();
tt.set_last_viewed_timer(chainTimer);
isChainedTimer = true;

// Update system alarm list
tt.update_system_alarms();
alarms = require("sched").getAlarms();
} else {
console.warn("tevtimer: unable to find chained timer with ID " + timer.chain_id);
}
}

if (alarm.msg) {
message += "\n" + alarm.msg;
} else {
message = atob("ACQswgD//33vRcGHIQAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAAAP/wAAAAAAAAAP/wAAAAAAAAAqqoAPAAAAAAqqqqoP8AAAAKqqqqqv/AAACqqqqqqq/wAAKqqqlWqqvwAAqqqqlVaqrAACqqqqlVVqqAAKqqqqlVVaqgAKqaqqlVVWqgAqpWqqlVVVqoAqlWqqlVVVaoCqlV6qlVVVaqCqVVfqlVVVWqCqVVf6lVVVWqKpVVX/lVVVVqqpVVV/+VVVVqqpVVV//lVVVqqpVVVfr1VVVqqpVVVfr1VVVqqpVVVb/lVVVqqpVVVW+VVVVqqpVVVVVVVVVqiqVVVVVVVVWqCqVVVVVVVVWqCqlVVVVVVVaqAqlVVVVVVVaoAqpVVVVVVVqoAKqVVVVVVWqgAKqlVVVVVaqgACqpVVVVVqqAAAqqlVVVaqoAAAKqqVVWqqgAAACqqqqqqqAAAAAKqqqqqgAAAAAAqqqqoAAAAAAAAqqoAAAAA==") + " " + message
}

Bangle.loadWidgets();
Bangle.drawWidgets();

// buzzCount should really be called buzzRepeat, so subtract 1
let buzzCount = timer.buzz_count - 1;

// Alarm options for non-chained timer are OK (dismiss the alarm) and
// Snooze (retrigger the alarm after a delay).
// Alarm options for chained timer are OK (dismiss) and Halt (dismiss
// and pause the triggering timer).
let promptButtons = isChainedTimer
? { 'Halt': 'halt', 'OK': 'ok' }
: { 'Snooze': 'snooze', 'OK': 'ok' };
E.showPrompt(message, {
title: 'tev timer',
buttons: promptButtons,
}).then(function (action) {
buzzCount = 0;

if (action === 'snooze') {
if (alarm.ot === undefined) {
alarm.ot = alarm.t;
}
let time = new Date();
let currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000);
alarm.t = currentTime + settings.defaultSnoozeMillis;
alarm.t %= 86400000;
require("sched").setAlarms(alarms);

Bangle.emit("alarmSnooze", alarm);
}
if (action === 'ok' || action === 'halt') {
let index = alarms.indexOf(alarm);
if (index !== -1) {
alarms.splice(index, 1);
require("sched").setAlarms(alarms);
}
if (timer !== chainTimer) {
timer.pause();
if (tt.SETTINGS.auto_reset) {
timer.reset();
}
}
}
if (action === 'halt') {
chainTimer.pause();
}
tt.update_system_alarms();
alarms = require("sched").getAlarms();

Bangle.emit("alarmDismiss", alarm);

require("sched").setAlarms(alarms);

if (action === 'halt' || tt.SETTINGS.alarm_return) {
load('tevtimer.app.js');
} else {
load();
}
});

function buzz() {
// Handle buzzing and screen unlocking

if (settings.unlockAtBuzz) {
Bangle.setLocked(false);
}

const pattern = timer.vibrate_pattern || settings.defaultTimerPattern;
console.log('buzz: ' + pattern);
console.log('buzzCount: ' + buzzCount);
require("buzz").pattern(pattern).then(() => {
if (buzzCount == null || buzzCount--) {
setTimeout(buzz, settings.buzzIntervalMillis);
} else if (alarm.as) { // auto-snooze
// buzzCount should really be called buzzRepeat, so subtract 1
buzzCount = timer.buzz_count - 1;
setTimeout(buzz, settings.defaultSnoozeMillis);
}
});
}

if ((require("Storage").readJSON("setting.json", 1) || {}).quiet > 1)
return;

buzz();
}

let alarms = require("sched").getAlarms();
let active = require("sched").getActiveAlarms(alarms);
if (active.length) {
// if there's an alarm, show it
showAlarm(active[0]);
} else {
// otherwise just go back to default app
setTimeout(load, 100);
}
1 change: 1 addition & 0 deletions apps/tevtimer/app-icon.js

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

Loading