Merge branch 'master' into env

pull/2890/head
Gordon Williams 2023-08-07 08:48:40 +01:00 committed by GitHub
commit bdcfb7e616
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
170 changed files with 4798 additions and 2343 deletions

View File

@ -98,7 +98,7 @@ This is the best way to test...
**Note:** It's a great idea to get a local copy of the repository on your PC,
then run `bin/sanitycheck.js` - it'll run through a bunch of common issues
that there might be. To get the project running locally, you have to initialize and update the git submodules first: `git submodule --init && git submodule update`.
that there might be. To get the project running locally, you have to initialize and update the git submodules first: `git submodule update --init`.
Be aware of the delay between commits and updates on github.io - it can take a few minutes (and a 'hard refresh' of your browser) for changes to take effect.

View File

@ -135,15 +135,17 @@
<h3>Utilities</h3>
<p>
<button class="btn tooltip" id="settime" data-tooltip="Set the Bangle's time to your Browser's time">Set Bangle.js Time</button>
<button class="btn tooltip" id="screenshot" data-tooltip="Create screenshot">Screenshot</button>
<button class="btn tooltip" id="downloadallapps" data-tooltip="Download all Bangle.js files to a ZIP file">Backup</button>
<button class="btn tooltip" id="uploadallapps" data-tooltip="Restore Bangle.js from a ZIP file">Restore</button>
</p><p>
<button class="btn tooltip" id="removeall" data-tooltip="Delete everything, leave it blank">Remove all Apps</button>
<button class="btn tooltip" id="reinstallall" data-tooltip="Re-install every app, leave all data">Reinstall apps</button>
<button class="btn tooltip" id="installdefault" data-tooltip="Delete everything, install default apps">Install default apps</button>
<button class="btn tooltip" id="installfavourite" data-tooltip="Delete everything, install your favourites">Install favourite apps</button>
<button class="btn tooltip" id="defaultbanglesettings" data-tooltip="Reset your Bangle's settings to the defaults">Reset Settings</button>
</p><p>
<button class="btn tooltip" id="newGithubIssue" data-tooltip="Create a new issue on GitHub">New issue on GitHub</button>
<button class="btn tooltip" id="downloadallapps" data-tooltip="Download all Bangle.js files to a ZIP file">Backup</button>
<button class="btn tooltip" id="uploadallapps" data-tooltip="Restore Bangle.js from a ZIP file">Restore</button>
<button class="btn tooltip" id="defaultbanglesettings" data-tooltip="Reset your Bangle's settings to the defaults">Reset Settings</button>
<button class="btn tooltip" id="webideremote" data-tooltip="Enable the Web IDE remote server">Web IDE Remote</button>
</p>
<h3>Settings</h3>
@ -172,6 +174,10 @@
<input type="checkbox" id="settings-minify">
<i class="form-icon"></i> Minify apps before upload (⚠DANGER⚠: Not recommended. Uploads smaller, faster apps but this <b>will</b> break many apps)
</label>
<label class="form-switch">
<input type="checkbox" id="settings-alwaysAllowUpdate">
<i class="form-icon"></i> Always allow to reinstall apps in place regardless of the version
</label>
<button class="btn" id="defaultsettings">Reset to default App Loader settings</button>
</details>
</div>

View File

@ -44,3 +44,4 @@
0.39: Dated event repeat option
0.40: Use substring of message when it's longer than fits the designated menu entry.
0.41: Fix a menu bug affecting alarms with empty messages.
0.42: Fix date not getting saved in event edit menu when tapping Confirm

View File

@ -190,7 +190,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
},
/*LANG*/"Cancel": () => showMainMenu(),
/*LANG*/"Confirm": () => {
prepareAlarmForSave(alarm, alarmIndex, time);
prepareAlarmForSave(alarm, alarmIndex, time, date);
saveAndReload();
showMainMenu();
}

View File

@ -2,7 +2,7 @@
"id": "alarm",
"name": "Alarms & Timers",
"shortName": "Alarms",
"version": "0.41",
"version": "0.42",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm",

View File

@ -29,3 +29,4 @@
0.28: Navigation messages no longer launch the Maps view unless they're new
0.29: Support for http request xpath return format
0.30: Send firmware and hardware versions on connection
Allow alarm enable/disable

View File

@ -86,7 +86,7 @@
var a = require("sched").newDefaultAlarm();
a.id = "gb"+j;
a.appid = "gbalarms";
a.on = true;
a.on = event.d[j].on !== undefined ? event.d[j].on : true;
a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
a.last = last;

View File

@ -2,3 +2,4 @@
0.02: Store last GPS lock, can be used instead of waiting for new GPS on start
0.03: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
0.04: Compatibility with Bangle.js 2, get location from My Location
0.05: Enable widgets

View File

@ -11,7 +11,6 @@
const SunCalc = require("suncalc"); // from modules folder
const storage = require("Storage");
const BANGLEJS2 = process.env.HWVERSION == 2; // check for bangle 2
function drawMoon(phase, x, y) {
const moonImgFiles = [
@ -110,7 +109,7 @@ function drawPoints() {
}
function drawData(title, obj, startX, startY) {
g.clear();
g.clearRect(Bangle.appRect);
drawTitle(title);
let xPos, yPos;
@ -154,9 +153,7 @@ function drawMoonPositionPage(gps, title) {
drawPoints();
drawPoint(azimuthDegrees, 8, moonColor);
let m = setWatch(() => {
let m = moonIndexPageMenu(gps);
}, BANGLEJS2 ? BTN : BTN3, {repeat: false, edge: "falling"});
Bangle.setUI({mode: "custom", back: () => moonIndexPageMenu(gps)});
}
function drawMoonIlluminationPage(gps, title) {
@ -174,9 +171,7 @@ function drawMoonIlluminationPage(gps, title) {
drawData(title, pageData, null, 35);
drawMoon(phaseIdx, g.getWidth() / 2, g.getHeight() / 2);
let m = setWatch(() => {
let m = moonIndexPageMenu(gps);
}, BANGLEJS2 ? BTN : BTN3, {repease: false, edge: "falling"});
Bangle.setUI({mode: "custom", back: () => moonIndexPageMenu(gps)});
}
@ -202,9 +197,7 @@ function drawMoonTimesPage(gps, title) {
const setAzimuthDegrees = parseInt(setPos.azimuth * 180 / Math.PI);
drawPoint(setAzimuthDegrees, 8, moonColor);
let m = setWatch(() => {
let m = moonIndexPageMenu(gps);
}, BANGLEJS2 ? BTN : BTN3, {repease: false, edge: "falling"});
Bangle.setUI({mode: "custom", back: () => moonIndexPageMenu(gps)});
}
function drawSunShowPage(gps, key, date) {
@ -233,9 +226,7 @@ function drawSunShowPage(gps, key, date) {
// Draw the suns position
drawPoint(azimuthDegrees, 8, {r: 1, g: 1, b: 0});
m = setWatch(() => {
m = sunIndexPageMenu(gps);
}, BANGLEJS2 ? BTN : BTN3, {repeat: false, edge: "falling"});
Bangle.setUI({mode: "custom", back: () => sunIndexPageMenu(gps)});
return null;
}
@ -314,7 +305,9 @@ function getCenterStringX(str) {
function init() {
let location = require("Storage").readJSON("mylocation.json",1)||{"lat":51.5072,"lon":0.1276,"location":"London"};
Bangle.loadWidgets();
indexPageMenu(location);
Bangle.drawWidgets();
}
let m;

View File

@ -1,7 +1,7 @@
{
"id": "astrocalc",
"name": "Astrocalc",
"version": "0.04",
"version": "0.05",
"description": "Calculates interesting information on the sun like sunset and sunrise and moon cycles for the current day based on your location from MyLocation app",
"icon": "astrocalc.png",
"tags": "app,sun,moon,cycles,tool",

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Handle missing settings (e.g. first-install)

View File

@ -1,5 +1,5 @@
(() => {
const chargingRotation = 0 | require('Storage').readJSON("chargerot.settings.json").rotate;
const chargingRotation = 0 | (require('Storage').readJSON("chargerot.settings.json",1)||{}).rotate;
const defaultRotation = 0 | require('Storage').readJSON("setting.json").rotate;
if (Bangle.isCharging()) g.setRotation(chargingRotation&3,chargingRotation>>2).clear();
Bangle.on('charging', (charging) => {

View File

@ -1,7 +1,7 @@
{
"id": "chargerot",
"name": "Charge LCD rotation",
"version": "0.01",
"version": "0.02",
"description": "When charging, this app can rotate your screen and revert it when unplugged. Made for all sort of cradles.",
"icon": "icon.png",
"tags": "battery",

View File

@ -4,4 +4,5 @@
0.04: On 2v18+ firmware, we can now stop swipe events from being handled by other apps
eg. when a clockinfo is selected, swipes won't affect swipe-down widgets
0.05: Reported image for battery is now transparent (2v18+)
0.06: When >1 clockinfo, swiping one back tries to ensure they don't display the same thing
0.06: When >1 clockinfo, swiping one back tries to ensure they don't display the same thing
0.07: Developer tweak: clkinfo load errors are emitted

View File

@ -141,7 +141,7 @@ exports.load = function() {
if(b) b.items = b.items.concat(a.items);
else menu = menu.concat(a);
} catch(e){
console.log("Could not load clock info "+E.toJS(fn));
console.log("Could not load clock info "+E.toJS(fn)+": "+e);
}
});

View File

@ -1,7 +1,7 @@
{ "id": "clock_info",
"name": "Clock Info Module",
"shortName": "Clock Info",
"version":"0.06",
"version":"0.07",
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
"icon": "app.png",
"type": "module",

View File

@ -5,3 +5,5 @@
0.05: Now scrolls text when string gets longer than screen width.
0.06: The code is now more reliable and the input snappier. Widgets will be drawn if present.
0.07: Settings for display colors
0.08: Catch and discard swipe events on fw2v19 and up (as well as some cutting
edge 2v18 ones), allowing compatability with the Back Swipe app.

View File

@ -103,6 +103,133 @@ exports.input = function(options) {
initDraw();
//setTimeout(initDraw, 0); // So Bangle.appRect reads the correct environment. It would draw off to the side sometimes otherwise.
let dragHandlerDB = function(event) {
"ram";
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
// Choose character by draging along red rectangle at bottom of screen
if (event.y >= ( (R.y+R.h) - 12 )) {
// Translate x-position to character
if (event.x < ABCPADDING) { abcHL = 0; }
else if (event.x >= 176-ABCPADDING) { abcHL = 25; }
else { abcHL = Math.floor((event.x-ABCPADDING)/6); }
// Datastream for development purposes
//print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev));
// Unmark previous character and mark the current one...
// Handling switching between letters and numbers/punctuation
if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
if (abcHL != abcHLPrev) {
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2);
}
// Print string at top of screen
if (event.b == 0) {
text = text + ABC.charAt(abcHL);
updateTopString();
// Autoswitching letter case
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL);
}
// Update previous character to current one
abcHLPrev = abcHL;
typePrev = 'abc';
}
// 12345678901234567890
// Choose number or puctuation by draging on green rectangle
else if ((event.y < ( (R.y+R.h) - 12 )) && (event.y > ( (R.y+R.h) - 52 ))) {
// Translate x-position to character
if (event.x < NUMPADDING) { numHL = 0; }
else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; }
else { numHL = Math.floor((event.x-NUMPADDING)/6); }
// Datastream for development purposes
//print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev));
// Unmark previous character and mark the current one...
// Handling switching between letters and numbers/punctuation
if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
if (numHL != numHLPrev) {
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4);
}
// Print string at top of screen
if (event.b == 0) {
g.setColor(HLCOLOR);
// Backspace if releasing before list of numbers/punctuation
if (event.x < NUMPADDING) {
// show delete sign
showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4);
delSpaceLast = 1;
text = text.slice(0, -1);
updateTopString();
//print(text);
}
// Append space if releasing after list of numbers/punctuation
else if (event.x > (R.x+R.w)-NUMPADDING) {
//show space sign
showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4);
delSpaceLast = 1;
text = text + ' ';
updateTopString();
//print(text);
}
// Append selected number/punctuation
else {
text = text + NUMHIDDEN.charAt(numHL);
updateTopString();
// Autoswitching letter case
if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase();
}
}
// Update previous character to current one
numHLPrev = numHL;
typePrev = 'num';
}
// Make a space or backspace by swiping right or left on screen above green rectangle
else if (event.y > 20+4) {
if (event.b == 0) {
g.setColor(HLCOLOR);
if (event.x < (R.x+R.w)/2) {
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
// show delete sign
showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4);
delSpaceLast = 1;
// Backspace and draw string upper right corner
text = text.slice(0, -1);
updateTopString();
if (text.length==0) changeCase(abcHL);
//print(text, 'undid');
}
else {
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
//show space sign
showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4);
delSpaceLast = 1;
// Append space and draw string upper right corner
text = text + NUMHIDDEN.charAt(0);
updateTopString();
//print(text, 'made space');
}
}
}
};
let catchSwipe = ()=>{
E.stopEventPropagation&&E.stopEventPropagation();
};
function changeCase(abcHL) {
if (settings.uppercase) return;
g.setColor(BGCOLOR);
@ -119,131 +246,12 @@ exports.input = function(options) {
mode: 'custom',
back: ()=>{
Bangle.setUI();
Bangle.prependListener&&Bangle.removeListener('swipe', catchSwipe); // Remove swipe lister if it was added with `Bangle.prependListener()` (fw2v19 and up).
g.clearRect(Bangle.appRect);
resolve(text);
},
drag: function(event) {
"ram";
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
// Choose character by draging along red rectangle at bottom of screen
if (event.y >= ( (R.y+R.h) - 12 )) {
// Translate x-position to character
if (event.x < ABCPADDING) { abcHL = 0; }
else if (event.x >= 176-ABCPADDING) { abcHL = 25; }
else { abcHL = Math.floor((event.x-ABCPADDING)/6); }
// Datastream for development purposes
//print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev));
// Unmark previous character and mark the current one...
// Handling switching between letters and numbers/punctuation
if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
if (abcHL != abcHLPrev) {
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2);
}
// Print string at top of screen
if (event.b == 0) {
text = text + ABC.charAt(abcHL);
updateTopString();
// Autoswitching letter case
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL);
}
// Update previous character to current one
abcHLPrev = abcHL;
typePrev = 'abc';
}
// 12345678901234567890
// Choose number or puctuation by draging on green rectangle
else if ((event.y < ( (R.y+R.h) - 12 )) && (event.y > ( (R.y+R.h) - 52 ))) {
// Translate x-position to character
if (event.x < NUMPADDING) { numHL = 0; }
else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; }
else { numHL = Math.floor((event.x-NUMPADDING)/6); }
// Datastream for development purposes
//print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev));
// Unmark previous character and mark the current one...
// Handling switching between letters and numbers/punctuation
if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
if (numHL != numHLPrev) {
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4);
}
// Print string at top of screen
if (event.b == 0) {
g.setColor(HLCOLOR);
// Backspace if releasing before list of numbers/punctuation
if (event.x < NUMPADDING) {
// show delete sign
showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4);
delSpaceLast = 1;
text = text.slice(0, -1);
updateTopString();
//print(text);
}
// Append space if releasing after list of numbers/punctuation
else if (event.x > (R.x+R.w)-NUMPADDING) {
//show space sign
showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4);
delSpaceLast = 1;
text = text + ' ';
updateTopString();
//print(text);
}
// Append selected number/punctuation
else {
text = text + NUMHIDDEN.charAt(numHL);
updateTopString();
// Autoswitching letter case
if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase();
}
}
// Update previous character to current one
numHLPrev = numHL;
typePrev = 'num';
}
// Make a space or backspace by swiping right or left on screen above green rectangle
else if (event.y > 20+4) {
if (event.b == 0) {
g.setColor(HLCOLOR);
if (event.x < (R.x+R.w)/2) {
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
// show delete sign
showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4);
delSpaceLast = 1;
// Backspace and draw string upper right corner
text = text.slice(0, -1);
updateTopString();
if (text.length==0) changeCase(abcHL);
//print(text, 'undid');
}
else {
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
//show space sign
showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4);
delSpaceLast = 1;
// Append space and draw string upper right corner
text = text + NUMHIDDEN.charAt(0);
updateTopString();
//print(text, 'made space');
}
}
}
}
drag: dragHandlerDB,
});
Bangle.prependListener&&Bangle.prependListener('swipe', catchSwipe); // Intercept swipes on fw2v19 and later. Should not break on older firmwares.
});
};

View File

@ -1,6 +1,6 @@
{ "id": "dragboard",
"name": "Dragboard",
"version":"0.07",
"version":"0.08",
"description": "A library for text input via swiping keyboard",
"icon": "app.png",
"type":"textinput",

View File

@ -1 +1,3 @@
0.01: New App based on dragboard, but with a U shaped drag area
0.02: Catch and discard swipe events on fw2v19 and up (as well as some cutting
edge 2v18 ones), allowing compatability with the Back Swipe app.

View File

@ -104,45 +104,53 @@ exports.input = function(options) {
}
}
let dragHandlerUB = function(event) {
"ram";
// drag on middle bottom rectangle
if (event.x > MIDPADDING - 2 && event.x < (R.x2-MIDPADDING + 2) && event.y >= ( (R.y2) - 12 )) {
moveCharPos(MIDDLE, event.b == 0, (event.x-middleStart)/(middleWidth/MIDDLE.length));
}
// drag on left or right rectangle
else if (event.y > R.y && (event.x < MIDPADDING-2 || event.x > (R.x2-MIDPADDING + 2))) {
moveCharPos(event.x<MIDPADDING-2 ? LEFT : RIGHT, event.b == 0, (event.y-topStart)/((R.y2 - topStart)/vLength));
}
// drag on top rectangle for number or punctuation
else if ((event.y < ( (R.y2) - 12 )) && (event.y > ( (R.y2) - 52 ))) {
moveCharPos(NUM, event.b == 0, (event.x-NUMPADDING)/6);
}
// Make a space or backspace by tapping right or left on screen above green rectangle
else if (event.y > R.y && event.b == 0) {
if (event.x < (R.x2)/2) {
showChars('<-');
text = text.slice(0, -1);
} else {
//show space sign
showChars('->');
text += ' ';
}
prevChar = null;
updateTopString();
}
};
let catchSwipe = ()=>{
E.stopEventPropagation&&E.stopEventPropagation();
};
return new Promise((resolve,reject) => {
// Interpret touch input
Bangle.setUI({
mode: 'custom',
back: ()=>{
Bangle.setUI();
Bangle.prependListener&&Bangle.removeListener('swipe', catchSwipe); // Remove swipe lister if it was added with `Bangle.prependListener()` (fw2v19 and up).
g.clearRect(Bangle.appRect);
resolve(text);
},
drag: function(event) {
"ram";
// drag on middle bottom rectangle
if (event.x > MIDPADDING - 2 && event.x < (R.x2-MIDPADDING + 2) && event.y >= ( (R.y2) - 12 )) {
moveCharPos(MIDDLE, event.b == 0, (event.x-middleStart)/(middleWidth/MIDDLE.length));
}
// drag on left or right rectangle
else if (event.y > R.y && (event.x < MIDPADDING-2 || event.x > (R.x2-MIDPADDING + 2))) {
moveCharPos(event.x<MIDPADDING-2 ? LEFT : RIGHT, event.b == 0, (event.y-topStart)/((R.y2 - topStart)/vLength));
}
// drag on top rectangle for number or punctuation
else if ((event.y < ( (R.y2) - 12 )) && (event.y > ( (R.y2) - 52 ))) {
moveCharPos(NUM, event.b == 0, (event.x-NUMPADDING)/6);
}
// Make a space or backspace by tapping right or left on screen above green rectangle
else if (event.y > R.y && event.b == 0) {
if (event.x < (R.x2)/2) {
showChars('<-');
text = text.slice(0, -1);
} else {
//show space sign
showChars('->');
text += ' ';
}
prevChar = null;
updateTopString();
}
}
drag: dragHandlerDB
});
Bangle.prependListener&&Bangle.prependListener('swipe', catchSwipe); // Intercept swipes on fw2v19 and later. Should not break on older firmwares.
R = Bangle.appRect;
MIDPADDING = R.x + 35;

View File

@ -1,6 +1,6 @@
{ "id": "draguboard",
"name": "DragUboard",
"version":"0.01",
"version":"0.02",
"description": "A library for text input via swiping U-shaped keyboard.",
"icon": "app.png",
"type":"textinput",

View File

@ -28,4 +28,4 @@ immediately follows the correct theme.
0.22: Bangle 2: Change to not automatically marking the first app on a page
when moving pages. Add caching for faster startups.
0.23: Bangle 1: Fix issue with missing icons, added touch screen interactions
0.24: Add buzz-on-interaction setting

View File

@ -9,7 +9,8 @@
showLaunchers: true,
direct: false,
swipeExit: false,
timeOut: "Off"
timeOut: "Off",
interactionBuzz: false,
}, require('Storage').readJSON("dtlaunch.json", true) || {});
let s = require("Storage");
@ -89,6 +90,13 @@
g.flip();
};
let buzzShort = function() {
if (settings.interactionBuzz) Bangle.buzz(20);
};
let buzzLong = function() {
if (settings.interactionBuzz) Bangle.buzz(100);
};
Bangle.drawWidgets(); // To immediately update widget field to follow current theme - remove leftovers if previous app set custom theme.
Bangle.loadWidgets();
drawPage(0);
@ -100,9 +108,11 @@
if(settings.swipeExit && dirLeftRight==1) Bangle.showClock();
if (dirUpDown==-1||dirLeftRight==-1){
++page; if (page>maxPage) page=0;
buzzShort();
drawPage(page);
} else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){
--page; if (page<0) page=maxPage;
buzzShort();
drawPage(page);
}
};
@ -123,8 +133,10 @@
drawIcon(page,i,true && !settings.direct);
if (selected>=0 || settings.direct) {
if (selected!=i && !settings.direct){
buzzShort();
drawIcon(page,selected,false);
} else {
buzzLong();
load(apps[page*4+i].src);
}
}
@ -134,6 +146,7 @@
}
}
if ((i==4 || (page*4+i)>Napps) && selected>=0) {
buzzShort();
drawIcon(page,selected,false);
selected=-1;
}

View File

@ -1,7 +1,7 @@
{
"id": "dtlaunch",
"name": "Desktop Launcher",
"version": "0.23",
"version": "0.24",
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
"icon": "icon.png",

View File

@ -6,7 +6,8 @@
showLaunchers: true,
direct: false,
swipeExit: false,
timeOut: "Off"
timeOut: "Off",
interactionBuzz: false,
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings() {
@ -55,6 +56,13 @@
settings.timeOut = timeOutChoices[v];
writeSettings();
}
}
},
/*LANG*/'Interaction buzz': {
value: settings.interactionBuzz,
onchange: v => {
settings.interactionBuzz = v;
writeSettings();
}
},
});
});

View File

@ -2,3 +2,5 @@
0.02: Allow redirection of loads to the launcher
0.03: Allow hiding the fastloading info screen
0.04: (WIP) Allow use of app history when going back (`load()` or `Bangle.load()` calls without specified app).
0.05: Check for changes in setting.js and force real reload if needed
0.06: Fix caching whether an app is fastloadable

View File

@ -12,6 +12,7 @@ This allows fast loading of all apps with two conditions:
* If Quick Launch is installed it can be excluded from app history
* Allows to redirect all loads usually loading the clock to the launcher instead
* The "Fastloading..." screen can be switched off
* Enable checking `setting.json` and force a complete load on changes
## App history

View File

@ -19,15 +19,24 @@ let loadingScreen = function(){
let cache = s.readJSON("fastload.cache") || {};
let checkApp = function(n){
const SYS_SETTINGS="setting.json";
let appFastloadPossible = function(n){
if(SETTINGS.detectSettingsChange && (!cache[SYS_SETTINGS] || s.hash(SYS_SETTINGS) != cache[SYS_SETTINGS])){
cache[SYS_SETTINGS] = s.hash(SYS_SETTINGS);
s.writeJSON("fastload.cache", cache);
return false;
}
// no widgets, no problem
if (!global.WIDGETS) return true;
let app = s.read(n);
if (cache[n] && E.CRC32(app) == cache[n].crc)
let hash = s.hash(n);
if (cache[n] && hash == cache[n].hash)
return cache[n].fast;
let app = s.read(n);
cache[n] = {};
cache[n].fast = app.includes("Bangle.loadWidgets");
cache[n].crc = E.CRC32(app);
cache[n].hash = hash;
s.writeJSON("fastload.cache", cache);
return cache[n].fast;
};
@ -39,7 +48,7 @@ let slowload = function(n){
};
let fastload = function(n){
if (!n || checkApp(n)){
if (!n || appFastloadPossible(n)){
// Bangle.load can call load, to prevent recursion this must be the system load
global.load = slowload;
Bangle.load(n);

View File

@ -1,7 +1,7 @@
{ "id": "fastload",
"name": "Fastload Utils",
"shortName" : "Fastload Utils",
"version": "0.04",
"version": "0.06",
"icon": "icon.png",
"description": "Enable experimental fastloading for more apps",
"type":"bootloader",

View File

@ -59,6 +59,13 @@
}
};
mainmenu['Detect settings changes'] = {
value: !!settings.detectSettingsChange,
onchange: v => {
writeSettings("detectSettingsChange",v);
}
};
return mainmenu;
}

View File

@ -1,4 +1,5 @@
1.0: Local cards data
1.1: Download cards data from Trello public board
1.2: Configuration instructions added and card layout optimized
1.3: Font size can be changed in Settings
1.00: Local cards data
1.10: Download cards data from Trello public board
1.20: Configuration instructions added and card layout optimized
1.30: Font size can be changed in Settings
1.31: Fix for fast-loading support

View File

@ -10,7 +10,7 @@ Configuration:
4. Add ".json" to the end of the Trello board URL and refresh page
5. Find your list ID
6. Save list ID to the "flashcards.settings.json" file on your watch, e.g.:
{"listId":"65942f7b27z68000996ddc00","fontSize":1,"cardWidth":9,"swipeGesture":0}
{"listId":"65942f7b27z68000996ddc00","fontSize":1,"cardWidth":9,"swipeGesture":1}
7. Connect phone with Gadgetbridge to the watch
8. Enable "Allow Internet Access" in Gadgetbridge
9. On the watch go to Settings -> Apps -> Flash Cards -> Get from Trello

View File

@ -2,186 +2,185 @@
* Copyright 2023 Crisp Advice
* We believe in Finnish
*/
{
// Modules
let Layout = require("Layout");
let locale = require("locale");
let storage = require("Storage");
// Modules
var Layout = require("Layout");
var locale = require("locale");
var storage = require("Storage");
// Global variables
const SWAP_SIDE_BUZZ_MILLISECONDS = 50;
const CARD_DATA_FILE = "flashcards.data.json";
const CARD_SETTINGS_FILE = "flashcards.settings.json";
const CARD_EMPTY = "no cards found";
let cards = [];
let cardIndex = 0;
let backSide = false;
let drawTimeout;
let fontSizes = ["15%","20%","25%"];
let lastDragX = 0;
let lastDragY = 0;
// Global variables
let SWAP_SIDE_BUZZ_MILLISECONDS = 50;
let CARD_DATA_FILE = "flashcards.data.json";
let CARD_SETTINGS_FILE = "flashcards.settings.json";
let CARD_EMPTY = "no cards found";
let cards = [];
let cardIndex = 0;
let backSide = false;
let drawTimeout;
let fontSizes = ["15%","20%","25%"];
let lastDragX = 0;
let lastDragY = 0;
let settings = Object.assign({
listId: "",
fontSize: 1,
cardWidth: 9,
swipeGesture: 1
}, storage.readJSON(CARD_SETTINGS_FILE, true) || {});
let settings = Object.assign({
listId: "",
fontSize: 1,
cardWidth: 9,
swipeGesture: 0
}, storage.readJSON(CARD_SETTINGS_FILE, true) || {});
// Cards data
function wordWrap(textStr, maxLength) {
if (maxLength == undefined) {
maxLength = settings.cardWidth;
}
let res = '';
let str = textStr.trim();
while (str.length > maxLength) {
let found = false;
// Inserts new line at first whitespace of the line
for (i = maxLength - 1; i > 0; i--) {
if (str.charAt(i)==' ') {
res = res + [str.slice(0, i), "\n"].join('');
str = str.slice(i + 1);
found = true;
break;
// Cards data
let wordWrap = function (textStr, maxLength) {
if (maxLength == undefined) {
maxLength = settings.cardWidth;
}
let res = '';
let str = textStr.trim();
while (str.length > maxLength) {
let found = false;
// Inserts new line at first whitespace of the line
for (i = maxLength - 1; i > 0; i--) {
if (str.charAt(i)==' ') {
res = res + [str.slice(0, i), "\n"].join('');
str = str.slice(i + 1);
found = true;
break;
}
}
// Inserts new line at MAX_LENGTH position, the word is too long to wrap
if (!found) {
res += [str.slice(0, maxLength), "\n"].join('');
str = str.slice(maxLength);
}
}
// Inserts new line at MAX_LENGTH position, the word is too long to wrap
if (!found) {
res += [str.slice(0, maxLength), "\n"].join('');
str = str.slice(maxLength);
}
return res + str;
}
return res + str;
}
function loadLocalCards() {
var cardsJSON = "";
if (storage.read(CARD_DATA_FILE))
let loadLocalCards = function() {
var cardsJSON = "";
if (storage.read(CARD_DATA_FILE))
{
cardsJSON = storage.readJSON(CARD_DATA_FILE, 1) || {};
}
refreshCards(cardsJSON,false);
}
let refreshCards = function(cardsJSON,showMsg)
{
cardsJSON = storage.readJSON(CARD_DATA_FILE, 1) || {};
}
refreshCards(cardsJSON,false);
}
cardIndex = 0;
backSide = false;
cards = [];
function refreshCards(cardsJSON,showMsg)
{
cardIndex = 0;
backSide = false;
cards = [];
if (cardsJSON && cardsJSON.length) {
cardsJSON.forEach(card => {
cards.push([ wordWrap(card.name), wordWrap(card.desc) ]);
});
}
if (cardsJSON && cardsJSON.length) {
cardsJSON.forEach(card => {
cards.push([ wordWrap(card.name), wordWrap(card.desc) ]);
});
}
if (!cards.length) {
cards.push([ wordWrap(CARD_EMPTY), wordWrap(CARD_EMPTY) ]);
drawMessage("e: cards not found");
} else if (showMsg) {
drawMessage("i: cards refreshed");
}
}
// Drawing a card
let queueDraw = function() {
let timeout = 60000;
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, timeout - (Date.now() % timeout));
};
let cardLayout = new Layout( {
type:"v", c: [
{type:"txt", font:"6x8:3", label:"", id:"widgets", fillx:1 },
{type:"txt", font:fontSizes[settings.fontSize], label:"ABCDEFGHIJ KLMNOPQRST UVWXYZÅÖÄ", filly:1, fillx:1, id:"card" },
{type:"txt", font:"6x8:2", label:"00:00", id:"clock", fillx:1, bgCol:g.theme.fg, col:g.theme.bg }
]
}, {lazy:true});
function drawCard() {
cardLayout.card.label = cards[cardIndex][backSide ? 1 : 0];
cardLayout.clock.label = locale.time(new Date(),1);
cardLayout.render();
}
function drawMessage(msg) {
cardLayout.card.label = wordWrap(msg);
cardLayout.render();
console.log(msg);
}
function draw() {
drawCard();
Bangle.drawWidgets();
queueDraw();
}
function swipeCard(forward)
{
if(forward) {
cardIndex = (cardIndex + 1) % cards.length;
}
else if(--cardIndex < 0) {
cardIndex = cards.length - 1;
}
drawCard();
}
// Handle a touch: swap card side
function handleTouch(zone, event) {
backSide = !backSide;
drawCard();
Bangle.buzz(SWAP_SIDE_BUZZ_MILLISECONDS);
}
// Handle a stroke event: cycle cards
function handleStroke(event) {
let first_x = event.xy[0];
let last_x = event.xy[event.xy.length - 2];
swipeCard((last_x - first_x) > 0);
}
// Handle a drag event: cycle cards
function handleDrag(event) {
let isFingerReleased = (event.b === 0);
if(isFingerReleased) {
let isHorizontalDrag = (Math.abs(lastDragX) >= Math.abs(lastDragY)) &&
(lastDragX !== 0);
if(isHorizontalDrag) {
swipeCard(lastDragX > 0);
if (!cards.length) {
cards.push([ wordWrap(CARD_EMPTY), wordWrap(CARD_EMPTY) ]);
drawMessage("e: cards not found");
} else if (showMsg) {
drawMessage("i: cards refreshed");
}
}
else {
lastDragX = event.dx;
lastDragY = event.dy;
// Drawing a card
let queueDraw = function() {
let timeout = 60000;
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, timeout - (Date.now() % timeout));
};
let cardLayout = new Layout( {
type:"v", c: [
{type:"txt", font:"6x8:3", label:"", id:"widgets", fillx:1 },
{type:"txt", font:fontSizes[settings.fontSize], label:"ABCDEFGHIJ KLMNOPQRST UVWXYZÅÖÄ", filly:1, fillx:1, id:"card" },
{type:"txt", font:"6x8:2", label:"00:00", id:"clock", fillx:1, bgCol:g.theme.fg, col:g.theme.bg }
]
}, {lazy:true});
let drawCard = function() {
cardLayout.card.label = cards[cardIndex][backSide ? 1 : 0];
cardLayout.clock.label = locale.time(new Date(),1);
cardLayout.render();
}
let drawMessage = function(msg) {
cardLayout.card.label = wordWrap(msg);
cardLayout.render();
console.log(msg);
}
let draw = function() {
drawCard();
Bangle.drawWidgets();
queueDraw();
}
let swipeCard = function(forward)
{
if(forward) {
cardIndex = (cardIndex + 1) % cards.length;
}
else if(--cardIndex < 0) {
cardIndex = cards.length - 1;
}
drawCard();
}
// Handle a touch: swap card side
let handleTouch = function(zone, event) {
backSide = !backSide;
drawCard();
Bangle.buzz(SWAP_SIDE_BUZZ_MILLISECONDS);
}
// Handle a stroke event: cycle cards
let handleStroke = function(event) {
let first_x = event.xy[0];
let last_x = event.xy[event.xy.length - 2];
swipeCard((last_x - first_x) > 0);
}
// Handle a drag event: cycle cards
let handleDrag = function(event) {
let isFingerReleased = (event.b === 0);
if(isFingerReleased) {
let isHorizontalDrag = (Math.abs(lastDragX) >= Math.abs(lastDragY)) &&
(lastDragX !== 0);
if(isHorizontalDrag) {
swipeCard(lastDragX > 0);
}
}
else {
lastDragX = event.dx;
lastDragY = event.dy;
}
}
// Ensure pressing the button goes to the launcher (by making this seem like a clock?)
Bangle.setUI({mode:"clock", remove:function() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
Bangle.removeListener("touch", handleTouch);
if (settings.swipeGesture) { Bangle.removeListener("drag", handleDrag);} else { Bangle.removeListener("stroke", handleStroke); }
}});
// initialize
cardLayout.update();
Bangle.loadWidgets();
loadLocalCards();
Bangle.on("touch", handleTouch);
if (settings.swipeGesture) { Bangle.on("drag", handleDrag); } else { Bangle.on("stroke", handleStroke); }
// On start: display the first card
g.clear();
draw();
}
// Ensure pressing the button goes to the launcher (by making this seem like a clock?)
Bangle.setUI({mode:"clock"/*, remove:function() {
// Code to enable fast load. NOTE: this doesn't work on this app because all
// functions and vars are declared global: https://www.espruino.com/Bangle.js+Fast+Load
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
Bangle.removeListener("touch", handleTouch);
if (settings.swipeGesture) { Bangle.removeListener("drag", handleDrag);} else { Bangle.removeListener("stroke", handleStroke); }
}*/});
// initialize
cardLayout.update();
Bangle.loadWidgets();
loadLocalCards();
Bangle.on("touch", handleTouch);
if (settings.swipeGesture) { Bangle.on("drag", handleDrag); } else { Bangle.on("stroke", handleStroke); }
// On start: display the first card
g.clear();
draw();

View File

@ -1 +1 @@
{"listId":"","fontSize":1,"cardWidth":9,"swipeGesture":0}
{"listId":"","fontSize":1,"cardWidth":9,"swipeGesture":1}

View File

@ -2,7 +2,7 @@
"id": "flashcards",
"name": "Flash Cards",
"shortName": "Flash Cards",
"version": "1.3",
"version": "1.31",
"description": "Flash cards based on public Trello board",
"readme":"README.md",
"screenshots" : [ { "url":"screenshot.png" }],

View File

@ -10,7 +10,7 @@
listId: "",
fontSize: 1,
cardWidth: 9,
swipeGesture: 0
swipeGesture: 1
}, storage.readJSON(settingsFile, true) || {});
function writeSettings() {
@ -24,7 +24,7 @@
"< Back" : () => back(),
/*LANG*/"Get from Trello": () => {
if (!storage.read(settingsFile)) { writeSettings();}
E.showPrompt("Download cards?").then((v) => {
E.showPrompt(/*LANG*/"Download cards?").then((v) => {
let delay = 500;
if (v) {
if (Bangle.http)

View File

@ -87,3 +87,14 @@
* Reduce framerate if locked
* Stroke to move around in the map
* Fix for missing paths in display
0.20:
* Large display for instant speed
* Bugfix for negative coordinates
* Disable menu while the map is not loaded
* Turn screen off while idling to save battery (with setting)
* New setting : disable buzz on turns
* New setting : turn bluetooth off to save battery
* New setting : power screen off between points to save battery
* Color change for lost direction (now purple)
* Adaptive screen powersaving

View File

@ -18,8 +18,8 @@ It provides the following features :
- display the path with current position from gps
- display a local map around you, downloaded from openstreetmap
- detects and buzzes if you leave the path
- buzzes before sharp turns
- buzzes before waypoints
- (optional) buzzes before sharp turns
- (optional) buzzes before waypoints
(for example when you need to turn in https://mapstogpx.com/)
- display instant / average speed
- display distance to next point
@ -51,8 +51,8 @@ Your path will be displayed in svg.
### Starting Gipy
At start you will have a menu for selecting your trace (if more than one).
Choose the one you want and you will reach the splash screen where you'll wait for the gps signal.
Once you have a signal you will reach the main screen:
Choose the one you want and you will reach the splash screen where you'll wait for the map.
Once the map is loaded you will reach the main screen:
![Screenshot](legend.png)
@ -83,7 +83,7 @@ On your screen you can see:
### Lost
If you stray away from path we will rescale the display to continue displaying nearby segments and
display the direction to follow as a black segment.
display the direction to follow as a purple segment.
Note that while lost, the app will slow down a lot since it will start scanning all possible points to figure out where you
are. On path it just needed to scan a few points ahead and behind.
@ -93,13 +93,18 @@ The distance to next point displayed corresponds to the length of the black segm
### Menu
If you click the button you'll reach a menu where you can currently zoom out to see more of the map
(with a slower refresh rate) and reverse the path direction.
(with a slower refresh rate), reverse the path direction and disable power saving (keeping backlight on).
### Settings
Few settings for now (feel free to suggest me more) :
- lost distance : at which distance from path are you considered to be lost ?
- buzz on turns : should the watch buzz when reaching a waypoint ?
- disable bluetooth : turn bluetooth off completely to try to save some power.
- brightness : how bright should screen be ? (by default 0.5, again saving power)
- power lcd off (disabled by default): turn lcd off when inactive to save power. the watch will wake up when reaching points,
when you touch the screen and when speed is below 13km/h.
### Caveats
@ -107,6 +112,7 @@ It is good to use but you should know :
- the gps might take a long time to start initially (see the assisted gps update app).
- gps signal is noisy : there is therefore a small delay for instant speed. sometimes you may jump somewhere else.
- if you adventure in gorges the gps signal will become garbage.
- your gpx trace has been decimated and approximated : the **REAL PATH** might be **A FEW METERS AWAY**
- sometimes the watch will tell you that you are lost but you are in fact on the path. It usually figures again
the real gps position after a few minutes. It usually happens when the signal is acquired very fast.
@ -119,4 +125,8 @@ I had to go back uphill by quite a distance.
Feel free to give me feedback : is it useful for you ? what other features would you like ?
If you want to raise issues the main repository is [https://github.com/wagnerf42/BangleApps](here) and
the rust code doing the actual map computations is located [https://github.com/wagnerf42/gps](here).
You can try the cutting edge version at [https://wagnerf42.github.io/BangleApps/](https://wagnerf42.github.io/BangleApps/)
frederic.wagner@imag.fr

View File

@ -1,19 +1,51 @@
*** thoughts on lcd power ***
so, i tried experimenting with turning the lcd off in order to save power.
the good news: this saves a lot. i did a 3h ride which usually depletes the battery and I still had
around two more hours to go.
now the bad news:
- i had to de-activate the twist detection : you cannot raise your watch to the eyes to turn it on.
that's because with twist detection on all road bumps turn the watch on constantly.
- i tried manual detection like :
Bangle.on('accel', function(xyz) {
if (xyz.diff > 0.4 && xyz.mag > 1 && xyz.z < -1.4) {
Bangle.setLCDPower(true);
Bangle.setLocked(false);
}
});
this works nicely when you sit on a chair with a simulated gps signal but does not work so nicely when on the bike.
sometimes it is ok, sometimes you can try 10 times with no success.
- instead i use screen touch to turn it on. that's a bother since you need two hands but well it could be worth it.
the problem is in the delay: between 1 and 5 seconds before the screen comes back on.
my conclusion is that:
* we should not turn screen off unless we agree to have an unresponsive ui
* we should maybe autowake near segments ends and when lost
* we should play with backlight instead
**************************
+ when you walk the direction still has a tendency to shift
+ put back foot only ways
+ try fiddling with jit
+ put back street names
+ put back shortest paths but with points cache this time and jit
+ how to display paths from shortest path ?
misc:
+ use Bangle.project(latlong)
* additional features
- config screen
- are we on foot (and should use compass)
- we need to buzz 200m before sharp turns (or even better, 30seconds)
(and look at more than next point)
- display distance to next water/toilet ?
- display scale (100m)
- compress path ?
* misc
- code is becoming messy

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
"id": "gipy",
"name": "Gipy",
"shortName": "Gipy",
"version": "0.19",
"version": "0.20",
"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

@ -67,11 +67,11 @@ export interface InitOutput {
readonly __wbindgen_malloc: (a: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
readonly __wbindgen_export_2: WebAssembly.Table;
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hab13c10d53cd1c5a: (a: number, b: number, c: number) => void;
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__heb2f4d39a212d7d1: (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__h26ce002f44a5439b: (a: number, b: number, c: number, d: number) => void;
readonly wasm_bindgen__convert__closures__invoke2_mut__h362f82c7669db137: (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._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hab13c10d53cd1c5a(arg0, arg1, addHeapObject(arg2));
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__heb2f4d39a212d7d1(arg0, arg1, addHeapObject(arg2));
}
function _assertClass(instance, klass) {
@ -369,7 +369,7 @@ function handleError(f, args) {
}
}
function __wbg_adapter_84(arg0, arg1, arg2, arg3) {
wasm.wasm_bindgen__convert__closures__invoke2_mut__h26ce002f44a5439b(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
wasm.wasm_bindgen__convert__closures__invoke2_mut__h362f82c7669db137(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
}
/**
@ -460,6 +460,21 @@ 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);
}, arguments) };
imports.wbg.__wbg_new_2d0053ee81e4dd2a = function() { return handleError(function () {
const ret = new Headers();
return addHeapObject(ret);
@ -496,21 +511,6 @@ function getImports() {
const ret = getObject(arg0).text();
return addHeapObject(ret);
}, arguments) };
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);
}, arguments) };
imports.wbg.__wbg_new_abda76e883ba8a5f = function() {
const ret = new Error();
return addHeapObject(ret);
@ -675,8 +675,8 @@ function getImports() {
const ret = wasm.memory;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper2298 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 260, __wbg_adapter_24);
imports.wbg.__wbindgen_closure_wrapper2245 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 267, __wbg_adapter_24);
return addHeapObject(ret);
};

Binary file not shown.

View File

@ -12,8 +12,8 @@ export function gps_from_area(a: number, b: number, c: number, d: number): numbe
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 _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hab13c10d53cd1c5a(a: number, b: number, c: number): void;
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__heb2f4d39a212d7d1(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__h26ce002f44a5439b(a: number, b: number, c: number, d: number): void;
export function wasm_bindgen__convert__closures__invoke2_mut__h362f82c7669db137(a: number, b: number, c: number, d: number): void;

View File

@ -1,29 +1,66 @@
(function (back) {
var FILE = "gipy.json";
// Load settings
var settings = Object.assign(
{
lost_distance: 50,
},
require("Storage").readJSON(FILE, true) || {}
);
(function(back) {
var FILE = "gipy.json";
// Load settings
var settings = Object.assign({
lost_distance: 50,
buzz_on_turns: false,
disable_bluetooth: true,
brightness: 0.5,
power_lcd_off: false,
},
require("Storage").readJSON(FILE, true) || {}
);
function writeSettings() {
require("Storage").writeJSON(FILE, settings);
}
function writeSettings() {
require("Storage").writeJSON(FILE, settings);
}
// Show the menu
E.showMenu({
"": { title: "Gipy" },
"< Back": () => back(),
"lost distance": {
value: 50 | settings.lost_distance, // 0| converts undefined to 0
min: 10,
max: 500,
onchange: (v) => {
settings.max_speed = v;
writeSettings();
},
},
});
// Show the menu
E.showMenu({
"": {
title: "Gipy"
},
"< Back": () => back(),
/*LANG*/"buzz on turns": {
value: settings.buzz_on_turns == true,
onchange: (v) => {
settings.buzz_on_turns = v;
writeSettings();
}
},
/*LANG*/"disable bluetooth": {
value: settings.disable_bluetooth == true,
onchange: (v) => {
settings.disable_bluetooth = v;
writeSettings();
}
},
"lost distance": {
value: settings.lost_distance,
min: 10,
max: 500,
onchange: (v) => {
settings.lost_distance = v;
writeSettings();
},
},
"brightness": {
value: settings.brightness,
min: 0,
max: 1,
step: 0.1,
onchange: (v) => {
settings.brightness = v;
writeSettings();
},
},
/*LANG*/"power lcd off": {
value: settings.power_lcd_off == true,
onchange: (v) => {
settings.power_lcd_off = v;
writeSettings();
}
},
});
});

View File

@ -15,4 +15,7 @@
Save state if route or waypoint has been chosen
0.09: Workaround a minifier issue allowing to install gpstrek with minification enabled
0.10: Adds map view of loaded route
Automatically search for new waypoint if moving away from current target
Automatically search for new waypoint if moving away from current target
0.11: Adds configuration
Draws direction arrows on route
Turn of compass when GPS fix is available

View File

@ -7,25 +7,12 @@ const MODE_SLICES = 2;
const STORAGE = require("Storage");
const BAT_FULL = require("Storage").readJSON("setting.json").batFullVoltage || 0.3144;
const SETTINGS = {
mapCompass: true,
mapScale:0.2, //initial value
mapRefresh:1000, //minimum time in ms between refreshs of the map
mapChunkSize: 5, //render this many waypoints at a time
overviewScroll: 30, //scroll this amount on swipe in pixels
overviewScale: 0.02, //initial value
refresh:500, //general refresh interval in ms
refreshLocked:3000, //general refresh interval when Bangle is locked
cacheMinFreeMem:2000,
cacheMaxEntries:0,
minCourseChange: 5, //course change needed in degrees before redrawing the map
minPosChange: 5, //position change needed in pixels before redrawing the map
waypointChangeDist: 50, //distance in m to next waypoint before advancing automatically
queueWaitingTime: 5, // waiting time during processing of task queue items when running with timeouts
autosearch: true,
maxDistForAutosearch: 300,
autosearchLimit: 3
};
const SETTINGS = Object.assign(
require('Storage').readJSON("gpstrek.default.json", true) || {},
require('Storage').readJSON("gpstrek.json", true) || {}
);
let init = function(){
global.screen = 1;
@ -38,7 +25,6 @@ let init = function(){
Bangle.loadWidgets();
WIDGETS.gpstrek.start(false);
if (!WIDGETS.gpstrek.getState().numberOfSlices) WIDGETS.gpstrek.getState().numberOfSlices = 2;
if (!WIDGETS.gpstrek.getState().mode) WIDGETS.gpstrek.getState().mode = MODE_MENU;
};
@ -184,11 +170,6 @@ let getDoubleLineSlice = function(title1,title2,provider1,provider2){
};
};
const dot = Graphics.createImage(`
XX
XX
`);
const arrow = Graphics.createImage(`
X
XXX
@ -198,6 +179,14 @@ const arrow = Graphics.createImage(`
XXX XXX
`);
const thinarrow = Graphics.createImage(`
X
XXX
XX XX
XX XX
XX XX
`);
const cross = Graphics.createImage(`
XX XX
XX XX
@ -459,7 +448,7 @@ let getMapSlice = function(){
if (!isMapOverview){
drawCurrentPos();
}
if (!isMapOverview && renderInTimeouts){
if (SETTINGS.mapCompass && !isMapOverview && renderInTimeouts){
drawMapCompass();
}
if (renderInTimeouts) drawInterface();
@ -472,7 +461,8 @@ let getMapSlice = function(){
i:startingIndex,
poly:[],
maxWaypoints: maxWaypoints,
breakLoop: false
breakLoop: false,
dist: 0
};
let drawChunk = function(data){
@ -483,6 +473,7 @@ let getMapSlice = function(){
let last;
let toDraw;
let named = [];
let dir = [];
for (let j = 0; j < SETTINGS.mapChunkSize; j++){
data.i = data.i + (reverse?-1:1);
let p = get(route, data.i);
@ -497,7 +488,17 @@ let getMapSlice = function(){
break;
}
toDraw = Bangle.project(p);
if (p.name) named.push({i:data.poly.length,n:p.name});
if (SETTINGS.mapDirection){
let lastWp = get(route, data.i - (reverse?-1:1));
if (lastWp) data.dist+=distance(lastWp,p);
if (!isMapOverview && data.dist > 20/mapScale){
dir.push({i:data.poly.length,b:require("graphics_utils").degreesToRadians(bearing(lastWp,p)-(reverse?0:180))});
data.dist=0;
}
}
if (p.name)
named.push({i:data.poly.length,n:p.name});
data.poly.push(startingPoint.x-toDraw.x);
data.poly.push((startingPoint.y-toDraw.y)*-1);
}
@ -518,7 +519,11 @@ let getMapSlice = function(){
}
graphics.drawString(c.n, data.poly[c.i] + 10, data.poly[c.i+1]);
}
for (let c of dir){
graphics.drawImage(thinarrow, data.poly[c.i], data.poly[c.i+1], {rotate: c.b});
}
if (finish)
graphics.drawImage(finishIcon, data.poly[data.poly.length - 2] -5, data.poly[data.poly.length - 1] - 4);
else if (last) {
@ -1254,11 +1259,6 @@ let showMenu = function(){
"Background" : showBackgroundMenu,
"Calibration": showCalibrationMenu,
"Reset" : ()=>{ E.showPrompt("Do Reset?").then((v)=>{ if (v) {WIDGETS.gpstrek.resetState(); removeMenu();} else {E.showMenu(mainmenu);}}).catch(()=>{E.showMenu(mainmenu);});},
"Info rows" : {
value : WIDGETS.gpstrek.getState().numberOfSlices,
min:1,max:6,step:1,
onchange : v => { WIDGETS.gpstrek.getState().numberOfSlices = v; }
},
};
E.showMenu(mainmenu);
@ -1374,7 +1374,7 @@ const finishData = {
};
let getSliceHeight = function(number){
return Math.floor(Bangle.appRect.h/WIDGETS.gpstrek.getState().numberOfSlices);
return Math.floor(Bangle.appRect.h/SETTINGS.numberOfSlices);
};
let compassSlice = getCompassSlice();
@ -1455,7 +1455,6 @@ let updateRouting = function() {
lastSearch = Date.now();
autosearchCounter++;
}
let counter = 0;
while (hasNext(s.route) && distance(s.currentPos,get(s.route)) < SETTINGS.waypointChangeDist) {
next(s.route);
minimumDistance = Number.MAX_VALUE;
@ -1479,7 +1478,7 @@ let updateSlices = function(){
slices.push(healthSlice);
slices.push(systemSlice);
slices.push(system2Slice);
maxSlicePages = Math.ceil(slices.length/s.numberOfSlices);
maxSlicePages = Math.ceil(slices.length/SETTINGS.numberOfSlices);
};
let page_slices = 0;
@ -1515,9 +1514,9 @@ let drawSlices = function(){
if (force){
clear();
}
let firstSlice = page_slices*s.numberOfSlices;
let firstSlice = page_slices*SETTINGS.numberOfSlices;
let sliceHeight = getSliceHeight();
let slicesToDraw = slices.slice(firstSlice,firstSlice + s.numberOfSlices);
let slicesToDraw = slices.slice(firstSlice,firstSlice + SETTINGS.numberOfSlices);
for (let slice of slicesToDraw) {
g.reset();
if (!slice.refresh || slice.refresh() || force)

21
apps/gpstrek/default.json Normal file
View File

@ -0,0 +1,21 @@
{
"mapCompass": true,
"mapScale":0.5,
"mapRefresh":1000,
"mapChunkSize": 15,
"mapDirection": true,
"overviewScroll": 30,
"overviewScale": 0.02,
"refresh":500,
"refreshLocked":3000,
"cacheMinFreeMem":2000,
"cacheMaxEntries":0,
"minCourseChange": 5,
"minPosChange": 5,
"waypointChangeDist": 50,
"queueWaitingTime": 5,
"autosearch": true,
"maxDistForAutosearch": 300,
"autosearchLimit": 3,
"numberOfSlices": 3
}

View File

@ -1,7 +1,7 @@
{
"id": "gpstrek",
"name": "GPS Trekking",
"version": "0.10",
"version": "0.11",
"description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!",
"icon": "icon.png",
"screenshots": [{"url":"screenInit.png"},{"url":"screenMenu.png"},{"url":"screenMap.png"},{"url":"screenLost.png"},{"url":"screenOverview.png"},{"url":"screenOverviewScroll.png"},{"url":"screenSlices.png"},{"url":"screenSlices2.png"},{"url":"screenSlices3.png"}],
@ -12,8 +12,13 @@
"interface" : "interface.html",
"storage": [
{"name":"gpstrek.app.js","url":"app.js"},
{"name":"gpstrek.settings.js","url":"settings.js"},
{"name":"gpstrek.default.json","url":"default.json"},
{"name":"gpstrek.wid.js","url":"widget.js"},
{"name":"gpstrek.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"gpstrek.state.json"}]
"data": [
{"name":"gpstrek.state.json"},
{"name":"gpstrek.json"}
]
}

162
apps/gpstrek/settings.js Normal file
View File

@ -0,0 +1,162 @@
(function(back) {
const FILE="gpstrek.json";
let settings;
function writeSettings(key, value) {
var s = require('Storage').readJSON(FILE, true) || {};
s[key] = value;
require('Storage').writeJSON(FILE, s);
readSettings();
}
function readSettings(){
settings = Object.assign(
require('Storage').readJSON("gpstrek.default.json", true) || {},
require('Storage').readJSON(FILE, true) || {}
);
}
function showMapMenu(){
var menu = {
'': { 'title': 'Map', back: showMainMenu },
'Show compass on map': {
value: !!settings.mapCompass,
onchange: v => {
writeSettings("mapCompass",v);
},
},
'Initial map scale': {
value: settings.mapScale,
min: 0.01,max: 2, step:0.01,
onchange: v => {
writeSettings("mapScale",v);
},
},
'Rendered waypoints': {
value: settings.mapChunkSize,
min: 5,max: 60, step:5,
onchange: v => {
writeSettings("mapChunkSize",v);
}
},
'Overview scroll': {
value: settings.overviewScroll,
min: 10,max: 100, step:10,
format: v => v + "px",
onchange: v => {
writeSettings("overviewScroll",v);
}
},
'Initial overview scale': {
value: settings.overviewScale,
min: 0.005,max: 0.1, step:0.005,
onchange: v => {
writeSettings("overviewScale",v);
}
},
'Show direction': {
value: !!settings.mapDirection,
onchange: v => {
writeSettings("mapDirection",v);
}
}
};
E.showMenu(menu);
}
function showRoutingMenu(){
var menu = {
'': { 'title': 'Routing', back: showMainMenu },
'Auto search closest waypoint': {
value: !!settings.autosearch,
onchange: v => {
writeSettings("autosearch",v);
},
},
'Auto search limit': {
value: settings.autosearchLimit,
onchange: v => {
writeSettings("autosearchLimit",v);
},
},
'Waypoint change distance': {
value: settings.waypointChangeDist,
format: v => v + "m",
min: 5,max: 200, step:5,
onchange: v => {
writeSettings("waypointChangeDist",v);
},
}
};
E.showMenu(menu);
}
function showRefreshMenu(){
var menu = {
'': { 'title': 'Refresh', back: showMainMenu },
'Unlocked refresh': {
value: settings.refresh,
format: v => v + "ms",
min: 250,max: 5000, step:250,
onchange: v => {
writeSettings("refresh",v);
}
},
'Locked refresh': {
value: settings.refreshLocked,
min: 1000,max: 60000, step:1000,
format: v => v + "ms",
onchange: v => {
writeSettings("refreshLocked",v);
}
},
'Minimum refresh': {
value: settings.mapRefresh,
format: v => v + "ms",
min: 250,max: 5000, step:250,
onchange: v => {
writeSettings("mapRefresh",v);
}
},
'Minimum course change': {
value: settings.minCourseChange,
min: 0,max: 180, step:1,
format: v => v + "°",
onchange: v => {
writeSettings("minCourseChange",v);
}
},
'Minimum position change': {
value: settings.minPosChange,
min: 0,max: 50, step:1,
format: v => v + "px",
onchange: v => {
writeSettings("minPosChange",v);
}
}
};
E.showMenu(menu);
}
function showMainMenu(){
var mainmenu = {
'': { 'title': 'GPS Trekking', back: back },
'Map': showMapMenu,
'Routing': showRoutingMenu,
'Refresh': showRefreshMenu,
"Info rows" : {
value : settings.numberOfSlices,
min:1,max:6,step:1,
onchange : v => {
writeSettings("numberOfSlices",v);
}
},
};
E.showMenu(mainmenu);
}
readSettings();
showMainMenu();
})

View File

@ -45,7 +45,15 @@ function onPulse(e){
}
function onGPS(fix) {
if(fix.fix) state.currentPos = fix;
if(fix.fix) {
state.currentPos = fix;
if (Bangle.isCompassOn()){
Bangle.setCompassPower(0, "gpstrek");
state.compassSamples = new Array(SAMPLES).fill(0)
}
} else {
Bangle.setCompassPower(1, "gpstrek");
}
}
let radians = function(a) {

View File

@ -22,3 +22,7 @@
0.21: Update boot.min.js.
0.22: Fix timeout for heartrate sensor on 3 minute setting (#2435)
0.23: Fix HRM logic
0.24: Correct daily health summary for movement (some logic errors resulted in garbage data being written)
0.25: lib.read* methods now return correctly scaled movement
movement graph in app is now an average, not sum
fix 11pm slot for daily HRM

View File

@ -59,7 +59,7 @@ function hrmPerHour() {
E.showMessage(/*LANG*/"Loading...");
current_selection = "hrmPerHour";
var data = new Uint16Array(24);
var cnt = new Uint8Array(23);
var cnt = new Uint8Array(24);
require("health").readDay(new Date(), h=>{
data[h.hr]+=h.bpm;
if (h.bpm) cnt[h.hr]++;
@ -87,7 +87,12 @@ function movementPerHour() {
E.showMessage(/*LANG*/"Loading...");
current_selection = "movementPerHour";
var data = new Uint16Array(24);
require("health").readDay(new Date(), h=>data[h.hr]+=h.movement);
var cnt = new Uint8Array(24);
require("health").readDay(new Date(), h=>{
data[h.hr]+=h.movement
cnt[h.hr]++;
});
data.forEach((d,i)=>data[i] = d/cnt[i]);
setButton(menuMovement);
barChart(/*LANG*/"HOUR", data);
}
@ -96,7 +101,12 @@ function movementPerDay() {
E.showMessage(/*LANG*/"Loading...");
current_selection = "movementPerDay";
var data = new Uint16Array(31);
require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.movement);
var cnt = new Uint8Array(31);
require("health").readDailySummaries(new Date(), h=>{
data[h.hr]+=h.movement
cnt[h.hr]++;
});
data.forEach((d,i)=>data[i] = d/cnt[i]);
setButton(menuMovement);
barChart(/*LANG*/"DAY", data);
}

View File

@ -52,7 +52,7 @@ Bangle.on("health", health => {
return String.fromCharCode(
health.steps>>8,health.steps&255, // 16 bit steps
health.bpm, // 8 bit bpm
Math.min(health.movement / 8, 255)); // movement
Math.min(health.movement, 255)); // movement
}
var rec = getRecordIdx(d);
@ -68,6 +68,12 @@ Bangle.on("health", health => {
require("Storage").write(fn, "HEALTH1\0", 0, DB_FILE_LEN); // header
}
var recordPos = DB_HEADER_LEN+(rec*DB_RECORD_LEN);
// scale down reported movement value in order to fit it within a
// uint8 DB field
health = Object.assign({}, health);
health.movement /= 8;
require("Storage").write(fn, getRecordData(health), recordPos, DB_FILE_LEN);
if (rec%DB_RECORDS_PER_DAY != DB_RECORDS_PER_DAY-2) return;
// we're at the end of the day. Read in all of the data for the day and sum it up
@ -82,10 +88,10 @@ Bangle.on("health", health => {
var dt = f.substr(recordPos, DB_RECORD_LEN);
if (dt!="\xFF\xFF\xFF\xFF") {
health.steps += (dt.charCodeAt(0)<<8)+dt.charCodeAt(1);
health.movement += dt.charCodeAt(2);
health.movCnt++;
var bpm = dt.charCodeAt(2);
health.bpm += bpm;
health.movement += dt.charCodeAt(3);
health.movCnt++;
if (bpm) health.bpmCnt++;
}
recordPos -= DB_RECORD_LEN;

View File

@ -1,5 +1,5 @@
function l(){var a=require("Storage").readJSON("health.json",1)||{},d=Bangle.getHealthStatus("day").steps;a.stepGoalNotification&&0<a.stepGoal&&d>=a.stepGoal&&(d=(new Date(Date.now())).toISOString().split("T")[0],!a.stepGoalNotificationDate||a.stepGoalNotificationDate<d)&&(Bangle.buzz(200,.5),require("notify").show({title:a.stepGoal+" steps",body:"You reached your step goal!",icon:atob("DAyBABmD6BaBMAsA8BCBCBCBCA8AAA==")}),a.stepGoalNotificationDate=d,require("Storage").writeJSON("health.json",
a))}(function(){var a=0|(require("Storage").readJSON("health.json",1)||{}).hrm;if(1==a||2==a){var d=function(){Bangle.setHRMPower(1,"health");setTimeout(function(){return Bangle.setHRMPower(0,"health")},6E4*a);if(1==a){var b=function(){Bangle.setHRMPower(1,"health");setTimeout(function(){Bangle.setHRMPower(0,"health")},6E4)};setTimeout(b,2E5);setTimeout(b,4E5)}};Bangle.on("health",d);Bangle.on("HRM",function(b){90<b.confidence&&1>Math.abs(Bangle.getHealthStatus().bpm-b.bpm)&&Bangle.setHRMPower(0,
"health")});90<Bangle.getHealthStatus().bpmConfidence||d()}else Bangle.setHRMPower(!!a,"health")})();Bangle.on("health",function(a){function d(c){return String.fromCharCode(c.steps>>8,c.steps&255,c.bpm,Math.min(c.movement/8,255))}var b=new Date(Date.now()-59E4);a&&0<a.steps&&l();var f=function(c){return 145*(c.getDate()-1)+6*c.getHours()+(0|6*c.getMinutes()/60)}(b);b=function(c){return"health-"+c.getFullYear()+"-"+(c.getMonth()+1)+".raw"}(b);var g=require("Storage").read(b);if(g){var e=g.substr(8+
4*f,4);if("\u00ff\u00ff\u00ff\u00ff"!=e){print("HEALTH ERR: Already written!");return}}else require("Storage").write(b,"HEALTH1\x00",0,17988);var h=8+4*f;require("Storage").write(b,d(a),h,17988);if(143==f%145)if(f=h+4,"\u00ff\u00ff\u00ff\u00ff"!=g.substr(f,4))print("HEALTH ERR: Daily summary already written!");else{a={steps:0,bpm:0,movement:0,movCnt:0,bpmCnt:0};for(var k=0;144>k;k++)e=g.substr(h,4),"\u00ff\u00ff\u00ff\u00ff"!=e&&(a.steps+=(e.charCodeAt(0)<<8)+e.charCodeAt(1),a.movement+=e.charCodeAt(2),
a.movCnt++,e=e.charCodeAt(2),a.bpm+=e,e&&a.bpmCnt++),h-=4;a.bpmCnt&&(a.bpm/=a.bpmCnt);a.movCnt&&(a.movement/=a.movCnt);require("Storage").write(b,d(a),f,17988)}})
function m(){var a=require("Storage").readJSON("health.json",1)||{},d=Bangle.getHealthStatus("day").steps;a.stepGoalNotification&&0<a.stepGoal&&d>=a.stepGoal&&(d=(new Date(Date.now())).toISOString().split("T")[0],!a.stepGoalNotificationDate||a.stepGoalNotificationDate<d)&&(Bangle.buzz(200,.5),require("notify").show({title:a.stepGoal+" steps",body:"You reached your step goal!",icon:atob("DAyBABmD6BaBMAsA8BCBCBCBCA8AAA==")}),a.stepGoalNotificationDate=d,require("Storage").writeJSON("health.json",
a))}(function(){var a=0|(require("Storage").readJSON("health.json",1)||{}).hrm;if(1==a||2==a){function d(){Bangle.setHRMPower(1,"health");setTimeout(()=>Bangle.setHRMPower(0,"health"),6E4*a);if(1==a){function b(){Bangle.setHRMPower(1,"health");setTimeout(()=>{Bangle.setHRMPower(0,"health")},6E4)}setTimeout(b,2E5);setTimeout(b,4E5)}}Bangle.on("health",d);Bangle.on("HRM",b=>{90<b.confidence&&1>Math.abs(Bangle.getHealthStatus().bpm-b.bpm)&&Bangle.setHRMPower(0,"health")});90<Bangle.getHealthStatus().bpmConfidence||
d()}else Bangle.setHRMPower(!!a,"health")})();Bangle.on("health",a=>{function d(c){return String.fromCharCode(c.steps>>8,c.steps&255,c.bpm,Math.min(c.movement,255))}var b=new Date(Date.now()-59E4);a&&0<a.steps&&m();var f=function(c){return 145*(c.getDate()-1)+6*c.getHours()+(0|6*c.getMinutes()/60)}(b);b=function(c){return"health-"+c.getFullYear()+"-"+(c.getMonth()+1)+".raw"}(b);var g=require("Storage").read(b);if(g){var e=g.substr(8+4*f,4);if("\xff\xff\xff\xff"!=e){print("HEALTH ERR: Already written!");
return}}else require("Storage").write(b,"HEALTH1\x00",0,17988);var h=8+4*f;a=Object.assign({},a);a.movement/=8;require("Storage").write(b,d(a),h,17988);if(143==f%145)if(f=h+4,"\xff\xff\xff\xff"!=g.substr(f,4))print("HEALTH ERR: Daily summary already written!");else{a={steps:0,bpm:0,movement:0,movCnt:0,bpmCnt:0};for(var k=0;144>k;k++){e=g.substr(h,4);if("\xff\xff\xff\xff"!=e){a.steps+=(e.charCodeAt(0)<<8)+e.charCodeAt(1);var l=e.charCodeAt(2);a.bpm+=l;a.movement+=e.charCodeAt(3);a.movCnt++;
l&&a.bpmCnt++}h-=4}a.bpmCnt&&(a.bpm/=a.bpmCnt);a.movCnt&&(a.movement/=a.movCnt);require("Storage").write(b,d(a),f,17988)}})

View File

@ -29,7 +29,7 @@ exports.readAllRecords = function(d, cb) {
day:day+1, hr : hr, min:m*10,
steps : (h.charCodeAt(0)<<8) | h.charCodeAt(1),
bpm : h.charCodeAt(2),
movement : h.charCodeAt(3)
movement : h.charCodeAt(3)*8
});
}
idx += DB_RECORD_LEN;
@ -53,7 +53,7 @@ exports.readDailySummaries = function(d, cb) {
day:day+1,
steps : (h.charCodeAt(0)<<8) | h.charCodeAt(1),
bpm : h.charCodeAt(2),
movement : h.charCodeAt(3)
movement : h.charCodeAt(3)*8
});
}
idx += DB_RECORDS_PER_DAY*DB_RECORD_LEN;
@ -75,7 +75,7 @@ exports.readDay = function(d, cb) {
hr : hr, min:m*10,
steps : (h.charCodeAt(0)<<8) | h.charCodeAt(1),
bpm : h.charCodeAt(2),
movement : h.charCodeAt(3)
movement : h.charCodeAt(3)*8
});
}
idx += DB_RECORD_LEN;

View File

@ -1,3 +1,3 @@
function h(a){return"health-"+a.getFullYear()+"-"+(a.getMonth()+1)+".raw"}function k(a){return 145*(a.getDate()-1)+6*a.getHours()+(0|6*a.getMinutes()/60)}exports.readAllRecords=function(a,f){a=h(a);a=require("Storage").read(a);if(void 0!==a)for(var c=8,d=0;31>d;d++){for(var b=0;24>b;b++)for(var e=0;6>e;e++){var g=a.substr(c,4);"\u00ff\u00ff\u00ff\u00ff"!=g&&f({day:d+1,hr:b,min:10*e,steps:g.charCodeAt(0)<<8|g.charCodeAt(1),bpm:g.charCodeAt(2),movement:g.charCodeAt(3)});c+=
4}c+=4}};exports.readDailySummaries=function(a,f){k(a);a=h(a);a=require("Storage").read(a);if(void 0!==a)for(var c=584,d=0;31>d;d++){var b=a.substr(c,4);"\u00ff\u00ff\u00ff\u00ff"!=b&&f({day:d+1,steps:b.charCodeAt(0)<<8|b.charCodeAt(1),bpm:b.charCodeAt(2),movement:b.charCodeAt(3)});c+=580}};exports.readDay=function(a,f){k(a);var c=h(a);c=require("Storage").read(c);if(void 0!==c){a=8+580*(a.getDate()-1);for(var d=0;24>d;d++)for(var b=0;6>b;b++){var e=c.substr(a,4);"\u00ff\u00ff\u00ff\u00ff"!=e&&f({hr:d,
min:10*b,steps:e.charCodeAt(0)<<8|e.charCodeAt(1),bpm:e.charCodeAt(2),movement:e.charCodeAt(3)});a+=4}}}
function h(a){return"health-"+a.getFullYear()+"-"+(a.getMonth()+1)+".raw"}function k(a){return 145*(a.getDate()-1)+6*a.getHours()+(0|6*a.getMinutes()/60)}exports.readAllRecords=function(a,f){a=h(a);a=require("Storage").read(a);if(void 0!==a)for(var c=8,d=0;31>d;d++){for(var b=0;24>b;b++)for(var e=0;6>e;e++){var g=a.substr(c,4);"\xff\xff\xff\xff"!=g&&f({day:d+1,hr:b,min:10*e,steps:g.charCodeAt(0)<<8|g.charCodeAt(1),bpm:g.charCodeAt(2),movement:8*g.charCodeAt(3)});c+=
4}c+=4}};exports.readDailySummaries=function(a,f){k(a);a=h(a);a=require("Storage").read(a);if(void 0!==a)for(var c=584,d=0;31>d;d++){var b=a.substr(c,4);"\xff\xff\xff\xff"!=b&&f({day:d+1,steps:b.charCodeAt(0)<<8|b.charCodeAt(1),bpm:b.charCodeAt(2),movement:8*b.charCodeAt(3)});c+=580}};exports.readDay=function(a,f){k(a);var c=h(a);c=require("Storage").read(c);if(void 0!==c){a=8+580*(a.getDate()-1);for(var d=0;24>d;d++)for(var b=0;6>b;b++){var e=c.substr(a,4);"\xff\xff\xff\xff"!=e&&
f({hr:d,min:10*b,steps:e.charCodeAt(0)<<8|e.charCodeAt(1),bpm:e.charCodeAt(2),movement:8*e.charCodeAt(3)});a+=4}}}

View File

@ -2,7 +2,7 @@
"id": "health",
"name": "Health Tracking",
"shortName": "Health",
"version": "0.23",
"version": "0.25",
"description": "Logs health data and provides an app to view it",
"icon": "app.png",
"tags": "tool,system,health",

View File

@ -21,4 +21,6 @@
0.15: Ensure that we hide widgets if in fullscreen mode
(So that widgets are still hidden if launcher is fast-loaded)
0.16: Use firmware provided E.showScroller method
0.17: fix fullscreen with oneClickExit
0.17: fix fullscreen with oneClickExit
0.18: Better performance
0.19: Remove 'jit' keyword as 'for(..of..)' is not supported (fix #2937)

View File

@ -9,7 +9,6 @@
timeOut:"Off"
}, s.readJSON("iconlaunch.json", true) || {});
if (!settings.fullscreen) {
Bangle.loadWidgets();
Bangle.drawWidgets();
@ -19,9 +18,9 @@
let launchCache = s.readJSON("iconlaunch.cache.json", true)||{};
let launchHash = s.hash(/\.info/);
if (launchCache.hash!=launchHash) {
launchCache = {
hash : launchHash,
apps : s.list(/\.info$/)
launchCache = {
hash : launchHash,
apps : s.list(/\.info$/)
.map(app=>{let a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};})
.filter(app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || !app.type))
.sort((a,b)=>{
@ -34,42 +33,65 @@
s.writeJSON("iconlaunch.cache.json", launchCache);
}
// cache items
const ICON_MISSING = s.read("iconlaunch.na.img");
let count = 0;
let selectedItem = -1;
const R = Bangle.appRect;
const iconSize = 48;
const appsN = Math.floor(R.w / iconSize);
const whitespace = (R.w - appsN * iconSize) / (appsN + 1);
const whitespace = Math.floor((R.w - appsN * iconSize) / (appsN + 1));
const iconYoffset = Math.floor(whitespace/4)-1;
const itemSize = iconSize + whitespace;
launchCache.items = {};
for (let c of launchCache.apps){
let i = Math.floor(count/appsN);
if (!launchCache.items[i])
launchCache.items[i] = {};
launchCache.items[i][(count%3)] = c;
count++;
}
let texted;
let drawItem = function(itemI, r) {
g.clearRect(r.x, r.y, r.x + r.w - 1, r.y + r.h - 1);
let x = 0;
for (let i = itemI * appsN; i < appsN * (itemI + 1); i++) {
if (!launchCache.apps[i]) break;
x += whitespace;
if (!launchCache.apps[i].icon) {
g.setFontAlign(0, 0, 0).setFont("12x20:2").drawString("?", x + r.x + iconSize / 2, r.y + iconSize / 2);
} else {
if (!launchCache.apps[i].icondata) launchCache.apps[i].icondata = s.read(launchCache.apps[i].icon);
g.drawImage(launchCache.apps[i].icondata, x + r.x, r.y);
}
if (selectedItem == i) {
g.drawRect(
x + r.x - 1,
r.y - 1,
x + r.x + iconSize + 1,
r.y + iconSize + 1
);
}
x += iconSize;
let x = whitespace;
let i = itemI * appsN - 1;
let selectedApp;
let c;
let selectedRect;
let item = launchCache.items[itemI];
if (texted == itemI){
g.clearRect(r.x, r.y, r.x + r.w - 1, r.y + r.h - 1);
texted = undefined;
}
for (c of item) {
i++;
let id = c.icondata || (c.iconData = (c.icon ? s.read(c.icon) : ICON_MISSING));
g.drawImage(id,x + r.x - 1, r.y + iconYoffset - 1, x + r.x + iconSize, r.y + iconYoffset + iconSize);
if (selectedItem == i) {
selectedApp = c;
selectedRect = [
x + r.x - 1,
r.y + iconYoffset - 1,
x + r.x + iconSize,
r.y + iconYoffset + iconSize
];
}
x += iconSize + whitespace;
}
if (selectedRect) {
g.drawRect.apply(null, selectedRect);
drawText(itemI, r.y, selectedApp);
texted=itemI;
}
drawText(itemI, r.y);
};
let drawText = function(i, appY) {
const selectedApp = launchCache.apps[selectedItem];
let drawText = function(i, appY, selectedApp) {
"jit";
const idy = (selectedItem - (selectedItem % 3)) / 3;
if (!selectedApp || i != idy) return;
if (i != idy) return;
appY = appY + itemSize/2;
g.setFontAlign(0, 0, 0);
g.setFont("12x20");
@ -122,21 +144,21 @@
},
btn:Bangle.showClock
};
//work both the fullscreen and the oneClickExit
if( settings.fullscreen && settings.oneClickExit)
{
idWatch=setWatch(function(e) {
idWatch=setWatch(function(e) {
Bangle.showClock();
}, BTN, {repeat:false, edge:'rising' });
}
else if( settings.oneClickExit )
else if( settings.oneClickExit )
{
options.back=Bangle.showClock;
}
let scroller = E.showScroller(options);
@ -151,7 +173,7 @@
};
let swipeHandler = (h,_) => { if(settings.swipeExit && h==1) { Bangle.showClock(); } };
Bangle.on("swipe", swipeHandler)
Bangle.on("drag", updateTimeout);
Bangle.on("touch", updateTimeout);

View File

@ -2,7 +2,7 @@
"id": "iconlaunch",
"name": "Icon Launcher",
"shortName" : "Icon launcher",
"version": "0.17",
"version": "0.19",
"icon": "app.png",
"description": "A launcher inspired by smartphones, with an icon-only scrollable menu.",
"tags": "tool,system,launcher",
@ -10,7 +10,8 @@
"supports": ["BANGLEJS2"],
"storage": [
{ "name": "iconlaunch.app.js", "url": "app.js" },
{ "name": "iconlaunch.settings.js", "url": "settings.js" }
{ "name": "iconlaunch.settings.js", "url": "settings.js" },
{ "name": "iconlaunch.na.img", "url": "na.img" }
],
"data": [{"name":"iconlaunch.json"},{"name":"iconlaunch.cache.json"}],
"screenshots": [{ "url": "screenshot1.png" }, { "url": "screenshot2.png" }],

BIN
apps/iconlaunch/na.img Normal file

Binary file not shown.

View File

@ -16,8 +16,7 @@
}
const timeOutChoices = [/*LANG*/"Off", "10s", "15s", "20s", "30s"];
const appMenu = {
"": { "title": /*LANG*/"Launcher" },
/*LANG*/"< Back": back,
"": { "title": /*LANG*/"Launcher", back: back },
/*LANG*/"Show Clocks": {
value: settings.showClocks == true,
onchange: (m) => {

View File

@ -4,3 +4,6 @@
0.04: Allow moving the cursor
0.05: Switch swipe directions for Caps Lock and moving cursor.
0.06: Add ability to auto-lowercase after a capital letter insertion.
0.07: Add compatability with `backswipe` app by using `Bangle.prependListener()` and `E.stopEventPropagation`- requires fw 2v19 or cutting
edge versions of 2v18. Falls back on `Bangle.on()` for backwards
compatability.

View File

@ -154,6 +154,7 @@ exports.input = function(options) {
displayText(false);
}
}
E.stopEventPropagation&&E.stopEventPropagation();
}
function onHelp(resolve,reject) {
@ -161,7 +162,7 @@ exports.input = function(options) {
E.showPrompt(
helpMessage, {title: "Help", buttons : {"Ok":true}}
).then(function(v) {
Bangle.on('swipe', onSwipe);
if (Bangle.prependListener) {Bangle.prependListener('swipe', onSwipe);} else {Bangle.on('swipe', onSwipe);}
generateLayout(resolve,reject);
layout.render();
});
@ -208,7 +209,7 @@ exports.input = function(options) {
} else {
generateLayout(resolve,reject);
displayText(false);
Bangle.on('swipe', onSwipe);
if (Bangle.prependListener) {Bangle.prependListener('swipe', onSwipe);} else {Bangle.on('swipe', onSwipe);}
layout.render();
}
});

View File

@ -1,6 +1,6 @@
{ "id": "kbmulti",
"name": "Multitap keyboard",
"version":"0.06",
"version":"0.07",
"description": "A library for text input via multitap/T9 style keypad",
"icon": "app.png",
"type":"textinput",

View File

@ -6,3 +6,5 @@
0.06: Support input of numbers and uppercase characters.
0.07: Support input of symbols.
0.08: Redone patterns a,e,m,w,z.
0.09: Catch and discard swipe events on fw2v19 and up (as well as some cutting
edge 2v18 ones), allowing compatability with the Back Swipe app.

View File

@ -253,28 +253,38 @@ exports.input = function(options) {
};
Bangle.drawWidgets();
let dragHandlerKB = e=>{
"ram";
if (isInside(R, e)) {
if (lastDrag) g.reset().setColor("#f00").drawLine(lastDrag.x,lastDrag.y,e.x,e.y);
lastDrag = e.b ? e : 0;
}
}
let touchHandlerKB = (n,e) => {
if (WIDGETS.kbswipe && isInside({x: WIDGETS.kbswipe.x, y: WIDGETS.kbswipe.y, w: WIDGETS.kbswipe.width, h: 24}, e)) {
// touch inside widget
cycleInput();
} else if (isInside(R, e)) {
// touch inside app area
show();
}
}
let catchSwipe = ()=>{
E.stopEventPropagation&&E.stopEventPropagation();
};
return new Promise((resolve,reject) => {
Bangle.setUI({mode:"custom", drag:e=>{
"ram";
if (isInside(R, e)) {
if (lastDrag) g.reset().setColor("#f00").drawLine(lastDrag.x,lastDrag.y,e.x,e.y);
lastDrag = e.b ? e : 0;
}
},touch:(n,e) => {
if (WIDGETS.kbswipe && isInside({x: WIDGETS.kbswipe.x, y: WIDGETS.kbswipe.y, w: WIDGETS.kbswipe.width, h: 24}, e)) {
// touch inside widget
cycleInput();
} else if (isInside(R, e)) {
// touch inside app area
show();
}
}, back:()=>{
Bangle.setUI({mode:"custom", drag:dragHandlerKB, touch:touchHandlerKB, back:()=>{
delete WIDGETS.kbswipe;
Bangle.removeListener("stroke", strokeHandler);
Bangle.prependListener&&Bangle.removeListener('swipe', catchSwipe); // Remove swipe lister if it was added with `Bangle.prependListener()` (fw2v19 and up).
if (flashInterval) clearInterval(flashInterval);
Bangle.setUI();
g.clearRect(Bangle.appRect);
resolve(text);
}});
Bangle.prependListener&&Bangle.prependListener('swipe', catchSwipe); // Intercept swipes on fw2v19 and later. Should not break on older firmwares.
});
};

View File

@ -1,6 +1,6 @@
{ "id": "kbswipe",
"name": "Swipe keyboard",
"version":"0.08",
"version":"0.09",
"description": "A library for text input via PalmOS style swipe gestures (beta!)",
"icon": "app.png",
"type":"textinput",

View File

@ -1,3 +1,5 @@
0.01: New App!
0.02: Introduced settings to customize the layout and functionality of the keyboard.
0.03: Convert Yes/No On/Off in settings to checkboxes
0.04: Catch and discard swipe events on fw2v19 and up (as well as some cutting
edge 2v18 ones), allowing compatability with the Back Swipe app.

View File

@ -161,8 +161,11 @@ function draw() {
},back:()=>{
clearInterval(flashInterval);
Bangle.setUI();
Bangle.prependListener&&Bangle.removeListener('swipe', catchSwipe); // Remove swipe lister if it was added with `Bangle.prependListener()` (fw2v19 and up).
g.clearRect(Bangle.appRect);
resolve(text);
}});
let catchSwipe = ()=>{E.stopEventPropagation&&E.stopEventPropagation();};
Bangle.prependListener&&Bangle.prependListener('swipe', catchSwipe); // Intercept swipes on fw2v19 and later. Should not break on older firmwares.
});
};

View File

@ -1,6 +1,6 @@
{ "id": "kbtouch",
"name": "Touch keyboard",
"version":"0.03",
"version":"0.04",
"description": "A library for text input via onscreen keyboard",
"icon": "app.png",
"type":"textinput",

View File

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

View File

@ -0,0 +1,7 @@
# Kinetic scrolling
This patches the default scroller implementation to use kinetic scrolling. It is based on the original implementation.
## Creator
[halemmerich](https://github.com/halemmerich)

BIN
apps/kineticscroll/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

181
apps/kineticscroll/boot.js Normal file
View File

@ -0,0 +1,181 @@
(function() {
E.showScroller = function(options) {
/* options = {
h = height
c = # of items
scroll = initial scroll position
scrollMin = minimum scroll amount (can be negative)
draw = function(idx, rect)
remove = function()
select = function(idx, touch)
}
returns {
scroll: int // current scroll amount
draw: function() // draw all
drawItem : function(idx) // draw specific item
isActive : function() // is this scroller still active?
}
*/
if (!options) return Bangle.setUI(); // remove existing handlers
const MAX_VELOCITY=100;
let scheduledDraw;
let velocity = 0;
let accDy = 0;
let scheduledBrake = setInterval(()=>{velocity*=0.9;}, 50);
let lastDragStart = 0;
let R = Bangle.appRect;
let menuScrollMin = 0|options.scrollMin;
let menuScrollMax = options.h*options.c - R.h;
if (menuScrollMax<menuScrollMin) menuScrollMax=menuScrollMin;
const touchHandler = (_,e)=>{
if (e.y<R.y-4) return;
velocity = 0;
accDy = 0;
let i = YtoIdx(e.y);
if ((menuScrollMin<0 || i>=0) && i<options.c){
let yAbs = (e.y + rScroll - R.y);
let yInElement = yAbs - i*options.h;
print("selected");
options.select(i, {x:e.x, y:yInElement});
}
};
const uiDraw = () => {
g.reset().clearRect(R).setClipRect(R.x,R.y,R.x2,R.y2);
var a = YtoIdx(R.y);
var b = Math.min(YtoIdx(R.y2),options.c-1);
for (var i=a;i<=b;i++)
options.draw(i, {x:R.x,y:idxToY(i),w:R.w,h:options.h});
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
}
const draw = () => {
let dy = velocity;
if (s.scroll - dy > menuScrollMax){
dy = s.scroll - menuScrollMax;
velocity = 0;
}
if (s.scroll - dy < menuScrollMin){
dy = s.scroll - menuScrollMin;
velocity = 0;
}
s.scroll -= dy;
let oldScroll = rScroll;
rScroll = s.scroll &~1;
let d = oldScroll-rScroll;
if (Math.abs(velocity) > 0.01)
scheduledDraw = setTimeout(draw,0);
else
scheduledDraw = undefined;
if (!d) {
return;
}
g.reset().setClipRect(R.x,R.y,R.x2,R.y2).scroll(0,d);
if (d < 0) {
let y = Math.max(R.y2-(1-d), R.y);
g.setClipRect(R.x,y,R.x2,R.y2);
let i = YtoIdx(y);
for (y = idxToY(i);y < R.y2;y+=options.h) {
options.draw(i, {x:R.x,y:y,w:R.w,h:options.h});
i++;
}
} else { // d>0
let y = Math.min(R.y+d, R.y2);
g.setClipRect(R.x,R.y,R.x2,y);
let i = YtoIdx(y);
y = idxToY(i);
for (y = idxToY(i);y > R.y-options.h;y-=options.h) {
options.draw(i, {x:R.x,y:y,w:R.w,h:options.h});
i--;
}
}
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
};
const dragHandler = e=>{
if ((velocity <0 && e.dy>0) || (velocity > 0 && e.dy<0)){
velocity *= -1;
accDy = 5 * velocity;
}
//velocity += e.dy * (Date.now() - lastDrag);
if (e.b > 0){
if (!lastDragStart){
lastDragStart = Date.now();
velocity = 0;
accDy = 0;
}
accDy += e.dy;
}
velocity = accDy / (Date.now() - lastDragStart) * MAX_VELOCITY;
if (lastDragStart && e.b == 0){
accDy = 0;
lastDragStart = 0;
}
velocity = E.clip(velocity,-MAX_VELOCITY,MAX_VELOCITY);
lastDrag=Date.now();
if (!scheduledDraw){
scheduledDraw = setTimeout(draw,0);
}
};
let uiOpts = {
mode : "custom",
back : options.back,
drag : dragHandler,
touch : touchHandler,
redraw : uiDraw
}
if (options.remove) uiOpts.remove = () => {
if (scheduledDraw)
clearTimeout(scheduledDraw);
clearInterval(scheduledBrake);
options.remove();
}
Bangle.setUI(uiOpts);
function idxToY(i) {
return i*options.h + R.y - rScroll;
}
function YtoIdx(y) {
return Math.floor((y + rScroll - R.y)/options.h);
}
let s = {
scroll : E.clip(0|options.scroll,menuScrollMin,menuScrollMax),
draw : () => {
g.reset().clearRect(R).setClipRect(R.x,R.y,R.x2,R.y2);
let a = YtoIdx(R.y);
let b = Math.min(YtoIdx(R.y2),options.c-1);
for (let i=a;i<=b;i++)
options.draw(i, {x:R.x,y:idxToY(i),w:R.w,h:options.h});
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
}, drawItem : i => {
let y = idxToY(i);
g.reset().setClipRect(R.x,Math.max(y,R.y),R.x2,Math.min(y+options.h,R.y2));
options.draw(i, {x:R.x,y:y,w:R.w,h:options.h});
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
}, isActive : () => Bangle.uiRedraw == uiDraw
};
let rScroll = s.scroll&~1; // rendered menu scroll (we only shift by 2 because of dither)
s.draw(); // draw the full scroller
g.flip(); // force an update now to make this snappier
return s;
};
})();

4
apps/kineticscroll/boot.min.js vendored Normal file
View File

@ -0,0 +1,4 @@
(function(){E.showScroller=function(c){function k(a){return a*c.h+b.y-l}function h(a){return Math.floor((a+l-b.y)/c.h)}if(!c)return Bangle.setUI();let p,e=0,m=0,w=setInterval(()=>{e*=.9},50),q=0,b=Bangle.appRect,n=0|c.scrollMin,r=c.h*c.c-b.h;r<n&&(r=n);const t=()=>{g.reset().clearRect(b).setClipRect(b.x,b.y,b.x2,b.y2);for(var a=h(b.y),d=Math.min(h(b.y2),c.c-1);a<=d;a++)c.draw(a,{x:b.x,y:k(a),w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},u=()=>{var a=e;f.scroll-a>r&&
(a=f.scroll-r,e=0);f.scroll-a<n&&(a=f.scroll-n,e=0);f.scroll-=a;a=l;l=f.scroll&-2;a-=l;p=.01<Math.abs(e)?setTimeout(u,0):void 0;if(a){g.reset().setClipRect(b.x,b.y,b.x2,b.y2).scroll(0,a);if(0>a){a=Math.max(b.y2-(1-a),b.y);g.setClipRect(b.x,a,b.x2,b.y2);var d=h(a);for(a=k(d);a<b.y2;a+=c.h)c.draw(d,{x:b.x,y:a,w:b.w,h:c.h}),d++}else for(a=Math.min(b.y+a,b.y2),g.setClipRect(b.x,b.y,b.x2,a),d=h(a),k(d),a=k(d);a>b.y-c.h;a-=c.h)c.draw(d,{x:b.x,y:a,w:b.w,h:c.h}),d--;g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-
1)}};let v={mode:"custom",back:c.back,drag:a=>{if(0>e&&0<a.dy||0<e&&0>a.dy)e*=-1,m=5*e;0<a.b&&(q||(q=Date.now(),m=e=0),m+=a.dy);e=m/(Date.now()-q)*100;q&&0==a.b&&(q=m=0);e=E.clip(e,-100,100);lastDrag=Date.now();p||(p=setTimeout(u,0))},touch:(a,d)=>{if(!(d.y<b.y-4)&&(m=e=0,a=h(d.y),(0>n||0<=a)&&a<c.c)){let x=d.y+l-b.y-a*c.h;print("selected");c.select(a,{x:d.x,y:x})}},redraw:t};c.remove&&(v.remove=()=>{p&&clearTimeout(p);clearInterval(w);c.remove()});Bangle.setUI(v);let f={scroll:E.clip(0|c.scroll,
n,r),draw:()=>{g.reset().clearRect(b).setClipRect(b.x,b.y,b.x2,b.y2);var a=h(b.y);let d=Math.min(h(b.y2),c.c-1);for(;a<=d;a++)c.draw(a,{x:b.x,y:k(a),w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},drawItem:a=>{let d=k(a);g.reset().setClipRect(b.x,Math.max(d,b.y),b.x2,Math.min(d+c.h,b.y2));c.draw(a,{x:b.x,y:d,w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},isActive:()=>Bangle.uiRedraw==t},l=f.scroll&-2;f.draw();g.flip();return f}})()

View File

@ -0,0 +1,14 @@
{ "id": "kineticscroll",
"name": "Kinetic Scroll",
"shortName":"Kinetic Scroll",
"version":"0.01",
"description": "Replacement for the system scroller with kinetic scrolling.",
"icon": "app.png",
"type": "bootloader",
"tags": "system",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"kineticscroll.boot.js","url":"boot.min.js"}
]
}

1
apps/lunaclock/ChangeLog Normal file
View File

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

9
apps/lunaclock/README.md Normal file
View File

@ -0,0 +1,9 @@
# Luna Clock
![](screenshot.png)
Simple clock face inspired by the moon.
## Creator
NovaDawn999

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkEIf4A/AH4AJiIABiAVRiUz/4AB+cyDJ8TCoX/mc/AQMgCxkjCwgAB+YCBDBcDCwYXCn4CC+ZKJgQWEFYQWCA4MyC5EvC5HzA4U/JI4uEIAQWBD4k/+QuKLYZDCDwQFCGAsBIgoBBFIIGBPIf/GAqMFDIQvDHARjBSQp1EFAIpCL4YGEmBGEE4akEIYYIDPIhGBiYwDmTSEgUiAwMiAAMzJAUf+ZgD+cCCwX/iMjCQMxiczmUDSIUvmIXDmIXBmchmcTn8jmcgJAUgC4U/iLXC+ciC4UgCgMyOwMRC4UwiJ2BLAIXDAgJHBCgReCEAQXBiUACgURWoQEBdAYvBCoIXL+TDBiAWCegUgicBC4cTC4IPBkE/OoM/AgK6EBIMQC4ixBH4ISCmUSkEvBoJ3BJAR9CBIMvC4KHCl4YCiESiYXBSAUBWwIRBNgIXEkZbCmLIBRYchgUykMjAILwBgLeBkAhCLAIEBUwK3BiEikUykRJCiEAiQDBAQJYDAgRABiQVBCwcyd4MCiIHCAAMhT4YACCwIXCkUhC4MBiQgDCAQFCkYVCAAhGBC4MBEIQZEFggWHAARrCkQVELYMhMAICBiJFCC4gMBJYI0CCoInCiAlBCooAEDIIUBHwsBFwIXKDAITBQAIECBAIWMJYSBDGAYXNIAaFFACAtELZpUBHoIBBDAIZBBYJ4CEA0TawMyaQQXBmUwAQMgiYKBmIXFgTjEkMgA4QXBcgQbBNCoAEA=="))

72
apps/lunaclock/app.js Normal file

File diff suppressed because one or more lines are too long

BIN
apps/lunaclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,17 @@
{
"id": "lunaclock",
"name": "Luna Clock",
"version": "0.01",
"description": "Simple clock face inspired by the moon.",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"lunaclock.app.js","url":"app.js"},
{"name":"lunaclock.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -2,3 +2,6 @@
0.02: Fix touch/drag/swipe handlers not being restored correctly if a message is removed
0.03: Scroll six lines per swipe, leaving the previous top/bottom row visible.
0.04: Use the event mechanism for getting messages
0.05: Fix the overlay keeping the LCD on
0.06: Better low memory handling
Fix first message beeing displayed again on unlock

View File

@ -1,3 +1,5 @@
const MIN_FREE_MEM = 1000;
const LOW_MEM = 2000;
const ovrx = 10;
const ovry = 10;
const ovrw = g.getWidth()-2*ovrx;
@ -28,6 +30,7 @@ let callInProgress = false;
let show = function(ovr){
let img = ovr;
LOG("show", img.getBPP());
if (ovr.getBPP() == 1) {
img = ovr.asImage();
img.palette = new Uint16Array([_g.theme.fg,_g.theme.bg]);
@ -162,7 +165,9 @@ let showMessage = function(ovr, msg) {
drawMessage(ovr, msg);
};
let drawBorder = function(ovr) {
let drawBorder = function(img) {
LOG("drawBorder", isQuiet());
if (img) ovr=img;
if (Bangle.isLocked())
ovr.setColor(ovr.theme.fgH);
else
@ -170,7 +175,6 @@ let drawBorder = function(ovr) {
ovr.drawRect(0,0,ovr.getWidth()-1,ovr.getHeight()-1);
ovr.drawRect(1,1,ovr.getWidth()-2,ovr.getHeight()-2);
show(ovr);
if (!isQuiet()) Bangle.setLCDPower(1);
};
let showCall = function(ovr, msg) {
@ -232,13 +236,6 @@ let next = function(ovr) {
showMessage(ovr, eventQueue[0]);
};
let showMapMessage = function(ovr, msg) {
ovr.clearRect(2,2,ovr.getWidth()-3,ovr.getHeight()-3);
drawMessage(ovr, {
body: "Not implemented!"
});
};
let callBuzzTimer = null;
let stopCallBuzz = function() {
if (callBuzzTimer) {
@ -407,7 +404,7 @@ let main = function(ovr, event) {
if (!lockListener) {
lockListener = function (){
drawBorder(ovr);
drawBorder();
};
Bangle.on('lock', lockListener);
}
@ -432,15 +429,22 @@ let main = function(ovr, event) {
let ovr;
exports.message = function(type, event) {
LOG("Got message", type, event);
// only handle some event types
if(!(type=="text" || type == "call")) return;
if(type=="text" && event.id == "nav") return;
if(event.handled) return;
bpp = 4;
if (process.memory().free < 2000) bpp = 1;
if (process.memory().free < LOW_MEM)
bpp = 1;
if (!ovr) {
while (process.memory().free < MIN_FREE_MEM && eventQueue.length > 0){
let dropped = eventQueue.pop();
print("Dropped message because of memory constraints", dropped);
}
if (!ovr || ovr.getBPP() != bpp) {
ovr = Graphics.createArrayBuffer(ovrw, ovrh, bpp, {
msb: true
});
@ -456,6 +460,7 @@ exports.message = function(type, event) {
ovr.theme = { fg:0, bg:1, fg2:1, bg2:0, fgH:1, bgH:0 };
main(ovr, event);
if (!isQuiet()) Bangle.setLCDPower(1);
event.handled = true;
g = _g;
};

View File

@ -1,7 +1,7 @@
{
"id": "messagesoverlay",
"name": "Messages Overlay",
"version": "0.04",
"version": "0.06",
"description": "An overlay based implementation of a messages UI (display notifications from iOS and Gadgetbridge/Android)",
"icon": "app.png",
"type": "bootloader",

View File

@ -1,3 +1,4 @@
0.01: New App!
0.02: Redraw only when seconds change
0.03: Fix typo in redraw check
0.04: Register as clock and implement fast loading

View File

@ -1,29 +1,30 @@
// Code based on the original Mixed Clock
{
/* jshint esversion: 6 */
var locale = require("locale");
const locale = require("locale");
const Radius = { "center": 7, "hour": 60, "min": 80, "dots": 88 };
const Center = { "x": 120, "y": 96 };
const Widths = { hour: 2, minute: 2 };
var buf = Graphics.createArrayBuffer(240,192,1,{msb:true});
var lastDate = new Date();
const buf = Graphics.createArrayBuffer(240,192,1,{msb:true});
let timeoutId;
function rotatePoint(x, y, d) {
rad = -1 * d / 180 * Math.PI;
var sin = Math.sin(rad);
var cos = Math.cos(rad);
xn = ((Center.x + x * cos - y * sin) + 0.5) | 0;
yn = ((Center.y + x * sin - y * cos) + 0.5) | 0;
p = [xn, yn];
return p;
}
const rotatePoint = function(x, y, d, center, res) {
"jit";
const rad = -1 * d / 180 * Math.PI;
const sin = Math.sin(rad);
const cos = Math.cos(rad);
res[0] = ((center.x + x * cos - y * sin) + 0.5) | 0;
res[1] = ((center.y + x * sin - y * cos) + 0.5) | 0;
};
// from https://github.com/espruino/Espruino/issues/1702
function setLineWidth(x1, y1, x2, y2, lw) {
var dx = x2 - x1;
var dy = y2 - y1;
var d = Math.sqrt(dx * dx + dy * dy);
const setLineWidth = function(x1, y1, x2, y2, lw) {
"ram";
let dx = x2 - x1;
let dy = y2 - y1;
let d = Math.sqrt(dx * dx + dy * dy);
dx = dx * lw / d;
dy = dy * lw / d;
@ -44,71 +45,84 @@ function setLineWidth(x1, y1, x2, y2, lw) {
x2 - dy, y2 + dx,
x1 - dy, y1 + dx
];
}
};
function drawMixedClock(force) {
var date = new Date();
if ((force || Bangle.isLCDOn()) && buf.buffer && date.getSeconds() !== lastDate.getSeconds()) {
lastDate = date;
var dateArray = date.toString().split(" ");
var isEn = locale.name.startsWith("en");
var point = [];
var minute = date.getMinutes();
var hour = date.getHours();
var radius;
g.reset();
buf.clear();
// draw date
buf.setFont("6x8", 2);
buf.setFontAlign(-1, 0);
buf.drawString(locale.dow(date,true) + ' ', 4, 16, true);
buf.drawString(isEn?(' ' + dateArray[2]):locale.month(date,true), 4, 176, true);
buf.setFontAlign(1, 0);
buf.drawString(isEn?locale.month(date,true):(' ' + dateArray[2]), 237, 16, true);
buf.drawString(dateArray[3], 237, 176, true);
const drawMixedClock = function() {
const date = new Date();
const dateArray = date.toString().split(" ");
const isEn = locale.name.startsWith("en");
let point = [0, 0];
const minute = date.getMinutes();
const hour = date.getHours();
let radius;
// draw hour and minute dots
for (i = 0; i < 60; i++) {
radius = (i % 5) ? 2 : 4;
point = rotatePoint(0, Radius.dots, i * 6);
buf.fillCircle(point[0], point[1], radius);
}
g.reset();
buf.clear();
// draw digital time
buf.setFont("6x8", 3);
buf.setFontAlign(0, 0);
buf.drawString(dateArray[4], 120, 120, true);
// draw date
buf.setFont("6x8", 2);
buf.setFontAlign(-1, 0);
buf.drawString(locale.dow(date,true) + ' ', 4, 16, true);
buf.drawString(isEn?(' ' + dateArray[2]):locale.month(date,true), 4, 176, true);
buf.setFontAlign(1, 0);
buf.drawString(isEn?locale.month(date,true):(' ' + dateArray[2]), 237, 16, true);
buf.drawString(dateArray[3], 237, 176, true);
// draw new minute hand
point = rotatePoint(0, Radius.min, minute * 6);
buf.drawLine(Center.x, Center.y, point[0], point[1]);
buf.fillPoly(setLineWidth(Center.x, Center.y, point[0], point[1], Widths.minute));
// draw new hour hand
point = rotatePoint(0, Radius.hour, hour % 12 * 30 + date.getMinutes() / 2 | 0);
buf.fillPoly(setLineWidth(Center.x, Center.y, point[0], point[1], Widths.hour));
// draw center
buf.fillCircle(Center.x, Center.y, Radius.center);
g.drawImage({width:buf.getWidth(),height:buf.getHeight(),bpp:1,buffer:buf.buffer},0,24);
// draw hour and minute dots
for (i = 0; i < 60; i++) {
radius = (i % 5) ? 2 : 4;
rotatePoint(0, Radius.dots, i * 6, Center, point);
buf.fillCircle(point[0], point[1], radius);
}
}
Bangle.on('lcdPower', function(on) {
if (on)
drawMixedClock(true);
Bangle.drawWidgets();
});
// draw digital time
buf.setFont("6x8", 3);
buf.setFontAlign(0, 0);
buf.drawString(dateArray[4], 120, 120, true);
setInterval(() => drawMixedClock(true), 30000); // force an update every 30s even screen is off
// draw new minute hand
rotatePoint(0, Radius.min, minute * 6, Center, point);
buf.drawLine(Center.x, Center.y, point[0], point[1]);
buf.fillPoly(setLineWidth(Center.x, Center.y, point[0], point[1], Widths.minute));
// draw new hour hand
rotatePoint(0, Radius.hour, hour % 12 * 30 + date.getMinutes() / 2 | 0, Center, point);
buf.fillPoly(setLineWidth(Center.x, Center.y, point[0], point[1], Widths.hour));
// draw center
buf.fillCircle(Center.x, Center.y, Radius.center);
g.drawImage({width:buf.getWidth(),height:buf.getHeight(),bpp:1,buffer:buf.buffer},0,24);
if (timeoutId !== undefined) {
clearTimeout(timeoutId);
}
const period = (Bangle.isLCDOn() ? 1000 : 60000); // Update every second if display is on else every minute
let timeout = period - (Date.now() % period);
timeoutId = setTimeout(()=>{
timeoutId = undefined;
drawMixedClock();
}, timeout);
};
const onLCDPower = function(on) {
if (on) {
drawMixedClock();
Bangle.drawWidgets();
}
};
Bangle.on('lcdPower', onLCDPower);
Bangle.setUI({mode:"clock", remove:function() {
if (timeoutId !== undefined) {
delete buf.buffer;
clearTimeout(timeoutId);
timeoutId = undefined;
Bangle.removeListener('lcdPower',onLCDPower);
}
}});
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
drawMixedClock(); // immediately draw
setInterval(drawMixedClock, 500); // update twice a second
// Show launcher when middle button pressed after freeing memory first
setWatch(() => {delete buf.buffer; Bangle.showLauncher()}, BTN2, {repeat:false,edge:"falling"});
}

View File

@ -1,7 +1,7 @@
{
"id": "miclock2",
"name": "Mixed Clock 2",
"version": "0.03",
"version": "0.04",
"description": "White color variant of the Mixed Clock with thicker clock hands for better readability in the bright sunlight, extra space under the clock for widgets and seconds in the digital clock.",
"icon": "clock-mixed.png",
"type": "clock",

View File

@ -3,3 +3,6 @@
0.03: Use default Bangle formatter for booleans
0.04: Remove copied sched alarm.js & import newer features (oneshot alarms)
0.05: Fix creating new alarms/timers in hardmode
0.06: Support fastloading
0.07: Fix fastloading support - ensure drag handler's restored after
menu display/fastload removes it

View File

@ -1,11 +1,12 @@
{
Bangle.loadWidgets();
Bangle.drawWidgets();
var R = Bangle.appRect;
var layer;
var drag;
var timerInt1 = [];
var timerInt2 = [];
const R = Bangle.appRect;
let layer;
let drag;
let timerInt1 = [];
let timerInt2 = [];
function getCurrentTime() {
let time = new Date();
@ -66,8 +67,7 @@ function setHM(alarm, on) {
function drawTimers() {
layer = 0;
var timers = require("sched").getAlarms().filter(a => a.timer && a.appid == "multitimer");
var alarms = require("sched").getAlarms();
const timers = require("sched").getAlarms().filter(a => a.timer && a.appid == "multitimer");
function updateTimers(idx) {
if (!timerInt1[idx]) timerInt1[idx] = setTimeout(function() {
@ -78,15 +78,15 @@ function drawTimers() {
}, 1000 - (timers[idx].t % 1000));
}
var s = E.showScroller({
E.showScroller({
h : 40, c : timers.length+2,
back : function() {load();},
draw : (idx, r) => {
function drawMenuItem(a) {
let msg = "";
g.setClipRect(R.x,R.y,R.x2,R.y2);
if (idx > 0 && timers[idx-1].msg) msg = "\n"+(timers[idx-1].msg.length > 10 ?
timers[idx-1].msg.substring(0, 10)+"..." : timers[idx-1].msg);
else msg = "";
return g.setColor(g.theme.bg2).fillRect({x:r.x+4,y:r.y+2,w:r.w-8, h:r.h-4, r:5})
.setColor(g.theme.fg2).setFont("6x8:2").setFontAlign(-1,0).drawString(a+msg,r.x+12,r.y+(r.h/2));
}
@ -112,22 +112,22 @@ function drawTimers() {
else if (idx > 0 && idx < timers.length+1) timerMenu(idx-1);
}
});
setUI();
}
function timerMenu(idx) {
layer = -1;
var timers = require("sched").getAlarms();
var timerIdx = [];
var j = 0;
const timers = require("sched").getAlarms();
const timerIdx = [];
let a;
for (let i = 0; i < timers.length; i++) {
if (timers[i].timer && timers[i].appid == "multitimer") {
a = i;
timerIdx.push(a);
j++;
}
}
var a = timers[timerIdx[idx]];
var msg = "";
a = timers[timerIdx[idx]];
function updateTimer() {
if (timerInt1[0] == undefined) timerInt1[0] = setTimeout(function() {
@ -138,7 +138,7 @@ function timerMenu(idx) {
}, 1000 - (a.t % 1000));
}
var s = E.showScroller({
E.showScroller({
h : 40, c : 5,
back : function() {
clearInt();
@ -153,6 +153,7 @@ function timerMenu(idx) {
}
if (i == 0) {
let msg = "";
if (a.msg) msg = "\n"+(a.msg.length > 10 ? a.msg.substring(0, 10)+"..." : a.msg);
if (a.on == true) {
drawMenuItem(formatTime(a.t-getCurrentTime())+msg);
@ -216,19 +217,17 @@ function timerMenu(idx) {
}
}
});
setUI();
}
function editTimer(idx, a) {
layer = -1;
var timers = require("sched").getAlarms().filter(a => a.timer && a.appid == "multitimer");
var alarms = require("sched").getAlarms();
var timerIdx = [];
var j = 0;
const timers = require("sched").getAlarms().filter(a => a.timer && a.appid == "multitimer");
const alarms = require("sched").getAlarms();
const timerIdx = [];
for (let i = 0; i < alarms.length; i++) {
if (alarms[i].timer && alarms[i].appid == "multitimer") {
b = i;
timerIdx.push(b);
j++;
timerIdx.push(i);
}
}
if (!a) {
@ -238,9 +237,10 @@ function editTimer(idx, a) {
if (!a.data) {
a.data = { hm: false };
}
var t = decodeTime(a.timer);
const t = decodeTime(a.timer);
function editMsg(idx, a) {
let msg;
g.clear();
idx < 0 ? msg = "" : msg = a.msg;
require("textinput").input({text:msg}).then(result => {
@ -256,9 +256,10 @@ function editTimer(idx, a) {
E.showAlert("Must install keyboard app").then(function() {
editTimer(idx, a);
});
setUI();
}
var menu = {
const menu = {
"": { "title": "Timer" },
"< Back": () => {
a.t = getCurrentTime() + a.timer;
@ -312,7 +313,7 @@ function editTimer(idx, a) {
value: !a.msg ? "" : a.msg.length > 6 ? a.msg.substring(0, 6)+"..." : a.msg,
//menu glitch? setTimeout required here
onchange: () => {
var kbapp = require("Storage").read("textinput");
const kbapp = require("Storage").read("textinput");
if (kbapp != undefined) setTimeout(editMsg, 0, idx, a);
else setTimeout(kbAlert, 0);
}
@ -324,11 +325,12 @@ function editTimer(idx, a) {
};
E.showMenu(menu);
setUI();
}
function drawSw() {
layer = 1;
var sw = require("Storage").readJSON("multitimer.json", true) || [];
const sw = require("Storage").readJSON("multitimer.json", true) || [];
function updateTimers(idx) {
if (!timerInt1[idx]) timerInt1[idx] = setTimeout(function() {
@ -339,12 +341,13 @@ function drawSw() {
}, 1000 - (sw[idx].t % 1000));
}
var s = E.showScroller({
E.showScroller({
h : 40, c : sw.length+2,
back : function() {load();},
draw : (idx, r) => {
function drawMenuItem(a) {
let msg;
g.setClipRect(R.x,R.y,R.x2,R.y2);
if (idx > 0 && sw[idx-1].msg) msg = "\n"+(sw[idx-1].msg.length > 10 ?
sw[idx-1].msg.substring(0, 10)+"..." : sw[idx-1].msg);
@ -374,11 +377,12 @@ function drawSw() {
else if (idx > 0 && idx < sw.length+1) swMenu(idx-1);
}
});
setUI();
}
function swMenu(idx, a) {
layer = -1;
var sw = require("Storage").readJSON("multitimer.json", true) || [];
const sw = require("Storage").readJSON("multitimer.json", true) || [];
if (sw[idx]) a = sw[idx];
else {
a = {"t" : 0, "on" : false, "msg" : ""};
@ -397,7 +401,7 @@ function swMenu(idx, a) {
function editMsg(idx, a) {
g.clear();
msg = a.msg;
const msg = a.msg;
require("textinput").input({text:msg}).then(result => {
if (result != "") {
a.msg = result;
@ -413,9 +417,10 @@ function swMenu(idx, a) {
E.showAlert("Must install keyboard app").then(function() {
swMenu(idx, a);
});
setUI();
}
var s = E.showScroller({
E.showScroller({
h : 40, c : 5,
back : function() {
clearInt();
@ -430,6 +435,7 @@ function swMenu(idx, a) {
}
if (i == 0) {
let msg;
if (a.msg) msg = "\n"+(a.msg.length > 10 ? a.msg.substring(0, 10)+"..." : a.msg);
else msg = "";
if (a.on == true) {
@ -482,7 +488,7 @@ function swMenu(idx, a) {
//edit message
if (i == 3) {
clearInt();
var kbapp = require("Storage").read("textinput");
const kbapp = require("Storage").read("textinput");
if (kbapp != undefined) editMsg(idx, a);
else kbAlert();
}
@ -495,21 +501,22 @@ function swMenu(idx, a) {
}
}
});
setUI();
}
function drawAlarms() {
layer = 2;
var alarms = require("sched").getAlarms().filter(a => !a.timer);
const alarms = require("sched").getAlarms().filter(a => !a.timer);
var s = E.showScroller({
E.showScroller({
h : 40, c : alarms.length+2,
back : function() {load();},
draw : (idx, r) => {
function drawMenuItem(a) {
g.setClipRect(R.x,R.y,R.x2,R.y2);
var on = "";
var dow = "";
let on = "";
let dow = "";
if (idx > 0 && alarms[idx-1].on == true) on = " - on";
else if (idx > 0 && alarms[idx-1].on == false) on = " - off";
if (idx > 0 && idx < alarms.length+1) dow = "\n"+"SMTWTFS".split("").map((d,n)=>alarms[idx-1].dow&(1<<n)?d:".").join("");
@ -525,7 +532,7 @@ function drawAlarms() {
.setColor(g.theme.fg).setFont("6x8:2").setFontAlign(0,0).drawString("< Swipe >",r.x+(r.w/2),r.y+(r.h/2));
}
else if (idx > 0 && idx < alarms.length+1){
var str = formatTime(alarms[idx-1].t);
const str = formatTime(alarms[idx-1].t);
drawMenuItem(str.slice(0, -3));
}
},
@ -535,6 +542,7 @@ function drawAlarms() {
else if (idx > 0 && idx < alarms.length+1) editAlarm(idx-1);
}
});
setUI();
}
function editDOW(dow, onchange) {
@ -542,26 +550,24 @@ function editDOW(dow, onchange) {
'': { 'title': 'Days of Week' },
'< Back' : () => onchange(dow)
};
for (var i = 0; i < 7; i++) (i => {
var dayOfWeek = require("locale").dow({ getDay: () => i });
for (let i = 0; i < 7; i++) (i => {
const dayOfWeek = require("locale").dow({ getDay: () => i });
menu[dayOfWeek] = {
value: !!(dow&(1<<i)),
onchange: v => v ? dow |= 1<<i : dow &= ~(1<<i),
};
})(i);
E.showMenu(menu);
setUI();
}
function editAlarm(idx, a) {
layer = -1;
var alarms = require("sched").getAlarms();
var alarmIdx = [];
var j = 0;
const alarms = require("sched").getAlarms();
const alarmIdx = [];
for (let i = 0; i < alarms.length; i++) {
if (!alarms[i].timer) {
b = i;
alarmIdx.push(b);
j++;
alarmIdx.push(i);
}
}
if (!a) {
@ -571,9 +577,10 @@ function editAlarm(idx, a) {
if (!a.data) {
a.data = { hm: false };
}
var t = decodeTime(a.t);
const t = decodeTime(a.t);
function editMsg(idx, a) {
let msg;
g.clear();
idx < 0 ? msg = "" : msg = a.msg;
require("textinput").input({text:msg}).then(result => {
@ -589,9 +596,10 @@ function editAlarm(idx, a) {
E.showAlert("Must install keyboard app").then(function() {
editAlarm(idx, a);
});
setUI();
}
var menu = {
const menu = {
"": { "title": "Alarm" },
"< Back": () => {
if (idx >= 0) alarms[alarmIdx[idx]] = a;
@ -646,7 +654,7 @@ function editAlarm(idx, a) {
value: !a.msg ? "" : a.msg.length > 6 ? a.msg.substring(0, 6)+"..." : a.msg,
//menu glitch? setTimeout required here
onchange: () => {
var kbapp = require("Storage").read("textinput");
const kbapp = require("Storage").read("textinput");
if (kbapp != undefined) setTimeout(editMsg, 0, idx, a);
else setTimeout(kbAlert, 0);
}
@ -662,11 +670,21 @@ function editAlarm(idx, a) {
};
E.showMenu(menu);
setUI();
}
drawTimers();
function setUI() {
// E.showMenu/E.showScroller/E.showAlert call setUI, so we register onDrag() separately
// and tack on uiRemove after the fact to avoid interfering
Bangle.on("drag", onDrag);
Bangle.uiRemove = () => {
Bangle.removeListener("drag", onDrag);
Object.values(timerInt1).forEach(clearTimeout);
Object.values(timerInt2).forEach(clearTimeout);
};
}
Bangle.on("drag", e=>{
function onDrag(e) {
if (layer < 0) return;
if (!drag) { // start dragging
drag = {x: e.x, y: e.y};
@ -687,4 +705,7 @@ Bangle.on("drag", e=>{
else if (layer == 2) drawAlarms();
}
}
});
}
drawTimers();
}

View File

@ -1,7 +1,7 @@
{
"id": "multitimer",
"name": "Multi Timer",
"version": "0.05",
"version": "0.07",
"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": [

View File

@ -16,11 +16,16 @@
Support for zooming in on map
Satellite count moved to widget bar to leave more room for the map
0.15: Make track drawing an option (default off)
0.16: Draw waypoints, too.
0.16: Draw waypoints, too
0.17: With new Recorder app allow track to be drawn in the background
Switch tile layer URL for faster/more reliable map tiles
0.18: Prefer map with highest resolution
0.19: Remember latitude, longitude & scale
0.20: Make Satellite counter widget 24px wide (was 48)
Move 'Center GPS' to the top of the menu
If 'Recorder' app installed, add a 'Record' menu item
If 'Recorder' app installed, add a 'Record' menu item
0.21: Draw a current position marker (Bangle.js 2 only)
Enable/Disable previous position marker in new setting "Draw cont. position"
0.22: Replace position marker with direction arrow
0.23: Bugfix: Enable Compass if needed
0.24: Allow zooming by clicking the screen

View File

@ -29,6 +29,7 @@ and marks the path that you've been travelling (if enabled), and
displays waypoints in the watch (if dependencies exist).
* Drag on the screen to move the map
* Click bottom left to zoom in, bottom right to zoom out
* Press the button to bring up a menu, where you can zoom, go to GPS location,
put the map back in its default location, or choose whether to draw the currently
recording GPS track (from the `Recorder` app).

View File

@ -7,6 +7,15 @@ var hasScrolled = false;
var settings = require("Storage").readJSON("openstmap.json",1)||{};
var plotTrack;
let checkMapPos = false; // Do we need to check the if the coordinates we have are valid
var startDrag = 0;
if (Bangle.setLCDOverlay) {
// Icon for current location+direction: https://icons8.com/icon/11932/gps 24x24, 1 Bit + transparency + inverted
var imgLoc = require("heatshrink").decompress(atob("jEYwINLAQk8AQl+AQn/AQcB/+AAQUD//AAQUH//gAQUP//wAQUf//4j8AvA9IA=="));
// overlay buffer for current location, a bit bigger then image so we can rotate
const ovSize = Math.ceil(Math.sqrt(imgLoc[0]*imgLoc[0]+imgLoc[1]*imgLoc[1]));
var ovLoc = Graphics.createArrayBuffer(ovSize,ovSize,imgLoc[2] & 0x7f,{msb:true});
}
if (settings.lat !== undefined && settings.lon !== undefined && settings.scale !== undefined) {
// restore last view
@ -15,6 +24,9 @@ if (settings.lat !== undefined && settings.lon !== undefined && settings.scale !
m.scale = settings.scale;
checkMapPos = true;
}
if (settings.dirSrc === undefined) {
settings.dirSrc = 1; // Default=GPS
}
// Redraw the whole page
function redraw() {
@ -27,8 +39,10 @@ function redraw() {
m.scale = m.map.scale;
m.draw();
}
checkMapPos = false;
drawPOI();
drawMarker();
drawLocation();
// if track drawing is enabled...
if (settings.drawTrack) {
if (HASWIDGETS && WIDGETS["gpsrec"] && WIDGETS["gpsrec"].plotTrack) {
@ -65,20 +79,63 @@ function drawPOI() {
})
}
// Draw the marker for where we are
function isInside(rect, e, w, h) {
return e.x-w/2>=rect.x && e.x+w/2<rect.x+rect.w
&& e.y-h/2>=rect.y && e.y+h/2<=rect.y+rect.h;
}
// Draw the location & direction marker for where we are
function drawMarker() {
if (!fix.fix) return;
if (!fix.fix || !settings.drawMarker) return;
var p = m.latLonToXY(fix.lat, fix.lon);
g.setColor(1,0,0);
g.fillRect(p.x-2, p.y-2, p.x+2, p.y+2);
if (isInside(R, p, 4, 4)) { // avoid drawing over widget area
g.setColor(1,0,0);
g.fillRect(p.x-2, p.y-2, p.x+2, p.y+2);
}
}
// Draw current location+direction with LCD Overlay (Bangle.js 2 only)
function drawLocation() {
if (!Bangle.setLCDOverlay) {
return; // Overlay not supported
}
if (!fix.fix || !mapVisible || settings.dirSrc === 0) {
if (this.hasOverlay) {
Bangle.setLCDOverlay(); // clear if map is not visible or no fix
this.hasOverlay = false;
}
return;
}
var p = m.latLonToXY(fix.lat, fix.lon);
ovLoc.clear();
if (isInside(R, p, ovLoc.getWidth(), ovLoc.getHeight())) { // avoid drawing over widget area
const angle = settings.dirSrc === 1 ? fix.course : Bangle.getCompass().heading;
if (!isNaN(angle)) {
ovLoc.drawImage(imgLoc, ovLoc.getWidth()/2, ovLoc.getHeight()/2, {rotate: angle*Math.PI/180});
}
}
Bangle.setLCDOverlay({width:ovLoc.getWidth(), height:ovLoc.getHeight(),
bpp:ovLoc.getBPP(), transparent:0,
palette:new Uint16Array([0, g.toColor("#00F")]),
buffer:ovLoc.buffer
}, p.x-ovLoc.getWidth()/2, p.y-ovLoc.getHeight()/2);
this.hasOverlay = true;
}
Bangle.on('GPS',function(f) {
fix=f;
if (HASWIDGETS && WIDGETS["sats"]) WIDGETS["sats"].draw(WIDGETS["sats"]);
if (mapVisible) drawMarker();
if (mapVisible) {
drawMarker();
drawLocation();
}
});
Bangle.setGPSPower(1, "app");
Bangle.setCompassPower(settings.dirSrc === 2, "openstmap");
if (HASWIDGETS) {
Bangle.loadWidgets();
@ -105,6 +162,7 @@ function showMenu() {
if (plotTrack && plotTrack.stop)
plotTrack.stop();
mapVisible = false;
drawLocation();
var menu = {
"":{title:/*LANG*/"Map"},
"< Back": ()=> showMap(),
@ -128,13 +186,36 @@ function showMenu() {
value : !!settings.drawTrack,
onchange : v => { settings.drawTrack=v; writeSettings(); }
},
/*LANG*/"Center Map": () =>{
/*LANG*/"Draw cont. position": {
value : !!settings.drawMarker,
onchange : v => { settings.drawMarker=v; writeSettings(); }
},
});
if (Bangle.setLCDOverlay) {
menu[/*LANG*/"Direction source"] = {
value: settings.dirSrc,
min: 0, max: 2,
format: v => [/*LANG*/"None", /*LANG*/"GPS", /*LANG*/"Compass"][v],
onchange: v => {
settings.dirSrc = v;
Bangle.setCompassPower(settings.dirSrc === 2, "openstmap");
writeSettings();
}
};
menu[/*LANG*/"Reset compass"] = () => {
Bangle.resetCompass();
showMap();
};
}
menu[/*LANG*/"Center Map"] = () =>{
m.lat = m.map.lat;
m.lon = m.map.lon;
m.scale = m.map.scale;
showMap();
}
});
};
// If we have the recorder widget, add a menu item to start/stop recording
if (WIDGETS.recorder) {
menu[/*LANG*/"Record"] = {
@ -145,6 +226,7 @@ function showMenu() {
}
};
}
menu[/*LANG*/"Exit"] = () => load();
E.showMenu(menu);
}
@ -155,13 +237,28 @@ function showMap() {
Bangle.setUI({mode:"custom",drag:e=>{
if (plotTrack && plotTrack.stop) plotTrack.stop();
if (e.b) {
if (!startDrag)
startDrag = getTime();
g.setClipRect(R.x,R.y,R.x2,R.y2);
g.scroll(e.dx,e.dy);
m.scroll(e.dx,e.dy);
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
hasScrolled = true;
drawLocation();
} else if (hasScrolled) {
delta = getTime() - startDrag;
startDrag = 0;
hasScrolled = false;
if (delta < 0.2) {
if (e.y > g.getHeight() / 2) {
if (e.x < g.getWidth() / 2) {
m.scale /= 2;
} else {
m.scale *= 2;
}
}
g.reset().clearRect(R);
}
redraw();
}
}, btn: () => showMenu() });

View File

@ -2,7 +2,7 @@
"id": "openstmap",
"name": "OpenStreetMap",
"shortName": "OpenStMap",
"version": "0.20",
"version": "0.24",
"description": "Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are. Once installed this also adds map functionality to `GPS Recorder` and `Recorder` apps",
"readme": "README.md",
"icon": "app.png",

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