mirror of https://github.com/espruino/BangleApps
Merge branch 'master' into env
commit
bdcfb7e616
|
@ -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.
|
||||
|
||||
|
|
12
android.html
12
android.html
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Handle missing settings (e.g. first-install)
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -59,6 +59,13 @@
|
|||
}
|
||||
};
|
||||
|
||||
mainmenu['Detect settings changes'] = {
|
||||
value: !!settings.detectSettingsChange,
|
||||
onchange: v => {
|
||||
writeSettings("detectSettingsChange",v);
|
||||
}
|
||||
};
|
||||
|
||||
return mainmenu;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"listId":"","fontSize":1,"cardWidth":9,"swipeGesture":0}
|
||||
{"listId":"","fontSize":1,"cardWidth":9,"swipeGesture":1}
|
||||
|
|
|
@ -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" }],
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
||||

|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
2835
apps/gipy/app.js
2835
apps/gipy/app.js
File diff suppressed because one or more lines are too long
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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"}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
})
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)}})
|
|
@ -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;
|
||||
|
|
|
@ -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}}}
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
|
@ -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);
|
||||
|
|
|
@ -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" }],
|
||||
|
|
Binary file not shown.
|
@ -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) => {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -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)
|
Binary file not shown.
After Width: | Height: | Size: 505 B |
|
@ -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;
|
||||
};
|
||||
})();
|
|
@ -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}})()
|
|
@ -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"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,9 @@
|
|||
# Luna Clock
|
||||
|
||||

|
||||
|
||||
Simple clock face inspired by the moon.
|
||||
|
||||
## Creator
|
||||
|
||||
NovaDawn999
|
|
@ -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=="))
|
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
|
@ -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 |
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"});
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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() });
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue