Merge branch 'espruino:master' into master

pull/3236/head
naden 2024-03-04 13:09:11 +01:00 committed by GitHub
commit 64ad1c1389
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
128 changed files with 1799 additions and 200 deletions

View File

@ -22,7 +22,7 @@
<body>
<header class="navbar-primary navbar">
<section class="navbar-section" >
<a href="https://banglejs.com" target="_blank" class="navbar-brand mr-2" ><img src="img/banglejs-logo-sml.png" alt="Bangle.js">
<a href="https://banglejs.com" target="_blank" class="navbar-brand mr-2"><img src="img/banglejs-logo-small.svg" alt="Bangle.js">
<div>App Loader</div></a>
<!-- <a href="#" class="btn btn-link">...</a> -->
</section>

View File

@ -144,7 +144,7 @@
],
"no-constant-condition": "off",
"no-delete-var": "off",
"no-empty": "off",
"no-empty": ["warn", { "allowEmptyCatch": true }],
"no-global-assign": "off",
"no-inner-declarations": "off",
"no-prototype-builtins": "off",

View File

@ -1 +1,2 @@
0.01: Initial version
0.02: Add settings page; Add line break to update message

View File

@ -1,10 +1,13 @@
# a_dndtoggle - Toggle Quiet Mode of the watch
When Quiet mode is off, just start this app to set quiet mode. Start it again to turn off quiet mode.
Use the app settings to choose which quiet mode you prefer ("Alarms" or "Silent"). Default is "Silent".
Work in progress.
#ToDo
Settings page, current status indicator.
Current status indicator
## Creator

View File

@ -6,11 +6,14 @@ let current = 0|bSettings.quiet;
//1 alarms
//2 silent
const dndSettings =
require('Storage').readJSON("a_dndtoggle.settings.json", true) || {};
console.log("old: " + current);
switch (current) {
case 0:
bSettings.quiet = 2;
bSettings.quiet = dndSettings.mode || 2;
Bangle.buzz();
setTimeout('Bangle.buzz();',500);
break;
@ -29,7 +32,7 @@ switch (current) {
console.log("new: " + bSettings.quiet);
E.showMessage(modeNames[current] + " -> " + modeNames[bSettings.quiet]);
E.showMessage(modeNames[current] + " -> \n" + modeNames[bSettings.quiet]);
setTimeout('exitApp();', 2000);

View File

@ -2,14 +2,18 @@
"id": "a_dndtoggle",
"name": "a_dndtoggle - Toggle Quiet Mode of the watch",
"shortName": "A_DND Toggle",
"version": "0.01",
"version": "0.02",
"description": "Toggle Quiet Mode of the watch just by starting this app.",
"icon": "a_dndtoggle.png",
"type": "app",
"tags": "tool",
"supports": ["BANGLEJS","BANGLEJS2"],
"data" : [
{"name":"a_dndtoggle.settings.json"}
],
"storage": [
{"name":"a_dndtoggle.app.js","url":"a_dndtoggle.app.js"},
{"name":"a_dndtoggle.settings.js","url":"settings.js"},
{"name":"a_dndtoggle.img","url":"app-icon.js","evaluate":true}
],
"readme": "README.md"

View File

@ -0,0 +1,33 @@
(function(back) {
const settings =
require('Storage').readJSON("a_dndtoggle.settings.json", true) || {};
function updateSettings() {
require('Storage').writeJSON("a_dndtoggle.settings.json", settings);
}
function buildMainMenu(){
// 0-Noisy is only a placeholder so that the other values map to the Bangle quiet mode options
const modes = [/*LANG*/"Noisy",/*LANG*/"Alarms",/*LANG*/"Silent"];
let mainmenu = {
'': { 'title': 'A_DND Toggle' },
'< Back': back,
/*LANG*/"Quiet Mode": {
value: settings.mode || 2,
min: 1, // don't allow choosing 0-Noisy
max: modes.length - 1,
format: v => modes[v],
onchange: v => {
settings.mode = v;
updateSettings();
}
}
};
return mainmenu;
}
E.showMenu(buildMainMenu());
});

View File

@ -104,7 +104,6 @@ Bangle.on('lcdPower',on=>{
if (on) {
secondInterval = setInterval(draw, 1000);
draw(); // draw immediately
}else{
}
});
Bangle.on('lock',on=>{

View File

@ -6,3 +6,4 @@
0.06: Formatting
0.07: Added potato GLaDOS and quote functionality when you tap her
0.08: Fixed drawing issues with the quotes and added more
0.09: Minor code improvements

View File

@ -270,8 +270,7 @@ function queueDraw() {
function draw() {
if (pause){}
else{
if (!pause){
// get date
var d = new Date();
var da = d.toString().split(" ");

View File

@ -2,7 +2,7 @@
"id": "aptsciclk",
"name": "Apeture Science Clock",
"shortName":"AptSci Clock",
"version": "0.08",
"version": "0.09",
"description": "A clock based on the portal series",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],

1
apps/autoreset/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

21
apps/autoreset/README.md Normal file
View File

@ -0,0 +1,21 @@
# Auto Reset
Sets a timeout to load the clock face. The timeout is stopped and started again upon user input.
## Usage
Install with app loader and Auto Reset will run in background. If you don't interact with the watch it will time out to the clock face after 10 minutes.
## TODO
- Add settings page
- set how many minutes the timeout should count down.
- whitelist/blacklist for apps.
## Requests
Mention @thyttan in an issue on the espruino/BangleApps repo for bug reports and feature requests.
## Creator
thyttan

BIN
apps/autoreset/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

20
apps/autoreset/boot.js Normal file
View File

@ -0,0 +1,20 @@
{
let timeoutAutoreset;
let resetTimeoutAutoreset = (force)=>{
if (timeoutAutoreset) clearTimeout(timeoutAutoreset);
setTimeout(()=>{ // Short outer timeout to make sure we have time to leave clock face before checking `Bangle.CLOCK!=1`.
if (Bangle.CLOCK!=1) { // Only add timeout if not already on clock face.
timeoutAutoreset = setTimeout(()=>{
if (Bangle.CLOCK!=1) Bangle.showClock();
}, 10*60*1000);
}
},200);
};
Bangle.on('touch', resetTimeoutAutoreset);
Bangle.on('swipe', resetTimeoutAutoreset);
Bangle.on('message', resetTimeoutAutoreset);
setWatch(resetTimeoutAutoreset, BTN, {repeat:true, edge:'rising'});
if (Bangle.CLOCK!=1) resetTimeoutAutoreset();
}

View File

@ -0,0 +1,13 @@
{ "id": "autoreset",
"name": "Auto Reset",
"version":"0.01",
"description": "Sets a timeout to load the clock face. The timeout is stopped and started again upon user input.",
"icon": "app.png",
"type": "bootloader",
"tags": "system",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"autoreset.boot.js","url":"boot.js"}
]
}

View File

@ -1,3 +1,4 @@
0.01: Added app
0.02: Removed unneeded squares
0.03: Added settings with fullscreen option
0.03: Added setting for fullscreen option
0.04: Added settings to hide unused squares and show date

View File

@ -1,11 +1,15 @@
var settings = Object.assign({
fullscreen: false,
fullscreen: false,
hidesq: false,
showdate: false,
}, require('Storage').readJSON("binaryclk.json", true) || {});
function draw() {
var dt = new Date();
var h = dt.getHours(), m = dt.getMinutes();
var h = dt.getHours(), m = dt.getMinutes(), d = dt.getDate();
const t = [];
t[0] = Math.floor(h/10);
t[1] = Math.floor(h%10);
t[2] = Math.floor(m/10);
@ -17,25 +21,44 @@ function draw() {
let i = 0;
var gap = 8;
var mgn = 20;
if (settings.fullscreen) {
gap = 12;
mgn = 0;
}
const sq = 29;
var pos = sq + gap;
for (let r = 3; r >= 0; r--) {
for (let c = 0; c < 4; c++) {
if (t[c] & Math.pow(2, r)) {
g.fillRect(mgn/2 + gap + c * pos, mgn + gap + i * pos, mgn/2 + gap + c * pos + sq, mgn + gap + i * pos + sq);
g.fillRect(Math.floor(mgn/2) + gap + c * pos, mgn + gap + i * pos, Math.floor(mgn/2) + gap + c * pos + sq, mgn + gap + i * pos + sq);
} else {
g.drawRect(mgn/2 + gap + c * pos, mgn + gap + i * pos, mgn/2 + gap + c * pos + sq, mgn + gap + i * pos + sq);
g.drawRect(Math.floor(mgn/2) + gap + c * pos, mgn + gap + i * pos, Math.floor(mgn/2) + gap + c * pos + sq, mgn + gap + i * pos + sq);
}
}
i++;
}
g.clearRect(mgn/2 + gap, mgn + gap, mgn/2 + gap + sq, mgn + 2 * gap + 2 * sq);
g.clearRect(mgn/2 + 3 * gap + 2 * sq, mgn + gap, mgn/2 + 3 * gap + 3 * sq, mgn + gap + sq);
var c1sqhide = 0;
var c3sqhide = 0;
if (settings.hidesq) {
c1sqhide = 2;
c3sqhide = 1;
}
if (settings.hidesq) {
g.clearRect(Math.floor(mgn/2), mgn, Math.floor(mgn/2) + pos, mgn + c1sqhide * pos);
g.clearRect(Math.floor(mgn/2) + 2 * pos + gap, mgn, Math.floor(mgn/2) + 3 * pos, mgn + c3sqhide * pos);
}
if (settings.showdate) {
g.setFontAlign(0, 0);
g.setFont("Vector",20);
g.drawRect(Math.floor(mgn/2) + gap, mgn + gap, Math.floor(mgn/2) + gap + sq, mgn + gap + sq);
g.drawString(d, Math.ceil(mgn/2) + gap + Math.ceil(sq/2) + 1, mgn + gap + Math.ceil(sq/2) + 1);
}
}
g.clear();

View File

@ -1,8 +1,8 @@
{
"id": "binaryclk",
"name": "Bin Clock",
"version": "0.03",
"description": "Clock face to show binary time in 24 hr format",
"version": "0.04",
"description": "Clock face to show binary time in 24 hour format",
"icon": "app-icon.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -2,6 +2,8 @@
var FILE = "binaryclk.json";
var settings = Object.assign({
fullscreen: false,
hidesq: false,
showdate: false,
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings() {
@ -16,7 +18,21 @@
onchange: v => {
settings.fullscreen = v;
writeSettings();
}
},
},
'Hide Squares': {
value: settings.hidesq,
onchange: v => {
settings.hidesq = v;
writeSettings();
},
},
'Show Date': {
value: settings.showdate,
onchange: v => {
settings.showdate = v;
writeSettings();
},
},
});
})

4
apps/c25k/ChangeLog Normal file
View File

@ -0,0 +1,4 @@
0.01: New App!
0.02: Add rep info to time screen
0.03: Add option to pause/resume workout (Bangle.js 1 only)
0.04: Add possibility of creating a custom exercise

96
apps/c25k/README.md Normal file
View File

@ -0,0 +1,96 @@
# C25K
Unofficial app for the Couch to 5k training plan.
From being a couch-potato to running 5k in 8 weeks!
Each week has 3 training days, ideally with rest days between them.
Each day's programme consists of running for a certain time with occasional walking/resting phases.
When walking is part of the programme, the (run+walk) stages are repeated a number of times.
![](c25k-scrn1.png)
![](c25k-scrn2.png)
![](c25k-scrn3.png)
## Features
- Show remaining time in seconds for each phase
- Vibrates on phase changes
- Keeps screen on to allow quickly glancing at the time while running
- Shows time on button press
## Usage
If you know the week and day of the programme you'd like to start, set `Week` and `Day` to the appropriate values in the main menu and press `Start`.
**Example**:
To start the programme of the **second day** of **week 4**:
![](c25k-scrn4.png)
---
Alternatively, you can go to the `View plan` menu to look at all the programmes and select the one you'd like to start.
**Example**:
Go to the `View plan` menu:
![](c25k-scrn5.png)
Select the programme to start it:
![](c25k-scrn6.png)
---
The format of the `View menu` is `w{week}d{day}(r:{run mins}|w:{walk mins}|x{number of reps})`.
For example `w6d1(r:6|w:3|x2)` means:
`it's the programme of day 1 on week 6`,
`it consists of running for 6 minutes`,
`followed by walking for 3`,
`done 2 times back to back`.
---
### Create a custom excercise
Under the `Custom run` menu, it's possible to create a custom excercise.
![](c25k-scrn9.png)
Some important details/limitations:
- To disable walking: set `walk` to `0`
- When walking is set to `0`, the repetition count is set to `1`.
- When repetition is set to `2` or higher, `walk` is set to `1`.
**Unfortunately, the value in the menu do not update to reflect the changes, so I recommend setting the values with the rules above in mind.**
---
### Show extra info:
If you ever need to peek at the time, just press the middle (or only) physical button on the watch:
![](c25k-scrn7.png)
This view also shows `current rep / total reps` at the top.
---
### Pause/resume workout:
**This is currently only available on Bangle.js 1.**
Press the top button to pause or to resume the active programme:
![](c25k-scrn8.png)
---
## Disclaimer
This app was hacked together in a day with no JS knowledge.
It's probably inefficient and buggy, but it does what I needed it to do: allow me to follow the C25K programme without a phone.
The app was designed with a Bangle.js 1 in mind, as that's the one I have.
It *should* work fine on the Bangle.js 2, but I couldn't test it on real hardware.
---
Made with <3 by [Erovia](https://github.com/Erovia/BangleApps/tree/c25k)

1
apps/c25k/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4X/AoPk9G9gsj14lZhWq0AEBgtVqALmhQJBAQMFBIICCBc4ADBQYLnAQQKEBcibETQIABHggLiAEQqEh/wgACCBcpXDBAIKDBcqJDh//BQYLkHwg7GBcY7FU5ALgAEQA="))

290
apps/c25k/app.js Normal file
View File

@ -0,0 +1,290 @@
var week = 1; // Stock plan: programme week
var day = 1; // Stock plan: programe day
var run = 1; // Custom plan: running time
var walk = 0; // Custom plan: walking time
var reps = 1; // Custom plan: repetition count
var time; // To store the date
var loop; // To store how many times we will have to do a countdown
var rep; // The current rep counter
var counter; // The seconds counter
var currentMode; // Either "run" or "walk"
var mainInterval; // Ticks every second, checking if a new countdown is needed
var activityInterval; // Ticks every second, doing the countdown
var extraInfoWatch; // Watch for button presses to show additional info
var paused = false; // Track pause state
var pauseOrResumeWatch; // Watch for button presses to pause/resume countdown
var defaultFontSize = (process.env.HWVERSION == 2) ? 7 : 9; // Default font size, Banglejs 2 has smaller
var activityBgColour; // Background colour of current activity
var currentActivity; // To store the current activity
function outOfTime() {
buzz();
// Once we're done
if (loop == 0) {
clearWatch(extraInfoWatch); // Don't watch for button presses anymore
if (pauseOrResumeWatch) clearWatch(pauseOrResumeWatch); // Don't watch for button presses anymore
g.setBgColor("#75C0E0"); // Blue background for the "Done" text
drawText("Done", defaultFontSize); // Write "Done" to screen
g.reset();
setTimeout(E.showMenu, 5000, mainmenu); // Show the main menu again after 5secs
clearInterval(mainInterval); // Stop the main interval from starting a new activity
mainInterval = undefined;
currentMode = undefined;
}
}
// Buzz 3 times on state transitions
function buzz() {
Bangle.buzz(500)
.then(() => new Promise(resolve => setTimeout(resolve, 200)))
.then(() => Bangle.buzz(500))
.then(() => new Promise(resolve => setTimeout(resolve, 200)))
.then(() => Bangle.buzz(500));
}
function drawText(text, size){
g.clear();
g.setFontAlign(0, 0); // center font
g.setFont("6x8", size);
g.drawString(text, g.getWidth() / 2, g.getHeight() / 2);
}
function countDown() {
if (!paused) {
var text = "";
var size = defaultFontSize;
if (time) {
var total = ("walk" in currentActivity) ? currentActivity.repetition : 1;
text += rep + "/" + total + "\n"; // Show the current/total rep count when time is shown
size -= 2; // Use smaller font size to fit everything nicely on the screen
}
text += (currentMode === "run") ? "Run\n" + counter : "Walk\n" + counter; // Switches output text
if (time) text += "\n" + time;
drawText(text, size); // draw the current mode and seconds
Bangle.setLCDPower(1); // keep the watch LCD lit up
counter--; // Reduce the seconds
// If the current activity is done
if (counter < 0) {
clearInterval(activityInterval);
activityInterval = undefined;
outOfTime();
return;
}
}
}
function startTimer() {
// If something is already running, do nothing
if (activityInterval) return;
// Switches between the two modes
if (!currentMode || currentMode === "walk") {
currentMode = "run";
rep++; // Increase the rep counter every time a "run" activity starts
counter = currentActivity.run * 60;
activityBgColour = "#ff5733"; // Red background for running
}
else {
currentMode = "walk";
counter = currentActivity.walk * 60;
activityBgColour = "#4da80a"; // Green background for walking
}
g.setBgColor(activityBgColour);
countDown();
if (!activityInterval) {
loop--; // Reduce the number of iterations
activityInterval = setInterval(countDown, 1000); // Start a new activity
}
}
function showTime() {
if (time) return; // If clock is already shown, don't do anything even if the button was pressed again
// Get the time and format it with a leading 0 if necessary
var d = new Date();
var h = d.getHours();
var m = d.getMinutes();
time = h + ":" + m.toString().padStart(2, 0);
setTimeout(function() { time = undefined; }, 5000); // Hide clock after 5secs
}
// Populate the PLAN menu
function populatePlan() {
for (var i = 0; i < PLAN.length; i++) {
for (var j = 0; j < PLAN[i].length; j++) {
// Ever line will have the following format:
// w{week}d{day}(r:{run mins}|w:{walk mins}|x{number of reps})
var name = "w" + (i + 1) + "d" + (j + 1);
if (process.env.HWVERSION == 2) name += "\n"; // Print in 2 lines to accomodate the Bangle.js 2 screen
name += "(r:" + PLAN[i][j].run;
if ("walk" in PLAN[i][j]) name += "|w:" + PLAN[i][j].walk;
if ("repetition" in PLAN[i][j]) name += "|x" + PLAN[i][j].repetition;
name += ")";
// Each menu item will have a function that start the program at the selected day
planmenu[name] = getFunc(i, j);
}
}
}
// Helper function to generate functions for the activePlan menu
function getFunc(i, j) {
return function() {
currentActivity = PLAN[i][j];
startActivity();
};
}
function startActivity() {
loop = ("walk" in currentActivity) ? currentActivity.repetition * 2 : 1;
rep = 0;
E.showMenu(); // Hide the main menu
extraInfoWatch = setWatch(showTime, (process.env.HWVERSION == 2) ? BTN1 : BTN2, {repeat: true}); // Show the clock on button press
if (process.env.HWVERSION == 1) pauseOrResumeWatch = setWatch(pauseOrResumeActivity, BTN1, {repeat: true}); // Pause or resume on button press (Bangle.js 1 only)
buzz();
mainInterval = setInterval(function() {startTimer();}, 1000); // Check every second if we need to do something
}
// Pause or resume current activity
function pauseOrResumeActivity() {
paused = !paused;
buzz();
if (paused) {
g.setBgColor("#fdd835"); // Yellow background for pause screen
drawText("Paused", (process.env.HWVERSION == 2) ? defaultFontSize - 3 : defaultFontSize - 2); // Although the font size is configured here, this feature does not work on Bangle.js 2 as the only physical button is tied to the extra info screen already
}
else {
g.setBgColor(activityBgColour);
}
}
const PLAN = [
[
{"run": 1, "walk": 1.5, "repetition": 8},
{"run": 1, "walk": 1.5, "repetition": 8},
{"run": 1, "walk": 1.5, "repetition": 8},
],
[
{"run": 1.5, "walk": 2, "repetition": 6},
{"run": 1.5, "walk": 2, "repetition": 6},
{"run": 1.5, "walk": 2, "repetition": 6},
],
[
{"run": 2, "walk": 2, "repetition": 5},
{"run": 2.5, "walk": 2.5, "repetition": 4},
{"run": 2.5, "walk": 2.5, "repetition": 4},
],
[
{"run": 3, "walk": 2, "repetition": 5},
{"run": 3, "walk": 2, "repetition": 5},
{"run": 4, "walk": 2.5, "repetition": 3},
],
[
{"run": 5, "walk": 2, "repetition": 3},
{"run": 8, "walk": 5, "repetition": 2},
{"run": 20},
],
[
{"run": 6, "walk": 3, "repetition": 2},
{"run": 10, "walk": 3, "repetition": 2},
{"run": 25},
],
[
{"run": 25},
{"run": 25},
{"run": 25},
],
[
{"run": 30},
{"run": 30},
{"run": 30},
],
];
var customRun = {"run": 1};
// Main menu
var mainmenu = {
"": { "title": "-- C25K --" },
"Week": {
value: week,
min: 1, max: PLAN.length, step: 1,
onchange : v => { week = v; }
},
"Day": {
value: day,
min: 1, max: 3, step: 1,
onchange: v => { day = v; }
},
"View plan": function() { E.showMenu(planmenu); },
"Custom run": function() { E.showMenu(custommenu); },
"Start": function() {
currentActivity = PLAN[week - 1][day -1];
startActivity();
},
"Exit": function() { load(); },
};
// Plan view
var planmenu = {
"": { title: "-- Plan --" },
"< Back": function() { E.showMenu(mainmenu);},
};
// Custom view
var custommenu = {
"": { title : "-- Cust. run --" },
"< Back": function() { E.showMenu(mainmenu);},
"Run (mins)": {
value: run,
min: 1, max: 150, step: 1,
wrap: true,
onchange: v => { customRun.run = v; }
},
"Walk (mins)": {
value: walk,
min: 0, max: 10, step: 1,
onchange: v => {
if (v > 0) {
if (reps == 1) { reps = 2; } // Walking only makes sense with multiple reps
customRun.repetition = reps;
customRun.walk = v;
}
else {
// If no walking, delete both the reps and walk data
delete customRun.repetition;
delete customRun.walk;
}
walk = v;
}
},
"Reps": {
value: reps,
min: 1, max: 10, step: 1,
onchange: v => {
if (v > 1) {
if (walk == 0) { walk = 1; } // Multiple reps only make sense with walking phases
customRun.walk = walk;
customRun.repetition = v;
}
else {
// If no multiple reps, delete both the reps and walk data
delete customRun.repetition;
delete customRun.walk;
}
reps = v;
}
},
"Start": function() { currentActivity = customRun; startActivity(); }
};
// Populate the activePlan menu view
populatePlan();
// Actually display the menu
E.showMenu(mainmenu);

BIN
apps/c25k/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

BIN
apps/c25k/c25k-scrn1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
apps/c25k/c25k-scrn2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
apps/c25k/c25k-scrn3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
apps/c25k/c25k-scrn4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
apps/c25k/c25k-scrn5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
apps/c25k/c25k-scrn6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
apps/c25k/c25k-scrn7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
apps/c25k/c25k-scrn8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
apps/c25k/c25k-scrn9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

30
apps/c25k/metadata.json Normal file
View File

@ -0,0 +1,30 @@
{
"id": "c25k",
"name": "C25K",
"icon": "app.png",
"version":"0.04",
"description": "Unofficial app for the Couch to 5k training plan",
"readme": "README.md",
"type": "app",
"tags": "running,c25k,tool,outdoors,exercise",
"allow_emulator": true,
"supports": [
"BANGLEJS",
"BANGLEJS2"
],
"storage": [
{"name": "c25k.app.js", "url": "app.js"},
{"name": "c25k.img", "url": "app-icon.js", "evaluate": true}
],
"screenshots": [
{"url": "c25k-scrn1.png"},
{"url": "c25k-scrn2.png"},
{"url": "c25k-scrn3.png"},
{"url": "c25k-scrn4.png"},
{"url": "c25k-scrn5.png"},
{"url": "c25k-scrn6.png"},
{"url": "c25k-scrn7.png"},
{"url": "c25k-scrn8.png"},
{"url": "c25k-scrn9.png"}
]
}

View File

@ -1,3 +1,4 @@
0.01: Car Crazy is now avialable for testing in beta!
0.02: 10 Levels are now added making the game harder as it goes along. Some of the levels include multiple cars and faster cars. More levels coming soon.
0.03: Settings are now added so that you can reset your high score.
0.04: Minor code improvements.

View File

@ -66,10 +66,8 @@ function moveEnemyPosition(){
enemyPositonCenterX2 = 120;
}else if((randomRoadPositionIndicator2 == 3)){
enemyPositonCenterX2 = 155;
}else if(level == 7||level == 8){
}
}
} // TODO: else if(level == 7)
}
function collision(){

View File

@ -2,7 +2,7 @@
"id": "carcrazy",
"name": "Car Crazy",
"shortName": "Car Crazy",
"version": "0.03",
"version": "0.04",
"description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.",
"icon": "carcrash.png",
"tags": "game",

View File

@ -1,2 +1,3 @@
0.01: Simple app to display loyalty cards
0.02: Hiding widgets while showing the code
0.03: Added option to use max brightness when showing code

View File

@ -14,6 +14,14 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
// get brightness
let brightness;
function loadBrightness() {
const getBrightness = require('Storage').readJSON("setting.json", 1) || {};
brightness = getBrightness.brightness || 0.1;
}
//may make it configurable in the future
const WHITE=-1
const BLACK=0
@ -89,6 +97,10 @@ function printLinearCode(binary) {
}
function showCode(card) {
// set to full bright when the setting is true
if(settings.fullBrightness) {
Bangle.setLCDBrightness(1);
}
widget_utils.hide();
E.showScroller();
// keeping it on rising edge would come back twice..
@ -129,6 +141,10 @@ function showCode(card) {
}
function showCard(card) {
// reset brightness to old value after maxing it out
if(settings.fullBrightness) {
Bangle.setLCDBrightness(brightness);
}
var lines = [];
var bodyFont = fontBig;
if(!card) return;
@ -208,4 +224,7 @@ function showList() {
back : () => load()
});
}
if(settings.fullBrightness) {
loadBrightness();
}
showList();

View File

@ -1,7 +1,7 @@
{
"id": "cards",
"name": "Cards",
"version": "0.02",
"version": "0.03",
"description": "Display loyalty cards",
"icon": "app.png",
"screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_card2.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}],

View File

@ -14,6 +14,13 @@
updateSettings();
}
},
/*LANG*/"Full Brightness" : {
value : !!settings.fullBrightness,
onchange: v => {
settings.fullBrightness = v;
updateSettings();
}
}
};
E.showMenu(mainmenu);
})

View File

@ -7,3 +7,4 @@
Improve connection code
0.07: Make Bangle.js 2 compatible
0.08: Convert Yes/No On/Off in settings to checkboxes
0.09: Automatically reconnect on error

View File

@ -226,7 +226,7 @@ function getSensorBatteryLevel(gatt) {
function connection_setup() {
mySensor.screenInit = true;
E.showMessage("Scanning for CSC sensor...");
NRF.requestDevice({ filters: [{services:["1816"]}]}).then(function(d) {
NRF.requestDevice({ filters: [{services:["1816"]}], maxInterval: 100}).then(function(d) {
device = d;
E.showMessage("Found device");
return device.gatt.connect();
@ -249,6 +249,7 @@ function connection_setup() {
}).catch(function(e) {
E.showMessage(e.toString(), "ERROR");
console.log(e);
setTimeout(connection_setup, 1000);
});
}

View File

@ -2,7 +2,7 @@
"id": "cscsensor",
"name": "Cycling speed sensor",
"shortName": "CSCSensor",
"version": "0.08",
"version": "0.09",
"description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch",
"icon": "icons8-cycling-48.png",
"tags": "outdoors,exercise,ble,bluetooth,bike,cycle,bicycle",

View File

@ -5,3 +5,4 @@
0.05: extraneous comments and code removed
display improved
now supports Adjust Clock widget, if installed
0.06: minor code improvements

View File

@ -223,9 +223,8 @@ function fixTime() {
Bangle.on("GPS",function cb(g) {
Bangle.setGPSPower(0,"time");
Bangle.removeListener("GPS",cb);
if (!g.time || (g.time.getFullYear()<2000) ||
(g.time.getFullYear()>2200)) {
} else {
if (g.time && (g.time.getFullYear()>=2000) &&
(g.time.getFullYear()<=2200)) {
// We have a GPS time. Set time
setTime(g.time.getTime()/1000);
}

View File

@ -2,7 +2,7 @@
"id": "doztime",
"name": "Dozenal Digital Time",
"shortName": "Dozenal Digital",
"version": "0.05",
"version": "0.06",
"description": "A dozenal Holocene calendar and dozenal diurnal digital clock",
"icon": "app.png",
"type": "clock",

7
apps/fallout_clock/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
node_modules/
res/
fallout_clock.code-workspace
package.json
package-lock.json

View File

@ -0,0 +1,5 @@
0.10: (20240125) Basic Working Clock.
0.11: (20240125) Widgets Added. Improved Interval Loop.
0.12: (20240221) Fix: Month Reporting Wrong.
0.20: (20240223) Created as a Package.
0.21: (20240223) Added StandardJS and NPM.

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Zachary D. Skelton <zskelton@skeltonnetworks.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,29 @@
# Fallout Clock
Inspired by the aesthetic of the Fallout series, this clock face looks to emulate the color and feel of a PipBoy.
![clockface](./res/screenshot.png)
## Usage
You can also go into Settings, and choose it as the default clock under **Select Clock**.
## Planned Features:
- Display Steps as Health
- Display Heartrate
- Brighter Color when the backlight is not on.
- Configurable Settings
## Controls
Zero Settings, Zero Configuration. Install and add as your clockface.
## Requests
To request new features, add [an issue](https://github.com/zskelton/fallout_clock/issues).
## Creator
Zachary D. Skelton <zskelton@skeltonnetworks.com>\
[Skelton Networks](https://skeltonnetworks.com)\
[Github](https://github.com/zskelton)

View File

@ -0,0 +1 @@
atob("MDDDAb88//9u/1r/1/YZrgAAit4kkkkkkkkkkAAVIkkkkkkkkkkkkkkkkkkAAAARJJIkkkkkkkkkkkkkkkAAAACJJJJUkkkkkkkkkkkkkAAAAARJJJJAAkkkkkkkkkkkAAAAACpJJJKgAAkkkkkkkkkgAAAAAVJJJJIAAAEkkkkkkkkAAAAACpJfpJUAAAAkkkkkkkgAAAAABJf/9JAAAAAEkkkkkkAAAAAARJdf/+gAAAAAkkkkkgAAAAAC//dL//gAAAAAEkkkkAAAAYADpJJL//8AAAAAAkkkkAAAD8AdJJJL///gAAAAAkkkgAAADr/pJJL////0AAAAAEkkgAAABJJL/pfb////gAAAAAkkAAAADpJeu22X////4AAAAAkkAAAADpL1tttuf/7f8AAAAAkgAAAAb/+ttttuSSS7/AAAAAEgAAAAdeyttttySSSb/gAAAAEgAAAC9eWtttuaySSf/2SSSSkgAAAVfxtttttyySX//9JJJQAAAAAJetttttttyST//9JJJUAAAABJeOaNyNutySW//9JJKgAAAARJdu6N1tvRySS3/JJJUAAAACJJVuVu1tzRyST2/JJKAAAAAVJL1ttyttuNuSWW7pJKgAAACpJLxtt6NtttuSS27pJUAAAAVJJLxtt6ttttuSWT9JKgAAAAJJJLxttzNtttuSST9JIAAAAiJJJL1ttttt2NuSSS9JUAAAA2222212xtty3RySSS9KgAAAEgAAAAZ6OW2tu1ySST9QAAAAEgAAAAaW1ttu2VySSXKAAAAAEkAAAACtu221ttySbdKgAAAAEkAAAADNty1ttuST9JUAAAAAkkAAAAAVty1ttuSXpKAAAAAAkkgAAAACtttttyT9JIAAAAAEkkkAAAAARttttyfdJUAAAAAEkkkAAAAACtttuSzJKgAAAAAkkkkgAAAAAWtuSSfpQAAAAAEkkkkkAAAAADa2yT9JAAAAAAkkkkkkgAAAAD7e3/pKgAAAAAkkkkkkkAAAAVL/9JJUAAAAAEkkkkkkkgAAARJJJJKAAAAAEkkkkkkkkkAAAJJJJJIAAAAAkkkkkkkkkkkACpJJJJUAAAAEkkkkkkkkkkkgCJJJJKgAAAEkkkkkkkkkkkkklJJJJQAAAEkkkkkkkkkkkkkkkkpJJAAEkkkkkkkkk=")

141
apps/fallout_clock/clock.js Normal file
View File

@ -0,0 +1,141 @@
/* global Bangle, Graphics, g */
// NAME: Fallout Clock (Bangle.js 2)
// DOCS: https://www.espruino.com/ReferenceBANGLEJS2
// AUTHOR: Zachary D. Skelton <zskelton@bws-solutions.com>
// VERSION: 0.1.0 (24JAN2024) - Creating [ Maj.Min.Bug ] REF: https://semver.org/
// LICENSE: MIT License (2024) [ https://opensource.org/licenses/MIT ]
/* THEME COLORS */
// Dark Full - #000000 - (0,0.00,0)
// Dark Half - #002f00 - (0,0.18,0)
// Dark Zero - #005f00 - (0,0.37,0)
// Light Zero - #008e00 - (0,0.55,0)
// Light Half - #00bf00 - (0,0.75,0)
// Light Full - #00ee00 - (0,0.93,0)
/* FONTS */
// Font: Good Time Rg - https://www.dafont.com/good-times.font
// Large = 50px
Graphics.prototype.setLargeFont = function () {
this.setFontCustom(
atob('AAAAAAAAAAAAAAAAAAAAAABAAAAAAAB8AAAAAAA/gAAAAAAP4AAAAAAD+AAAAAAA/gAAAAAAP4AAAAAAB8AAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAADwAAAAAAD8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAH/8AAAAAH/8AAAAAH/4AAAAAH/4AAAAAH/4AAAAAH/4AAAAAP/4AAAAAP/wAAAAAP/wAAAAAP/wAAAAAP/wAAAAAH/wAAAAAB/wAAAAAAfgAAAAAAHgAAAAAABgAAAAAAAAAAAAAAAAAAOAAAAAAB//AAAAAB//8AAAAB///wAAAA////AAAAf///4AAAP////AAAH////4AAD/+B//AAB/8AD/wAAf8AAP+AAP+AAB/gAD/AAAP8AA/gAAB/AAf4AAAf4AH8AAAD+AB/AAAA/gAfwAAAP4AH8AAAD+AB/AAAA/gAfwAAAP4AH8AAAD+AB/AAAA/gAfwAAAP4AH8AAAD+AB/gAAB/gAP4AAAfwAD/AAAP8AA/4AAH+AAH/AAD/gAB/8AD/wAAP/4H/8AAB////+AAAP////AAAB////gAAAP///wAAAB///wAAAAH//wAAAAAf/wAAAAAAOAAAAAAAAAAAAAfgAAAAAAH8AAAAAAB/AAAAAAAfwAAAAAAH8AAAAAAB/AAAAAAAfwAAAAAAH+AAAAAAB/wAAAAAAf/////wAD/////8AA//////AAH/////wAA/////8AAH/////AAAf////wAAA////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/wAH8AA//8AB/AA///AAfwAf//wAH8AH//8AB/AD///AAfwA///wAH8Af//8AB/AH8B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwD+AfwAH+B/AH8AB///wB/AAf//8AfwAD///AH8AA///gB/AAH//wAfwAA//4AH8AAH/8AB/AAAf8AAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AB/AAAB/AAfwAAAfwAH8AAAH8AB/AAAB/AAfwAAAfwAH8AAAH8AB/AAAB/AAfwAAAfwAH8AfAH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8A/gH8AB/gP4D/AAf8H/A/wAH///8/8AA/////+AAP/////gAB/////4AAf/8//8AAD/+P/+AAAf/B//AAAA/AH/AAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///AAAAB///8AAAAf///gAAAH///8AAAB////gAAAf///4AAAH////AAAAAAD/wAAAAAAP8AAAAAAB/AAAAAAAfwAAAAAAD+AAAAAAA/gAAAAAAP4AAAAAAD+AAAAAAA/gAAAAAAP4AAAAAAD+AAAAAAA/gAAAAAAP4AAAAAAD+AAAAAAA/gAAAAAAP4AAAAAAD+AAAAAAA/gAAAAAAP4AAAAAAD+AAAH/////8AB//////AAf/////wAH/////8AB//////AAf/////wAH/////8AAAAAP4AAAAAAD+AAAAAAA/gAAAAAAP4AAAAAAD+AAAAAAA/AAAAAAAAAAAAAAAAAAAAH///AD8AB///4B/AAf//+AfwAH///gH8AB///4B/AAf//+AfwAH///gH8AB///4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH8D/AAfwB/h/wAH8Af//8AB/AD//+AAfwA///gAH8AH//wAB/AA//4AAfgAH/8AAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAAAB//4AAAAB///wAAAB////AAAA////4AAAf////AAAP////4AAH/////AAB//fv/wAA/8H4f+AAP+B+D/gAH/AfgP8AB/gH4D/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+A/wAH8Afwf8AB/AH///AAfwA///gAH8AP//4AB/AD//8AAfwAf/+AAH8AD//AAAAAAP/gAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAB/AAAABAAfwAAAAwAH8AAAAcAB/AAAAfAAfwAAAPwAH8AAAH8AB/AAAD/AAfwAAD/wAH8AAB/8AB/AAA//AAfwAAf/gAH8AAP/gAB/AAP/wAAfwAH/4AAH8AD/8AAB/AB/8AAAfwB/+AAAH8A//AAAB/Af/gAAAfwP/gAAAH8P/wAAAB/H/4AAAAfz/8AAAAH9/8AAAAB//+AAAAAf//AAAAAH//gAAAAB//gAAAAAf/wAAAAAH/4AAAAAB/8AAAAAAf8AAAAAAH+AAAAAAB/AAAAAAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAD/gP/gAAB/+H/8AAA//z//gAAf////8AAP/////gAD/////4AB//////AAf+P/B/wAH+A/gP8AB/AP4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8A/gH8AB/gP8D/AAf+P/h/wAH/////8AA/////+AAP/////gAB/////wAAP/8//4AAB/+H/8AAAH+A/+AAAAAAD+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAH/4AAAAAH//gAAAAD//8AAAAB///AD8AA///4B/AAP///AfwAH///wH8AB/4f8B/AAf4B/AfwAH8APwH8AB/AD8B/AAfwA/AfwAH8APwH8AB/AD8B/AAfwA/AfwAH8APwH8AB/AD8B/AAfwA/AfwAH8APwH8AB/AD8B/AAfwA/AfwAH8APwH8AB/AD8B/AAfwA/AfwAH8APwH8AB/AD8B/AAfwA/AfwAH+APwP8AB/wD8D/AAP+A/B/gAD/wPx/4AAf/j9/+AAH/////AAA/////gAAH////4AAA////8AAAH///8AAAAf//+AAAAA//8AAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAEAAAAD4AHwAAAB/AD+AAAAfwA/gAAAH8AP4AAAB/AD+AAAAfwA/gAAAD4AHwAAAAMAAYAAAAAAAAAAAAAAAAA'),
46,
atob('DRYqEykpKiwsJi0rDQ=='),
50 | 65536
)
return this
}
// Medium = 16px ()
Graphics.prototype.setMediumFont = function () {
this.setFontCustom(
atob('AAAAAAAADwAAAB8AAAAPAAAAGwAAAb8AAB/9AAH/kAAv+QAA/4AAAPQAAAACvkAAH//wAD///AC/Qf4A/AA/APQAHwDwAA8A9AAfAPwAPwC+Qf4AP//8AB//9AAG/4AAoAAAAPAAAAD4AAAA////AL///wAv//8AAAAAAPAL/wDwH/8A8D//APA9DwDwPA8A8DwPAPA8DwDwPA8A9DwPAP78DwD/+A8AL+APAPAADwDwAA8A8BQPAPA8DwDwPA8A8DwPAPA8DwDwPA8A9DwfAP7/vwD///8AP+v8AAUBUACqqQAA//9AAP//wABVW8AAAAPAAAADwAAAA8AAAAPAAAADwAD///8A////AP///wAAA8AAAAKAAAAAAAD//A8A//wPAP/8DwDwPA8A8DwPAPA8DwDwPA8A8DwPAPA8DwDwPR8A8D+/APAv/gCgC/gAC//gAD///AC///4A/Tx/APg8LwDwPA8A8DwPAPA8DwDwPA8A8D0fAPA/vwDwL/4AoAv4AAAAQABQAAAA8AAHAPAAHwDwAL8A8AP/APAf+ADwf9AA8v9AAP/4AAD/4AAA/0AAAP0AAAAAAEAAL9v4AL///gD//78A+H0vAPA8DwDwPA8A8DwPAPA8DwDwPA8A9D0fAP7/vwD///8AP9v8AC/4AAC//g8A/r8PAPQfDwDwDw8A8A8PAPAPDwDwDw8A+A8fAP5PfwC///4AL//8AAb/4AAAAAAAAAAAAAA8DwAAfB8AADwPAA=='),
46,
atob('BAcNBg0NDg4ODA4OBA=='),
16 | 131072
)
return this
}
/* VARIABLES */
// Const
const H = g.getHeight()
const W = g.getWidth()
// Mutable
let timer = null
/* UTILITY FUNCTIONS */
// Return String of Current Time
function getCurrentTime () {
try {
const d = new Date()
const h = d.getHours()
const m = d.getMinutes()
return `${h}:${m.toString().padStart(2, 0)}`
} catch (e) {
console.log(e)
return '0:00'
}
}
// Return String of Current Date
function getCurrentDate () {
try {
const d = new Date()
const year = d.getFullYear()
const month = d.getMonth()
const day = d.getDate()
const display = `${month + 1}.${day.toString().padStart(2, 0)}.${year}`
return display
} catch (e) {
console.log(e)
return '0.0.0000'
}
}
// Set A New Draw for the Next Minute
function setNextDraw () {
console.log('tick')
// Clear Timeout
if (timer) {
clearInterval(timer)
}
// Calculate time until next minute
const d = new Date()
const s = d.getSeconds()
const ms = d.getMilliseconds()
const delay = 60000 - (s * 1000) - ms
// Set Timeout
timer = setInterval(draw, delay)
}
function draw () {
// Reset Variables
g.reset()
// Set Background Color
g.setBgColor(0, 0, 0)
// Draw Background
g.setColor(0, 0, 0)
g.fillRect(0, 0, W, H)
// Set Font for Time
g.setColor(0, 0.93, 0)
g.setLargeFont()
g.setFontAlign(0, 0)
// Draw Time
const time = getCurrentTime()
g.drawString(time, W / 2, H / 2, true /* clear background */)
// Set Font for Date
g.setColor(0, 0.75, 0)
g.setMediumFont()
g.setFontAlign(0, 1)
// Draw Date
const dateStr = getCurrentDate()
g.drawString(dateStr, W / 2, H - 45, true)
// Draw Border
g.setColor(0, 0.93, 0)
g.drawLine(5, 36, W - 5, 36)
g.drawLine(5, H - 9, W - 5, H - 9)
g.setColor(0, 0.18, 0)
g.fillRect(0, 27, W, 32)
g.fillRect(0, H, W, H - 5)
// Draw Widgets
Bangle.drawWidgets()
// Schedule Next Draw
setNextDraw()
}
/* MAIN LOOP */
function main () {
// Clear Screen
g.clear()
// Set as Clock to Enable Launcher Screen on BTN1
Bangle.setUI('clock')
// Load Widgets
Bangle.loadWidgets()
// Draw Clock
draw()
}
/* BOOT CODE */
main()

BIN
apps/fallout_clock/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -0,0 +1,18 @@
{
"id":"fallout_clock",
"name":"Fallout Clock",
"version":"0.21",
"description":"A simple clock for the Fallout fan",
"icon":"icon.png",
"type":"clock",
"tags": "clock,fallout,green,retro",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"fallout_clock.app.js", "url":"clock.js"},
{"name":"fallout_clock.img", "url":"app-icon.js", "evaluate":true}
],
"screenshots": [
{"url":"./screenshot.png", "name":"Fallout Clock Screenshot"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -127,4 +127,11 @@
* Interface: disable/enable waypoints detection and elevation
* Touching the screen when sleeping will wake up but not change screen
* Removing footways (if bicycles not allowed) to reduce resource usage
* Switching screen will clear the screen immediately so you can know your screen touch has been received
* Switching screen will clear the screen immediately so you can know your screen touch has been received
0.24:
* Fix for loading very large files (> 65kb)
* New menu on file selection : reverse path without waiting for file to load
* Fix for files converted from maps2gpx : path was not reduced in size correctly
* Experimental ski mode : have a ski slopes map
* Fix for path projection display when lost and zoomed out

View File

@ -33,7 +33,7 @@ It provides the following features :
### Preparing the file
You first need to have a trace file in *gpx* format.
You typically want to use a trace file in *gpx* format.
Usually I download from [komoot](https://www.komoot.com/) or I export
from google maps using [mapstogpx](https://mapstogpx.com/). [Brouter](https://brouter.damsy.net) is
also a nice open source option.
@ -48,6 +48,11 @@ Just click the disk icon and select your gpx file.
This will request additional information from openstreetmap.
Your path will be displayed in svg.
Note that it is also possible to just hit the shift key and drag the mouse on the map
to download a map (with no trace). If you want a map, hitting the "ski" checkbox
before selecting the area will parse openstreetmap data in order to get the pistes and
the lifts. Colors will correspond to difficulty levels.
### Starting Gipy
At start you will have a menu for selecting your trace (if more than one).

View File

@ -1,5 +1,7 @@
urgent TODO:
- is path projection wrong for high res ?
medium TODO:
- prefetch tiles ?
- update documentation to reflect new display ?

View File

@ -95,26 +95,30 @@ function compute_eta(hour, minutes, approximate_speed, remaining_distance) {
}
class TilesOffsets {
constructor(buffer, offset) {
let type_size = Uint8Array(buffer, offset, 1)[0];
constructor(filename, offset) {
let header = E.toArrayBuffer(s.read(filename, offset, 4));
let type_size = Uint8Array(header, 0, 1)[0];
offset += 1;
this.entry_size = Uint8Array(buffer, offset, 1)[0];
this.entry_size = Uint8Array(header, 1, 1)[0];
offset += 1;
let non_empty_tiles_number = Uint16Array(buffer, offset, 1)[0];
let non_empty_tiles_number = Uint16Array(header, 2, 1)[0];
offset += 2;
this.non_empty_tiles = Uint16Array(buffer, offset, non_empty_tiles_number);
let bytes = (type_size==24)?3:2;
let buffer = E.toArrayBuffer(s.read(filename, offset, 2*non_empty_tiles_number+bytes*non_empty_tiles_number));
this.non_empty_tiles = Uint16Array(buffer, 0, non_empty_tiles_number);
offset += 2 * non_empty_tiles_number;
if (type_size == 24) {
this.non_empty_tiles_ends = Uint24Array(
buffer,
offset,
2*non_empty_tiles_number,
non_empty_tiles_number
);
offset += 3 * non_empty_tiles_number;
} else if (type_size == 16) {
this.non_empty_tiles_ends = Uint16Array(
buffer,
offset,
2*non_empty_tiles_number,
non_empty_tiles_number
);
offset += 2 * non_empty_tiles_number;
@ -153,27 +157,29 @@ class TilesOffsets {
}
class Map {
constructor(buffer, offset, filename) {
constructor(filename, offset) {
// header
let color_array = Uint8Array(buffer, offset, 3);
let header = E.toArrayBuffer(s.read(filename, offset, 43));
let color_array = Uint8Array(header, 0, 3);
this.color = [
color_array[0] / 255,
color_array[1] / 255,
color_array[2] / 255,
];
offset += 3;
this.first_tile = Int32Array(buffer, offset, 2); // absolute tile id of first tile
this.first_tile = Int32Array(header, 3, 2); // absolute tile id of first tile
offset += 2 * 4;
this.grid_size = Uint32Array(buffer, offset, 2); // tiles width and height
this.grid_size = Uint32Array(header, 11, 2); // tiles width and height
offset += 2 * 4;
this.start_coordinates = Float64Array(buffer, offset, 2); // min x and y coordinates
this.start_coordinates = Float64Array(header, 19, 2); // min x and y coordinates
offset += 2 * 8;
let side_array = Float64Array(buffer, offset, 1); // side of a tile
let side_array = Float64Array(header, 35, 1); // side of a tile
this.side = side_array[0];
offset += 8;
// tiles offsets
let res = new TilesOffsets(buffer, offset);
let res = new TilesOffsets(filename, offset);
this.tiles_offsets = res[0];
offset = res[1];
@ -246,7 +252,7 @@ class Map {
let tile_y = absolute_tile_y - this.first_tile[1];
let side = img.getWidth() - 6;
let thick = this.color[0] != 0;
let thick = this.color[0] == 1;
img.setColor(this.color[0], this.color[1], this.color[2]);
let tile_num = tile_x + tile_y * this.grid_size[0];
@ -296,26 +302,25 @@ class Map {
}
class Interests {
constructor(buffer, offset) {
this.first_tile = Int32Array(buffer, offset, 2); // absolute tile id of first tile
constructor(filename, offset) {
let header = E.toArrayBuffer(s.read(filename, offset, 40));
this.first_tile = Int32Array(header, 0, 2); // absolute tile id of first tile
offset += 2 * 4;
this.grid_size = Uint32Array(buffer, offset, 2); // tiles width and height
this.grid_size = Uint32Array(header, 8, 2); // tiles width and height
offset += 2 * 4;
this.start_coordinates = Float64Array(buffer, offset, 2); // min x and y coordinates
this.start_coordinates = Float64Array(header, 16, 2); // min x and y coordinates
offset += 2 * 8;
let side_array = Float64Array(buffer, offset, 1); // side of a tile
let side_array = Float64Array(header, 32, 1); // side of a tile
this.side = side_array[0];
offset += 8;
let res = new TilesOffsets(buffer, offset);
let res = new TilesOffsets(filename, offset);
offset = res[1];
this.offsets = res[0];
let end = this.offsets.end_offset();
this.binary_interests = new Uint8Array(end);
let binary_interests = Uint8Array(buffer, offset, end);
for (let i = 0; i < end; i++) {
this.binary_interests[i] = binary_interests[i];
}
let buffer = E.toArrayBuffer(s.read(filename, offset, end));
this.binary_interests = Uint8Array(buffer);
offset += end;
return [this, offset];
}
@ -374,14 +379,15 @@ class Status {
this.scale_factor = diagonal_third / maps[0].side; // multiply geo coordinates by this to get pixels coordinates
if (this.path !== null) {
let r = [0];
//TODO: float32 ??
let r = new Float64Array(this.path.len);
// let's do a reversed prefix computations on all distances:
// loop on all segments in reversed order
let previous_point = null;
for (let i = this.path.len - 1; i >= 0; i--) {
let point = this.path.point(i);
if (previous_point !== null) {
r.unshift(r[0] + point.distance(previous_point));
r[i] = r[i+1] + point.distance(previous_point);
}
previous_point = point;
}
@ -634,6 +640,10 @@ class Status {
this.display();
}
display_direction() {
let scale_factor = this.scale_factor;
if (!this.zoomed_in) {
scale_factor *= 3/5;
}
//TODO: go towards point on path at 20 meter
if (this.current_segment === null) {
return;
@ -658,7 +668,7 @@ class Status {
this.displayed_position,
this.adjusted_cos_direction,
this.adjusted_sin_direction,
this.scale_factor
scale_factor
);
let cos1 = Math.cos(full_angle + 0.6 + Math.PI / 2);
@ -1076,6 +1086,9 @@ class Status {
let half_width = width / 2;
let half_height = height / 2 + Y_OFFSET;
let scale_factor = this.scale_factor;
if (!this.zoomed_in) {
scale_factor *= 3/5;
}
if (this.path !== null) {
// compute coordinate for projection on path
@ -1141,8 +1154,11 @@ function load_gps(filename) {
.drawString(filename, 0, g.getHeight() - 30);
g.flip();
let buffer = s.readArrayBuffer(filename);
let file_size = buffer.length;
let file_size;
{
let buffer = s.readArrayBuffer(filename);
file_size = buffer.length;
}
let offset = 0;
let path = null;
@ -1150,60 +1166,51 @@ function load_gps(filename) {
let maps = [];
let interests = null;
while (offset < file_size) {
let block_type = Uint8Array(buffer, offset, 1)[0];
let block_type = Uint8Array(E.toArrayBuffer(s.read(filename, offset, 1)))[0];
offset += 1;
if (block_type == 0) {
// it's a map
console.log("loading map");
let res = new Map(buffer, offset, filename);
let res = new Map(filename, offset);
let map = res[0];
offset = res[1];
maps.push(map);
} else if (block_type == 2) {
console.log("loading path");
let res = new Path(buffer, offset);
let res = new Path(filename, offset);
path = res[0];
offset = res[1];
} else if (block_type == 3) {
console.log("loading interests");
let res = new Interests(buffer, offset);
let res = new Interests(filename, offset);
interests = res[0];
offset = res[1];
} else if (block_type == 4) {
console.log("loading heights");
let heights_number = path.points.length / 2;
heights = Int16Array(buffer, offset, heights_number);
let buffer = E.toArrayBuffer(s.read(filename, offset, heights_number*2));
heights = Int16Array(buffer);
offset += 2 * heights_number;
} else {
console.log("todo : block type", block_type);
}
}
// checksum file size
if (offset != file_size) {
console.log("invalid file size", file_size, "expected", offset);
let msg = "invalid file\nsize " + file_size + "\ninstead of" + offset;
E.showAlert(msg).then(function () {
E.showAlert();
start_gipy(path, maps, interests, heights);
});
} else {
start_gipy(path, maps, interests, heights);
}
start_gipy(path, maps, interests, heights);
}
class Path {
constructor(buffer, offset) {
let points_number = Uint16Array(buffer, offset, 1)[0];
constructor(filename, offset) {
let points_number = Uint16Array(E.toArrayBuffer(s.read(filename, offset, 2)))[0];
offset += 2;
let waypoints_len = Math.ceil(points_number / 8.0);
let buffer = E.toArrayBuffer(s.read(filename, offset, points_number * 16 + waypoints_len));
// path points
this.points = Float64Array(buffer, offset, points_number * 2);
this.points = Float64Array(buffer, 0, 2 * points_number);
offset += 8 * points_number * 2;
// path waypoints
let waypoints_len = Math.ceil(points_number / 8.0);
this.waypoints = Uint8Array(buffer, offset, waypoints_len);
this.waypoints = Uint8Array(buffer, points_number * 2, waypoints_len);
offset += waypoints_len;
return [this, offset];
@ -1370,14 +1377,47 @@ function drawMenu() {
menu["Exit"] = function () {
load();
};
Bangle.setLCDBrightness(settings.brightness);
Bangle.setLocked(false);
E.showMenu(menu);
}
function ask_options(fn) {
g.clear();
let height = g.getHeight();
let width = g.getWidth();
g.drawRect(10, 10, width - 10, height / 2 - 10);
g.drawRect(10, height/2 + 10, width - 10, height - 10);
g.setFont("Vector:30").setFontAlign(0,0).drawString("Forward", width/2, height/4);
g.drawString("Backward", width/2, 3*height/4);
g.flip();
function options_select(b, xy) {
end = false;
if (xy.y < height / 2 - 10) {
g.setColor(0, 0, 0).fillRect(10, 10, width - 10, height / 2 - 10);
g.setColor(1, 1, 1).setFont("Vector:30").setFontAlign(0,0).drawString("Forward", width/2, height/4);
end = true;
} else if (xy.y > height/2 + 10) {
g.setColor(0, 0, 0).fillRect(10, height/2 + 10, width - 10, height - 10);
g.setColor(1, 1, 1).setFont("Vector:30").setFontAlign(0,0).drawString("Backward", width/2, 3*height/4);
go_backwards = true;
end = true;
}
if (end) {
g.flip();
Bangle.removeListener("touch", options_select);
console.log("loading", fn);
load_gps(fn);
}
}
Bangle.on("touch", options_select);
}
function start(fn) {
E.showMenu();
console.log("loading", fn);
load_gps(fn);
ask_options(fn);
}
function start_gipy(path, maps, interests, heights) {
@ -1387,7 +1427,9 @@ function start_gipy(path, maps, interests, heights) {
NRF.sleep(); // disable bluetooth completely
}
console.log("creating status");
status = new Status(path, maps, interests, heights);
console.log("done creating status");
setWatch(
function () {
@ -1491,6 +1533,7 @@ function start_gipy(path, maps, interests, heights) {
});
if (simulated) {
console.log("un-comment simulator to use it");
// status.starting_time = getTime();
// // let's keep the screen on in simulations
// Bangle.setLCDTimeout(0);
@ -1518,8 +1561,8 @@ function start_gipy(path, maps, interests, heights) {
// if (point_index >= status.path.len / 2 - 1) {
// return;
// }
// let p1 = status.path.point(2 * point_index); // use these to approximately follow path
// let p2 = status.path.point(2 * (point_index + 1));
// let p1 = status.path.point(8 * point_index); // use these to approximately follow path
// let p2 = status.path.point(8 * (point_index + 1));
// //let p1 = status.path.point(point_index); // use these to strictly follow path
// //let p2 = status.path.point(point_index + 1);

View File

@ -55,6 +55,9 @@
<label for="autodetect_waypoints">detect waypoints</label>
<input type="checkbox" id="include_elevation" name="include_elevation" checked/>
<label for="include_elevation">include elevation</label>
<input type="checkbox" id="ski" name="ski"/>
<label for="ski">ski</label>
<div id="status"></div>
<div id="svgmap"></div>
@ -140,7 +143,9 @@ if ((e.which !== 1) && (e.button !== 1)) { return; }
this._map.containerPointToLatLng(this._point));
let south_west = bounds.getSouthWest();
let north_east = bounds.getNorthEast();
gps_data = gps_from_area(south_west.lng, south_west.lat, north_east.lng, north_east.lat);
let ski = document.getElementById("ski").checked;
console.log("ski", ski);
gps_data = gps_from_area(south_west.lng, south_west.lat, north_east.lng, north_east.lat, ski);
document.getElementById('gps_file').value = "";
display_polygon(this._map);

View File

@ -2,7 +2,7 @@
"id": "gipy",
"name": "Gipy",
"shortName": "Gipy",
"version": "0.23",
"version": "0.24",
"description": "Follow gpx files using the gps. Don't get lost in your bike trips and hikes.",
"allow_emulator":false,
"icon": "gipy.png",

View File

@ -53,9 +53,10 @@ export function load_gps_from_string(input: string, autodetect_waypoints: boolea
* @param {number} ymin
* @param {number} xmax
* @param {number} ymax
* @param {boolean} ski
* @returns {Gps}
*/
export function gps_from_area(xmin: number, ymin: number, xmax: number, ymax: number): Gps;
export function gps_from_area(xmin: number, ymin: number, xmax: number, ymax: number, ski: boolean): Gps;
/**
*/
export class Gps {
@ -75,15 +76,15 @@ export interface InitOutput {
readonly get_gps_content: (a: number, b: number) => void;
readonly request_map: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number) => number;
readonly load_gps_from_string: (a: number, b: number, c: number) => number;
readonly gps_from_area: (a: number, b: number, c: number, d: number) => number;
readonly gps_from_area: (a: number, b: number, c: number, d: number, e: number) => number;
readonly __wbindgen_malloc: (a: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
readonly __wbindgen_export_2: WebAssembly.Table;
readonly wasm_bindgen__convert__closures__invoke1_mut__hef038f7a61abd0f6: (a: number, b: number, c: number) => void;
readonly wasm_bindgen__convert__closures__invoke1_mut__hc18aa489d857d6a0: (a: number, b: number, c: number) => void;
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
readonly __wbindgen_free: (a: number, b: number) => void;
readonly __wbindgen_exn_store: (a: number) => void;
readonly wasm_bindgen__convert__closures__invoke2_mut__h545ed49cfafdda52: (a: number, b: number, c: number, d: number) => void;
readonly wasm_bindgen__convert__closures__invoke2_mut__h41c3b5af183df3b2: (a: number, b: number, c: number, d: number) => void;
}
export type SyncInitInput = BufferSource | WebAssembly.Module;

View File

@ -205,7 +205,7 @@ function makeMutClosure(arg0, arg1, dtor, f) {
return real;
}
function __wbg_adapter_24(arg0, arg1, arg2) {
wasm.wasm_bindgen__convert__closures__invoke1_mut__hef038f7a61abd0f6(arg0, arg1, addHeapObject(arg2));
wasm.wasm_bindgen__convert__closures__invoke1_mut__hc18aa489d857d6a0(arg0, arg1, addHeapObject(arg2));
}
function _assertClass(instance, klass) {
@ -373,10 +373,11 @@ export function load_gps_from_string(input, autodetect_waypoints) {
* @param {number} ymin
* @param {number} xmax
* @param {number} ymax
* @param {boolean} ski
* @returns {Gps}
*/
export function gps_from_area(xmin, ymin, xmax, ymax) {
const ret = wasm.gps_from_area(xmin, ymin, xmax, ymax);
export function gps_from_area(xmin, ymin, xmax, ymax, ski) {
const ret = wasm.gps_from_area(xmin, ymin, xmax, ymax, ski);
return Gps.__wrap(ret);
}
@ -388,7 +389,7 @@ function handleError(f, args) {
}
}
function __wbg_adapter_86(arg0, arg1, arg2, arg3) {
wasm.wasm_bindgen__convert__closures__invoke2_mut__h545ed49cfafdda52(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
wasm.wasm_bindgen__convert__closures__invoke2_mut__h41c3b5af183df3b2(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
}
/**
@ -475,6 +476,17 @@ function getImports() {
const ret = fetch(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_signal_31753ac644b25fbb = function(arg0) {
const ret = getObject(arg0).signal;
return addHeapObject(ret);
};
imports.wbg.__wbg_new_6396e586b56e1dff = function() { return handleError(function () {
const ret = new AbortController();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_abort_064ae59cda5cd244 = function(arg0) {
getObject(arg0).abort();
};
imports.wbg.__wbg_new_2d0053ee81e4dd2a = function() { return handleError(function () {
const ret = new Headers();
return addHeapObject(ret);
@ -515,17 +527,6 @@ function getImports() {
const ret = getObject(arg0).fetch(getObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_signal_31753ac644b25fbb = function(arg0) {
const ret = getObject(arg0).signal;
return addHeapObject(ret);
};
imports.wbg.__wbg_new_6396e586b56e1dff = function() { return handleError(function () {
const ret = new AbortController();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_abort_064ae59cda5cd244 = function(arg0) {
getObject(arg0).abort();
};
imports.wbg.__wbg_newwithstrandinit_05d7180788420c40 = function() { return handleError(function (arg0, arg1, arg2) {
const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));
return addHeapObject(ret);
@ -694,8 +695,8 @@ function getImports() {
const ret = wasm.memory;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper2354 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 299, __wbg_adapter_24);
imports.wbg.__wbindgen_closure_wrapper2356 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 293, __wbg_adapter_24);
return addHeapObject(ret);
};

Binary file not shown.

View File

@ -10,12 +10,12 @@ export function get_polyline(a: number, b: number): void;
export function get_gps_content(a: number, b: number): void;
export function request_map(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number): number;
export function load_gps_from_string(a: number, b: number, c: number): number;
export function gps_from_area(a: number, b: number, c: number, d: number): number;
export function gps_from_area(a: number, b: number, c: number, d: number, e: number): number;
export function __wbindgen_malloc(a: number): number;
export function __wbindgen_realloc(a: number, b: number, c: number): number;
export const __wbindgen_export_2: WebAssembly.Table;
export function wasm_bindgen__convert__closures__invoke1_mut__hef038f7a61abd0f6(a: number, b: number, c: number): void;
export function wasm_bindgen__convert__closures__invoke1_mut__hc18aa489d857d6a0(a: number, b: number, c: number): void;
export function __wbindgen_add_to_stack_pointer(a: number): number;
export function __wbindgen_free(a: number, b: number): void;
export function __wbindgen_exn_store(a: number): void;
export function wasm_bindgen__convert__closures__invoke2_mut__h545ed49cfafdda52(a: number, b: number, c: number, d: number): void;
export function wasm_bindgen__convert__closures__invoke2_mut__h41c3b5af183df3b2(a: number, b: number, c: number, d: number): void;

View File

@ -8,3 +8,4 @@
0.08: Handling exceptions
0.09: Add option for showing battery high mark
0.10: Fix background color
0.11: Minor code improvements

View File

@ -3,7 +3,7 @@
"name": "A Battery Widget (with percentage) - Hanks Mod",
"shortName":"H Battery Widget",
"icon": "widget.png",
"version":"0.10",
"version":"0.11",
"type": "widget",
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",

View File

@ -24,8 +24,7 @@
var s = width - 1;
var x = this.x;
var y = this.y;
if ((typeof x === 'undefined') || (typeof y === 'undefined')) {
} else {
if (x !== undefined && y !== undefined) {
g.setBgColor(COLORS.white);
g.clearRect(old_x, old_y, old_x + width, old_y + height);

View File

@ -3,3 +3,4 @@
0.03: Update clock_info to avoid a redraw
0.04: Fix clkinfo -- use .get instead of .show
0.05: Use clock_info module as an app
0.06: Fix month in date (jan 0 -> 1, etc)

View File

@ -112,7 +112,7 @@ function getTime(){
function getDate(){
var date = new Date();
return twoD(date.getDate()) + "." + twoD(date.getMonth());
return twoD(date.getDate()) + "." + twoD(date.getMonth() + 1);
}
function getDay(){

View File

@ -1,7 +1,7 @@
{
"id": "linuxclock",
"name": "Linux Clock",
"version": "0.05",
"version": "0.06",
"description": "A Linux inspired clock.",
"readme": "README.md",
"icon": "app.png",

View File

@ -790,7 +790,8 @@ var locales = {
trans: { yes: "ja", Yes: "Ja", no: "nei", No: "Nei", ok: "ok", on: "på", off: "av", "< Back": "< Tilbake", "Delete": "Slett", "Mark Unread": "Merk som ulest" }
},
"ca_ES": {
lang: "es_ES",
lang: "ca_ES",
icon: "🇪🇺",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "€",
@ -800,7 +801,7 @@ var locales = {
temperature: "°C",
ampm: { 0: "", 1: "" },
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
datePattern: { 0: "%A, %d de %B %Y", "1": "%d/%m/%y" },
datePattern: { 0: "%d %B %Y", "1": "%d/%m/%y" },
abmonth: "gen.,febr.,març,abr.,maig,juny,jul.,ag.,set.,oct.,nov.,des.",
month: "gener,febrer,març,abril,maig,juny,juliol,agost,setembre,octobre,novembre,desembre",
abday: "dg.,dl.,dt.,dc.,dj.,dv.,ds.",

View File

@ -101,4 +101,6 @@
0.72: Nav message updastes don't automatically launch navigation menu unless they're new
0.73: Add sharp left+right nav icons
0.74: Add option for driving on left (affects roundabout icons in navigation)
0.75: Handle text with images in messages list by just displaying the first line
0.75: Handle text with images in messages list by just displaying the first line
0.76: Swipe up/down on a shown message to show the next newer/older message.

View File

@ -38,6 +38,7 @@ When a message is shown, you'll see a screen showing the message title and text.
* The 'back-arrow' button (or physical button on Bangle.js 2) goes back to Messages, marking the current message as read.
* The top-left icon shows more options, for instance deleting the message of marking unread
* On Bangle.js 2 you can tap on the message body to view a scrollable version of the title and text (or can use the top-left icon + `View Message`)
- On Bangle.js 2 swipe up/down to show newer/older message
* If shown, the 'tick' button:
* **Android** opens the notification on the phone
* **iOS** responds positively to the notification (accept call/etc)

View File

@ -289,7 +289,8 @@ function showMessageSettings(msg) {
}
function showMessage(msgid) {
var msg = MESSAGES.find(m=>m.id==msgid);
let idx = MESSAGES.findIndex(m=>m.id==msgid);
var msg = MESSAGES[idx];
if (updateLabelsInterval) {
clearInterval(updateLabelsInterval);
updateLabelsInterval=undefined;
@ -389,9 +390,11 @@ function showMessage(msgid) {
{type:"h",fillx:1, c: footer}
]},{back:goBack});
Bangle.swipeHandler = lr => {
Bangle.swipeHandler = (lr,ud) => {
if (lr>0 && posHandler) posHandler();
if (lr<0 && negHandler) negHandler();
if (ud>0 && idx<MESSAGES.length-1) showMessage(MESSAGES[idx+1].id);
if (ud<0 && idx>0) showMessage(MESSAGES[idx-1].id);
};
Bangle.on("swipe", Bangle.swipeHandler);
g.reset().clearRect(Bangle.appRect);

View File

@ -2,7 +2,7 @@
"id": "messagegui",
"name": "Message UI",
"shortName": "Messages",
"version": "0.75",
"version": "0.76",
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
"icon": "app.png",
"type": "app",

View File

@ -6,3 +6,4 @@
0.06: Support fastloading
0.07: Fix fastloading support - ensure drag handler's restored after
menu display/fastload removes it
0.08: Add setting for initial page to display

View File

@ -78,7 +78,7 @@ function drawTimers() {
}, 1000 - (timers[idx].t % 1000));
}
E.showScroller({
const s = E.showScroller({
h : 40, c : timers.length+2,
back : function() {load();},
draw : (idx, r) => {
@ -138,7 +138,7 @@ function timerMenu(idx) {
}, 1000 - (a.t % 1000));
}
E.showScroller({
const s = E.showScroller({
h : 40, c : 5,
back : function() {
clearInt();
@ -328,9 +328,22 @@ function editTimer(idx, a) {
setUI();
}
function readJson() {
let json = require("Storage").readJSON("multitimer.json", true) || {};
if (Array.isArray(json)) {
// old format, convert
json = { sw: json };
require("Storage").writeJSON("multitimer.json", json);
}
if (!json.sw) json.sw = [];
return json;
}
function drawSw() {
layer = 1;
const sw = require("Storage").readJSON("multitimer.json", true) || [];
const sw = readJson().sw;
function updateTimers(idx) {
if (!timerInt1[idx]) timerInt1[idx] = setTimeout(function() {
@ -341,7 +354,7 @@ function drawSw() {
}, 1000 - (sw[idx].t % 1000));
}
E.showScroller({
const s = E.showScroller({
h : 40, c : sw.length+2,
back : function() {load();},
draw : (idx, r) => {
@ -382,12 +395,13 @@ function drawSw() {
function swMenu(idx, a) {
layer = -1;
const sw = require("Storage").readJSON("multitimer.json", true) || [];
const json = readJson();
const sw = json.sw;
if (sw[idx]) a = sw[idx];
else {
a = {"t" : 0, "on" : false, "msg" : ""};
sw[idx] = a;
require("Storage").writeJSON("multitimer.json", sw);
require("Storage").writeJSON("multitimer.json", json);
}
function updateTimer() {
@ -408,7 +422,7 @@ function swMenu(idx, a) {
}
else delete a.msg;
sw[idx] = a;
require("Storage").writeJSON("multitimer.json", sw);
require("Storage").writeJSON("multitimer.json", json);
swMenu(idx, a);
});
}
@ -420,7 +434,7 @@ function swMenu(idx, a) {
setUI();
}
E.showScroller({
const s = E.showScroller({
h : 40, c : 5,
back : function() {
clearInt();
@ -458,7 +472,7 @@ function swMenu(idx, a) {
select : (i) => {
function saveAndReload() {
require("Storage").writeJSON("multitimer.json", sw);
require("Storage").writeJSON("multitimer.json", json);
s.draw();
}
@ -707,5 +721,17 @@ function onDrag(e) {
}
}
drawTimers();
switch (readJson().initialScreen) {
case 1:
drawSw();
break;
case 2:
drawAlarms();
break;
case 0:
case undefined:
default:
drawTimers();
break;
}
}

View File

@ -1,7 +1,7 @@
{
"id": "multitimer",
"name": "Multi Timer",
"version": "0.07",
"version": "0.08",
"description": "Set timers and chronographs (stopwatches) and watch them count down in real time. Pause, create, edit, and delete timers and chronos, and add custom labels/messages. Also sets alarms.",
"icon": "app.png",
"screenshots": [
@ -16,6 +16,7 @@
{"name":"multitimer.app.js","url":"app.js"},
{"name":"multitimer.boot.js","url":"boot.js"},
{"name":"multitimer.alarm.js","url":"alarm.js"},
{"name":"multitimer.settings.js","url":"settings.js"},
{"name":"multitimer.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"multitimer.json"}],

View File

@ -0,0 +1,32 @@
(function(back) {
const file = "multitimer.json";
let json = require('Storage').readJSON(file, true) || {};
if (Array.isArray(json)) {
// old format, convert
json = { sw: json };
}
if (!json.sw) json.sw = [];
function writeSettings() {
require('Storage').writeJSON(file, json);
}
const screens = ["Timers", "Chronos", "Alarms"];
E.showMenu({
"": {
"title": "multitimer"
},
"< Back": back,
"Initial screen": {
value: json.initialScreen || 0,
min: 0,
max: screens.length - 1,
format: v => screens[v],
onchange: v => {
json.initialScreen = v;
writeSettings();
}
},
});
});

View File

@ -85,7 +85,7 @@ function novaOpenEyes(speed, white, animation) {
scale: 2.2
});
}, speed * 5);
} else {}
}
} else {
g.drawImage(novaEyesStage4(), -10, -10, {
@ -126,7 +126,7 @@ function novaOpenEyes(speed, white, animation) {
});
open = true;
}, speed * 5);
} else {}
}
}
}
@ -136,7 +136,7 @@ function novaCloseEyes(speed, white, animation) {
g.drawImage(novaEyesStage0(), -10, -10, {
scale: 2.2
});
} else {}
}
setTimeout(function() {
g.drawImage(novaEyesStage1(), -10, -10, {
scale: 2.2
@ -164,7 +164,7 @@ function novaCloseEyes(speed, white, animation) {
g.drawImage(novaEyesWhiteStage0(), -10, -10, {
scale: 2.2
});
} else {}
}
setTimeout(function() {
timedraw(true);
g.drawImage(novaEyesTransStage1(), -10, -10, {

View File

@ -4,3 +4,4 @@
0.04: Update cards to draw rounded on newer firmware. Make sure in-game menu can't be pulled up during end of game.
0.05: add confirmation prompt to new game to prevent fat fingering new game during existing one.
0.06: fix AI logic typo and add prompt to show what AI played each turn.
0.07: Minor code improvements.

View File

@ -2,7 +2,7 @@
"name": "Red 7 Card Game",
"shortName" : "Red 7",
"icon": "icon.png",
"version":"0.06",
"version":"0.07",
"description": "An implementation of the card game Red 7 for your watch. Play against the AI and be the last player still in the game to win!",
"tags": "game",
"supports":["BANGLEJS2"],

View File

@ -486,8 +486,7 @@ function canPlay(hand, palette, otherPalette) {
} else {
//Check if any palette play can win with rule.
for(let h of hand.handCards) {
if(h === c) {}
else {
if(h !== c) {
clonePalette.addCard(c);
if(isWinningCombo(c, clonePalette, otherPalette)) {
return true;
@ -531,8 +530,7 @@ class AI {
} else {
//Check if any palette play can win with rule.
for(let h of this.hand.handCards) {
if(h === c) {}
else {
if(h !== c) {
clonePalette.addCard(h);
if(isWinningCombo(c, clonePalette, otherPalette)) {
ruleStack.addCard(c);

View File

@ -101,7 +101,8 @@ function getData() {
uploadBtn.disabled = true;
Util.showModal("Loading...");
Util.readStorageJSON(repJson, reps => {
Util.readStorageJSON(repJson, reps_ => {
reps = reps_;
Util.hideModal();
for(const rep of reps){
renderRep(rep);

View File

@ -23,3 +23,5 @@
0.20: Alarm dismiss and snooze events
0.21: Fix crash in clock_info
0.22: Dated event repeat option
0.23: Allow buzzing forever when an alarm fires
0.24: Emit alarmReload when alarms change (used by widalarm)

View File

@ -14,10 +14,10 @@ Global Settings
---------------
- `Unlock at Buzz` - If `Yes` the alarm/timer will unlock the watch
- `Delete Expired Timers` - Default for whether expired timers are removed after firing.
- `Default Auto Snooze` - Default _Auto Snooze_ value for newly created alarms (_Alarms_ only)
- `Default Snooze` - Default _Snooze_ value for newly created alarms/timers
- `Default Repeat` - Default _Repeat_ value for newly created alarms (_Alarms_ only)
- `Buzz Count` - The number of buzzes before the watch goes silent
- `Buzz Count` - The number of buzzes before the watch goes silent, or "forever" to buzz until stopped.
- `Buzz Interval` - The interval between one buzz and the next
- `Default Alarm/Timer Pattern` - Default vibration pattern for newly created alarms/timers

View File

@ -2,6 +2,26 @@
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
<style>
.multi-select {
/* e.g. day <select> */
height: 100%;
}
td.single-row {
/* no border between single rowspans */
border-bottom: none;
}
.btn-center {
justify-content: center;
display: flex;
}
.event-summary {
text-align: center;
}
</style>
<script src="../../core/lib/interface.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ical.js/1.5.0/ical.min.js"></script>
<script>
@ -115,15 +135,17 @@ function renderAlarm(alarm, exists) {
const localDate = alarm.date ? dateFromAlarm(alarm) : null;
const tr = document.createElement('tr');
const tr2 = document.createElement('tr');
tr.classList.add('event-row');
tr.dataset.uid = alarm.id;
const tdType = document.createElement('td');
tdType.type = "text";
tdType.classList.add('event-summary');
tr.appendChild(tdType);
const inputTime = document.createElement('input');
let type;
if (localDate) {
tdType.textContent = "Event";
type = "Event";
inputTime.type = "datetime-local";
inputTime.value = localDate.toISOString().slice(0,16);
inputTime.onchange = (e => {
@ -139,18 +161,19 @@ function renderAlarm(alarm, exists) {
inputTime.value = `${hours}:${mins}:${secs}`;
if (alarm.timer) {
tdType.textContent = "Timer";
type = "Timer";
inputTime.onchange = e => {
alarm.timer = hmsToMs(inputTime.value);
// alarm.t is set on upload
};
} else {
tdType.textContent = "Alarm";
type = "Alarm";
inputTime.onchange = e => {
alarm.t = hmsToMs(inputTime.value);
};
}
}
tdType.textContent = type;
if (!exists) {
const asterisk = document.createElement('sup');
asterisk.textContent = '*';
@ -160,11 +183,33 @@ function renderAlarm(alarm, exists) {
inputTime.classList.add('form-input');
inputTime.dataset.uid = alarm.id;
const tdTime = document.createElement('td');
tr.appendChild(tdTime);
tdTime.appendChild(inputTime);
let tdDays;
if (type === "Alarm") {
tdDays = document.createElement('td');
const selectDays = document.createElement('select');
selectDays.multiple = true;
selectDays.classList.add('form-input', 'multi-select');
selectDays.dataset.uid = alarm.id;
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const options = days.map((day, i) => {
const option = document.createElement('option');
option.value = day;
option.text = day;
option.selected = alarm.dow & (1 << i);
if(day !== "Sun")
selectDays.appendChild(option);
return option;
});
selectDays.appendChild(options.find(o => o.text === "Sun"));
selectDays.onchange = (e => {
alarm.dow = options.reduce((bits, opt, i) => bits | (opt.selected ? 1 << i : 0), 0);
});
tdDays.appendChild(selectDays);
}
const tdSummary = document.createElement('td');
tr.appendChild(tdSummary);
const inputSummary = document.createElement('input');
inputSummary.type = "text";
inputSummary.classList.add('event-summary');
@ -181,9 +226,8 @@ function renderAlarm(alarm, exists) {
inputSummary.onchange();
const tdOptions = document.createElement('td');
tr.appendChild(tdOptions);
const onOffCheck = document.createElement('input');
tdOptions.classList.add('btn-center');
onOffCheck.type = 'checkbox';
onOffCheck.checked = alarm.on;
onOffCheck.onchange = e => {
@ -199,8 +243,6 @@ function renderAlarm(alarm, exists) {
tdOptions.appendChild(onOff);
const tdInfo = document.createElement('td');
tr.appendChild(tdInfo);
const buttonDelete = document.createElement('button');
buttonDelete.classList.add('btn');
buttonDelete.classList.add('btn-action');
@ -212,9 +254,22 @@ function renderAlarm(alarm, exists) {
buttonDelete.onclick = (e => {
alarms = alarms.filter(a => a !== alarm);
document.getElementById('events').removeChild(tr);
document.getElementById('events').removeChild(tr2);
});
tr.appendChild(tdTime); tdTime.colSpan = 3; tdTime.classList.add('single-row');
tr2.appendChild(tdType);
tr2.appendChild(tdOptions);
tr2.appendChild(tdInfo);
if (tdDays) {
tr.appendChild(tdDays); tdDays.rowSpan = 2;
} else {
tdSummary.colSpan = 2;
}
tr.appendChild(tdSummary); tdSummary.rowSpan = 2;
document.getElementById('events').appendChild(tr);
document.getElementById('events').appendChild(tr2);
document.getElementById('upload').disabled = false;
}
@ -318,11 +373,9 @@ function onInit() {
<table class="table">
<thead>
<tr>
<th>Type</th>
<th>Date/Time</th>
<th colspan="3">Time & Options</th>
<th>Days</th>
<th>Summary</th>
<th>On?</th>
<th></th>
</tr>
</thead>
<tbody id="events">

View File

@ -55,10 +55,7 @@ exports.getTimeToAlarm = function(alarm, time) {
/// Force a reload of the current alarms and widget
exports.reload = function() {
eval(require("Storage").read("sched.boot.js"));
if (global.WIDGETS && WIDGETS["alarm"]) {
WIDGETS["alarm"].reload();
Bangle.drawWidgets();
}
Bangle.emit("alarmReload");
};
// Factory that creates a new alarm with default values
exports.newDefaultAlarm = function () {

View File

@ -1,7 +1,7 @@
{
"id": "sched",
"name": "Scheduler",
"version": "0.22",
"version": "0.24",
"description": "Scheduling library for alarms and timers",
"icon": "app.png",
"type": "scheduler",

View File

@ -71,7 +71,7 @@ function showAlarm(alarm) {
const pattern = alarm.vibrate || (alarm.timer ? settings.defaultTimerPattern : settings.defaultAlarmPattern);
require("buzz").pattern(pattern).then(() => {
if (buzzCount--) {
if (buzzCount == null || buzzCount--) {
setTimeout(buzz, settings.buzzIntervalMillis);
} else if (alarm.as) { // auto-snooze
buzzCount = settings.buzzCount;

View File

@ -43,12 +43,13 @@
},
/*LANG*/"Buzz Count": {
value: settings.buzzCount,
min: 5,
value: settings.buzzCount == null ? 4 : settings.buzzCount,
min: 4,
max: 15,
step: 1,
format: v => v === 4 ? "Forever" : v,
onchange: v => {
settings.buzzCount = v;
settings.buzzCount = v === 4 ? null : v;
require("sched").setSettings(settings);
}
},

View File

@ -77,4 +77,5 @@ of 'Select Clock'
calibration was done.
0.67: Rename 'Wake on BTN1/Touch' to 'Wake on Button/Tap' on Bangle.js 2
0.68: Fix syntax error
0.69: Add option to wake on double tap
0.69: Add option to wake on double tap
0.70: Fix load() typo

View File

@ -1,7 +1,7 @@
{
"id": "setting",
"name": "Settings",
"version": "0.69",
"version": "0.70",
"description": "A menu for setting up Bangle.js",
"icon": "settings.png",
"tags": "tool,system",

Some files were not shown because too many files have changed in this diff Show More