mirror of https://github.com/espruino/BangleApps
Merge branch 'master' into jekyll-apps.json
commit
d3af80efc9
|
@ -6,3 +6,4 @@
|
|||
0.07: Resolve one FIFO_FULL case and exit App with button press
|
||||
0.08: Leave GPS power switched on on exit (will switch off after 0.5 seconds anyway)
|
||||
0.09: Fix FIFO_FULL error
|
||||
0.10: Show satellites "in view" separated by GNS-system
|
||||
|
|
|
@ -16,9 +16,8 @@ var lastFix = {
|
|||
time: 0,
|
||||
satellites: 0
|
||||
};
|
||||
var SATinView = 0;
|
||||
var nofBD = 0;
|
||||
var nofGP = 0;
|
||||
var SATinView = 0, lastSATinView = -1, nofGP = 0, nofBD = 0, nofGL = 0;
|
||||
const leaveNofixLayout = 1; // 0 = stay on initial screen for debugging (default = 1)
|
||||
var listenerGPSraw = 0;
|
||||
|
||||
function formatTime(now) {
|
||||
|
@ -63,7 +62,7 @@ function getMaidenHead(param1,param2){
|
|||
function onGPS(fix) {
|
||||
if (lastFix.fix != fix.fix) {
|
||||
// if fix is different, change the layout
|
||||
if (fix.fix) {
|
||||
if (fix.fix && leaveNofixLayout) {
|
||||
layout = new Layout( {
|
||||
type:"v", c: [
|
||||
{type:"txt", font:"6x8:2", label:"GPS Info" },
|
||||
|
@ -92,11 +91,12 @@ function onGPS(fix) {
|
|||
g.clearRect(0,24,g.getWidth(),g.getHeight());
|
||||
layout.render();
|
||||
}
|
||||
//lastFix = fix;
|
||||
if (fix.fix) {
|
||||
if (fix.fix && leaveNofixLayout) {
|
||||
if (listenerGPSraw == 1) {
|
||||
Bangle.removeListener('GPS-raw', onGPSraw);
|
||||
listenerGPSraw = 0;
|
||||
lastSATinView = -1;
|
||||
Bangle.buzz(50);
|
||||
}
|
||||
var locale = require("locale");
|
||||
var satellites = fix.satellites;
|
||||
|
@ -115,27 +115,31 @@ function onGPS(fix) {
|
|||
layout.sat.label = fix.satellites;
|
||||
layout.render(layout.sat);
|
||||
}
|
||||
if (SATinView != lastFix.SATinView) {
|
||||
if (SATinView != lastSATinView) {
|
||||
if (!leaveNofixLayout) SATinView = -1;
|
||||
lastSATinView = SATinView;
|
||||
layout.clear(layout.progress);
|
||||
layout.progress.label = "in view: " + SATinView;
|
||||
layout.progress.label = "in view GP/BD/GL: " + nofGP + " " + nofBD + " " + nofGL;
|
||||
// console.log("in view GP/BD/GL: " + nofGP + " " + nofBD + " " + nofGL);
|
||||
layout.render(layout.progress);
|
||||
}
|
||||
}
|
||||
//layout.render();
|
||||
|
||||
if (listenerGPSraw == 0 && !fix.fix) {
|
||||
setTimeout(() => Bangle.on('GPS-raw', onGPSraw), 10);
|
||||
listenerGPSraw = 1;
|
||||
}
|
||||
|
||||
lastFix = fix;
|
||||
lastFix.SATinView = SATinView;
|
||||
}
|
||||
|
||||
function onGPSraw(nmea) {
|
||||
if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13));
|
||||
if (nmea.slice(3,6) == "GSV") {
|
||||
// console.log(nmea.slice(1,3) + " " + nmea.slice(11,13));
|
||||
if (nmea.slice(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13));
|
||||
SATinView = nofBD + nofGP;
|
||||
if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13));
|
||||
if (nmea.slice(0,7) == "$GLGSV,") nofGL = Number(nmea.slice(11,13));
|
||||
SATinView = nofGP + nofBD + nofGL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "gpsinfo",
|
||||
"name": "GPS Info",
|
||||
"version": "0.09",
|
||||
"version": "0.10",
|
||||
"description": "An application that displays information about altitude, lat/lon, satellites and time",
|
||||
"icon": "gps-info.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -9,3 +9,4 @@
|
|||
0.09: Bangle.js 2 - pressing the button goes back to clock (fix #971)
|
||||
After 10s of being locked, the launcher goes back to the clock screen
|
||||
0.10: added in selectable font in settings including scalable vector font
|
||||
0.11: Merge Bangle.js 1 and 2 launchers, again
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
var s = require("Storage");
|
||||
var apps = s.list(/\.info$/).map(app=>{var 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" || !app.type));
|
||||
apps.sort((a,b)=>{
|
||||
var n=(0|a.sortorder)-(0|b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
if (a.name<b.name) return -1;
|
||||
if (a.name>b.name) return 1;
|
||||
return 0;
|
||||
});
|
||||
var selected = 0;
|
||||
var menuScroll = 0;
|
||||
var menuShowing = false;
|
||||
|
||||
function drawMenu() {
|
||||
g.reset().setFont("6x8",2).setFontAlign(-1,0);
|
||||
var w = g.getWidth();
|
||||
var h = g.getHeight();
|
||||
var m = w/2;
|
||||
var n = Math.floor((h-48)/64);
|
||||
if (selected>=n+menuScroll) menuScroll = 1+selected-n;
|
||||
if (selected<menuScroll) menuScroll = selected;
|
||||
// arrows
|
||||
g.setColor(menuScroll ? g.theme.fg : g.theme.bg);
|
||||
g.fillPoly([m,6,m-14,20,m+14,20]);
|
||||
g.setColor((apps.length>n+menuScroll) ? g.theme.fg : g.theme.bg);
|
||||
g.fillPoly([m,h-7,m-14,h-21,m+14,h-21]);
|
||||
// draw
|
||||
g.setColor(g.theme.fg);
|
||||
for (var i=0;i<n;i++) {
|
||||
var app = apps[i+menuScroll];
|
||||
if (!app) break;
|
||||
var y = 24+i*64;
|
||||
if (i+menuScroll==selected) {
|
||||
g.setColor(g.theme.bgH).fillRect(0,y,w-1,y+63);
|
||||
g.setColor(g.theme.fgH).drawRect(0,y,w-1,y+63);
|
||||
} else {
|
||||
g.clearRect(0, y, w-1, y+63);
|
||||
g.setColor(g.theme.fg);
|
||||
}
|
||||
g.drawString(app.name,64,y+32);
|
||||
var icon=undefined;
|
||||
if (app.icon) icon = s.read(app.icon);
|
||||
if (icon) try {g.drawImage(icon,8,y+8);} catch(e){}
|
||||
}
|
||||
}
|
||||
g.clear();
|
||||
drawMenu();
|
||||
Bangle.setUI("updown",dir=>{
|
||||
if (dir) {
|
||||
selected += dir;
|
||||
if (selected<0) selected = apps.length-1;
|
||||
if (selected>=apps.length) selected = 0;
|
||||
drawMenu();
|
||||
} else {
|
||||
if (!apps[selected].src) return;
|
||||
if (require("Storage").read(apps[selected].src)===undefined) {
|
||||
E.showMessage("App Source\nNot found");
|
||||
setTimeout(drawMenu, 2000);
|
||||
} else {
|
||||
E.showMessage("Loading...");
|
||||
load(apps[selected].src);
|
||||
}
|
||||
}
|
||||
});
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
// 10s of inactivity goes back to clock
|
||||
if (Bangle.setLocked) Bangle.setLocked(false); // unlock initially
|
||||
var lockTimeout;
|
||||
Bangle.on('lock', locked => {
|
||||
if (lockTimeout) clearTimeout(lockTimeout);
|
||||
lockTimeout = undefined;
|
||||
if (locked)
|
||||
lockTimeout = setTimeout(_=>load(), 10000);
|
||||
});
|
|
@ -63,8 +63,11 @@ E.showScroller({
|
|||
}
|
||||
});
|
||||
|
||||
// pressing button goes back
|
||||
// on bangle.js 2, the screen is used for navigating, so the single button goes back
|
||||
// on bangle.js 1, the buttons are used for navigating
|
||||
if (process.env.HWVERSION==2) {
|
||||
setWatch(_=>load(), BTN1, {edge:"falling"});
|
||||
}
|
||||
|
||||
// 10s of inactivity goes back to clock
|
||||
Bangle.setLocked(false); // unlock initially
|
|
@ -9,5 +9,6 @@
|
|||
0.09: Tab anywhere to open the launcher.
|
||||
0.10: Removed swipes to be compatible with the Pattern Launcher. Stability improvements.
|
||||
0.11: Show the gadgetbridge weather temperature (settings).
|
||||
0.12: Added humidity to data.
|
||||
0.12: Added humidity as an option to display.
|
||||
0.13: Improved battery visualization.
|
||||
0.14: Added altitude as an option to display.
|
|
@ -13,10 +13,10 @@ To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerda
|
|||
* Full screen mode - widgets are still loaded but not shown.
|
||||
* Tab on left/right to switch between different screens.
|
||||
* Cusomizable data that is shown on screen 1 (steps, weather etc.)
|
||||
* Shows random images of real planets.
|
||||
* Shows random and real images of planets.
|
||||
* Tap on top/bottom of screen 1 to activate an alarm.
|
||||
* The lower orange line indicates the battery level.
|
||||
* Display graphs for steps + hrm on the second screen.
|
||||
* Display graphs (day or month) for steps + hrm on the second screen.
|
||||
|
||||
## Data that can be configured
|
||||
* Steps - Steps loaded via the health module
|
||||
|
@ -25,16 +25,17 @@ To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerda
|
|||
* HRM - Last measured HRM
|
||||
* Temp - Weather temperature loaded via the weather module + gadgetbridge
|
||||
* Humidity - Humidity loaded via the weather module + gadgetbridge
|
||||
* Altitude - Shows the altitude in m.
|
||||
* CoreT - Temperature of device
|
||||
|
||||
## Multiple screens support
|
||||
Access different screens via swipe left/ right
|
||||
Access different screens via tap on the left/ right side of the screen
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## Contributors
|
||||
- Initial creation and improvements: [David Peer](https://github.com/peerdavid).
|
||||
- Improvements: [Adam Schmalhofer](https://github.com/adamschmalhofer).
|
||||
- Improvements: [Jon Warrington](https://github.com/BartokW).
|
||||
- [David Peer](https://github.com/peerdavid).
|
||||
- [Adam Schmalhofer](https://github.com/adamschmalhofer).
|
||||
- [Jon Warrington](https://github.com/BartokW).
|
||||
|
|
|
@ -26,10 +26,8 @@ let cGrey = "#424242";
|
|||
* Global lcars variables
|
||||
*/
|
||||
let lcarsViewPos = 0;
|
||||
let drag;
|
||||
let hrmValue = 0;
|
||||
// let hrmValue = 0;
|
||||
var plotMonth = false;
|
||||
var disableInfoUpdate = true; // When gadgetbridge connects, step infos cannot be loaded
|
||||
|
||||
/*
|
||||
* Requirements and globals
|
||||
|
@ -115,12 +113,43 @@ function queueDraw() {
|
|||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
|
||||
function printData(key, y, c){
|
||||
/**
|
||||
* This function plots a data row in LCARS style.
|
||||
* Note: It can be called async and therefore, the text alignment and
|
||||
* font is set each time the function is called.
|
||||
*/
|
||||
function printRow(text, value, y, c){
|
||||
g.setFontAntonioMedium();
|
||||
g.setFontAlign(-1,-1,0);
|
||||
g.setColor(c);
|
||||
g.fillRect(79, y-2, 85 ,y+18);
|
||||
|
||||
g.setFontAlign(0,-1,0);
|
||||
g.drawString(value, 110, y);
|
||||
|
||||
g.setColor(c);
|
||||
g.setFontAlign(-1,-1,0);
|
||||
g.fillRect(133, y-2, 165 ,y+18);
|
||||
g.fillCircle(161, y+8, 10);
|
||||
g.setColor(cBlack);
|
||||
g.drawString(text, 135, y);
|
||||
}
|
||||
|
||||
|
||||
function drawData(key, y, c){
|
||||
try{
|
||||
_drawData(key, y, c);
|
||||
} catch(ex){
|
||||
// Show last error - next try hopefully works.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _drawData(key, y, c){
|
||||
key = key.toUpperCase()
|
||||
var text = key;
|
||||
var value = "ERR";
|
||||
var should_print= true;
|
||||
|
||||
if(key == "STEPS"){
|
||||
text = "STEP";
|
||||
|
@ -134,7 +163,7 @@ function printData(key, y, c){
|
|||
value = E.getAnalogVRef().toFixed(2) + "V";
|
||||
|
||||
} else if(key == "HRM"){
|
||||
value = hrmValue;
|
||||
value = Math.round(Bangle.getHealthStatus("day").bpm);
|
||||
|
||||
} else if (key == "TEMP"){
|
||||
var weather = getWeather();
|
||||
|
@ -143,24 +172,29 @@ function printData(key, y, c){
|
|||
} else if (key == "HUMIDITY"){
|
||||
text = "HUM";
|
||||
var weather = getWeather();
|
||||
value = weather.hum + "%";
|
||||
value = weather.hum;
|
||||
|
||||
} else if (key == "ALTITUDE"){
|
||||
should_print= false;
|
||||
text = "ALT";
|
||||
|
||||
// Immediately print something - avoid that its empty
|
||||
printRow(text, "", y, c);
|
||||
Bangle.getPressure().then(function(data){
|
||||
if(data && data.altitude){
|
||||
value = Math.round(data.altitude);
|
||||
printRow(text, value, y, c);
|
||||
}
|
||||
})
|
||||
|
||||
} else if(key == "CORET"){
|
||||
value = locale.temp(parseInt(E.getTemperature()));
|
||||
}
|
||||
|
||||
g.setColor(c);
|
||||
g.fillRect(79, y-2, 85 ,y+18);
|
||||
|
||||
g.setFontAlign(0,-1,0);
|
||||
g.drawString(value, 110, y);
|
||||
|
||||
g.setColor(c);
|
||||
g.setFontAlign(-1,-1,0);
|
||||
g.fillRect(133, y-2, 165 ,y+18);
|
||||
g.fillCircle(161, y+8, 10);
|
||||
g.setColor(cBlack);
|
||||
g.drawString(text, 135, y);
|
||||
// Print for all datapoints that are not async
|
||||
if(should_print){
|
||||
printRow(text, value, y, c);
|
||||
}
|
||||
}
|
||||
|
||||
function drawHorizontalBgLine(color, x1, x2, y, h){
|
||||
|
@ -273,9 +307,9 @@ function drawPosition0(){
|
|||
// Draw data
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.setColor(cWhite);
|
||||
printData(settings.dataRow1, 97, cOrange);
|
||||
printData(settings.dataRow2, 122, cPurple);
|
||||
printData(settings.dataRow3, 147, cBlue);
|
||||
drawData(settings.dataRow1, 97, cOrange);
|
||||
drawData(settings.dataRow2, 122, cPurple);
|
||||
drawData(settings.dataRow3, 147, cBlue);
|
||||
|
||||
// Draw state
|
||||
drawState();
|
||||
|
@ -446,7 +480,8 @@ function getWeather(){
|
|||
wrose: "-"
|
||||
};
|
||||
} else {
|
||||
weather.temp = locale.temp(parseInt(weather.temp-273.15))
|
||||
weather.temp = locale.temp(Math.round(weather.temp-273.15))
|
||||
weather.hum = weather.hum + "%";
|
||||
}
|
||||
|
||||
return weather;
|
||||
|
@ -519,10 +554,6 @@ Bangle.on('charging',function(charging) {
|
|||
drawState();
|
||||
});
|
||||
|
||||
Bangle.on('HRM', function (hrm) {
|
||||
hrmValue = hrm.bpm;
|
||||
});
|
||||
|
||||
|
||||
function increaseAlarm(){
|
||||
if(isAlarmEnabled()){
|
||||
|
|
|
@ -18,14 +18,14 @@
|
|||
storage.write(SETTINGS_FILE, settings)
|
||||
}
|
||||
|
||||
var data_options = ["Steps", "Battery", "VREF", "HRM", "Temp", "Humidity", "CoreT"];
|
||||
var data_options = ["Steps", "Battery", "VREF", "HRM", "Temp", "Humidity", "Altitude", "CoreT"];
|
||||
|
||||
E.showMenu({
|
||||
'': { 'title': 'LCARS Clock' },
|
||||
'< Back': back,
|
||||
'Row 1': {
|
||||
value: 0 | data_options.indexOf(settings.dataRow1),
|
||||
min: 0, max: 6,
|
||||
min: 0, max: 7,
|
||||
format: v => data_options[v],
|
||||
onchange: v => {
|
||||
settings.dataRow1 = data_options[v];
|
||||
|
@ -34,7 +34,7 @@
|
|||
},
|
||||
'Row 2': {
|
||||
value: 0 | data_options.indexOf(settings.dataRow2),
|
||||
min: 0, max: 6,
|
||||
min: 0, max: 7,
|
||||
format: v => data_options[v],
|
||||
onchange: v => {
|
||||
settings.dataRow2 = data_options[v];
|
||||
|
@ -43,7 +43,7 @@
|
|||
},
|
||||
'Row 3': {
|
||||
value: 0 | data_options.indexOf(settings.dataRow3),
|
||||
min: 0, max: 6,
|
||||
min: 0, max: 7,
|
||||
format: v => data_options[v],
|
||||
onchange: v => {
|
||||
settings.dataRow3 = data_options[v];
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "LCARS Clock",
|
||||
"shortName":"LCARS",
|
||||
"icon": "lcars.png",
|
||||
"version":"0.13",
|
||||
"version":"0.14",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
||||
|
|
|
@ -280,7 +280,7 @@ function showMessage(msgid) {
|
|||
showMessageSettings(msg);
|
||||
}},
|
||||
{ type:"v", fillx:1, c: [
|
||||
{type:"txt", font:fontSmall, label:msg.src||"Message", bgCol:colBg, fillx:1, pad:2, halign:1 },
|
||||
{type:"txt", font:fontSmall, label:msg.src||/*LANG*/"Message", bgCol:colBg, fillx:1, pad:2, halign:1 },
|
||||
title?{type:"txt", font:titleFont, label:title, bgCol:colBg, fillx:1, pad:2 }:{},
|
||||
]},
|
||||
]},
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
require('Storage').writeJSON("messages.settings.json", settings);
|
||||
}
|
||||
|
||||
var vibPatterns = ["Off", ".", "-", "--", "-.-", "---"];
|
||||
var vibPatterns = [/*LANG*/"Off", ".", "-", "--", "-.-", "---"];
|
||||
var currentVib = settings().vibrate;
|
||||
var mainmenu = {
|
||||
"" : { "title" : "Messages" },
|
||||
"" : { "title" : /*LANG*/"Messages" },
|
||||
"< Back" : back,
|
||||
'Vibrate': {
|
||||
/*LANG*/'Vibrate': {
|
||||
value: Math.max(0,vibPatterns.indexOf(settings().vibrate)),
|
||||
min: 0, max: vibPatterns.length,
|
||||
format: v => vibPatterns[v]||"Off",
|
||||
|
@ -25,16 +25,16 @@
|
|||
updateSetting("vibrate", vibPatterns[v]);
|
||||
}
|
||||
},
|
||||
'Repeat': {
|
||||
/*LANG*/'Repeat': {
|
||||
value: settings().repeat,
|
||||
min: 2, max: 10,
|
||||
format: v => v+"s",
|
||||
onchange: v => updateSetting("repeat", v)
|
||||
},
|
||||
'Unread timer': {
|
||||
/*LANG*/'Unread timer': {
|
||||
value: settings().unreadTimeout,
|
||||
min: 0, max: 240, step : 10,
|
||||
format: v => v?v+"s":"Off",
|
||||
format: v => v?v+"s":/*LANG*/"Off",
|
||||
onchange: v => updateSetting("unreadTimeout", v)
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Bangle.js 2 compatibility
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"id": "pparrot",
|
||||
"name": "Party Parrot",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Party with a parrot on your wrist",
|
||||
"icon": "party-parrot.png",
|
||||
"type": "app",
|
||||
"tags": "party,parrot,lol",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"bangle1-party-parrot-screenshot.png"}],
|
||||
"storage": [
|
||||
|
|
|
@ -5,16 +5,17 @@ var imgs = [
|
|||
atob("qE5xH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AdwOBlcAAAsrq2BJn+BqxMHKX5NFJhgAFqxO5TYcruug0HLAAoIBvdQCIZN11icDqBLHAA+gMYRQ0TgcrvhNOAAaiCeWWBTgZNSKAuBJ17rDvZOVeQK4C1hOxdaYAFvbxvdgZOGbgMlLCF8DwSgrJxSKCKALvRUFmslbsJJ4YMG0F1qElld10ATGgGBJ9BOCvaLHHYgNEIoqsBKAIJFUFDtCurbIvhPHcgcrAAL9DBQclUFDtCGQIAJIIUAcYQHDq2ArmAqxsDfIL2BKFAxCvhPK0F7uoODSYVWrgACwBXCLwYQDlaekE4ROKAA97CwJODAAJuCfwYABuqglwKeNAA9QMoJPFrjwDAAjxBlesd0hOSHgeAJwjwDWRCgh1guBqBPTHYKfHOBIIBqxPhEgN7J6yfFJ5VQBILwgwJPWWwJPK0DwpJ66LBd6OgJ8TvXuoXBJ6HLBINWJ8VQJ6d8HYROEwD5BkpP/bYpPFrgIBuoUHqEAlZPiZxAAMHYWAdw18CY91BYOsJ8WgJ6d7UAzuBN5JPCwJPivagUC4MrJwoeJJ4VcJ72BJ4UrJ6igCKALtCqASJqDvhJ4bwVHodWTwQcJ0AQCJz3+QASCMABclDYd1MBmBJ76ABkrYCvZPUR4QABBxInCT0DuCJYJ3CeKt1NJZdClesd0RKBFIbxVqEldtusdwJJDvgqBqDxVTxjtgdwd8Fgd7KC77MT0H+TwLQGKAT4CJ/9WTwxQGupPbd4QABldWq2B1hPbTwwvDVYJRCUbRxDAAhTCJyusSRowEktQAAKhXvd1qBSHKKmBdxIwFuotFUjQjBvgkElZPWlaOCABRPFChwAPEYpPSqy9GAGlWJ6JO7UJgA=="),
|
||||
atob("qE5xH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AtwNWlcAAAUrq2BJP5MJAApS/JoRMJAApR7JotPumfAA10VQlWJ2+AHwZMB4nDABHEz9PeoesJ2icDp5NLKIpkCKGesG4UlJoJOBKJQJBBYT1BeQRQwwJOCTgI/Bb4V0KAxIBp8llb+BCYlWJ2V0IYbzCgBCBJwgKDBoefkoFCUFuBGISWEIglPBI8rAAIYDCYeAJ2hPJcgmArmAqylGJ9ZODz5OEJ5IICq1cAAZQGlZOuJoiWFVQIICcgRPFrhLCuklAgOBJ0+AlZOJJAYABAwgUBJwtcDwMr4itCeE+sJwV0dgoAKU4UrwBPFeARuBUAMr1jslJyhPBIAMlJ5auCeEotCJyRPBMwRPLeAVWJ0eAE4NPJqLvR4iglwIlBFgQATH4ROFJ4qgmFYUAdqRPSWASghTwclJ6qPDJ4srBIIQGwCejT63DksAlZPHp4iD4gQCT0SfXb4ZPFA4N0CIl0eECeCZgSfWeA4hCz4QHJ7usEANPOgQtFeCagDOYV0OIpPCeDruCugkCZoqgUlbtDkpwGB4UAJ7mATYXEQoNPeC3EXYVWTwS/HJ8ErD4IlBeDXDEAQABNxBPfPQTqCEoSgXIIhtJf4ZOawIeBkomDUDYbCNhJPCwBOZwB6Hz8lK4oAT4lPNZRcCwJOheL10DJHEO4LuZdgUAugoHPAQ2JADDubToZOHAATVBBpb6YgGsJy1WdhJQIlYQMTylWJ05QEUQOfejd0EAOBJzElRh/EFwRRDAATuWkrBBJysrbaufKIhqCd1ydCJyZREQYIACDizuWJzLUDdoLyBDSq9CJ6eBJzYAbJ4WsTyuf4gAzT6usCoKfBp4AzlY4BwBPRCoQA5qxPRJ3ZQMA==")
|
||||
];
|
||||
var scale = g.getWidth()<200 ? 2 : 3;
|
||||
|
||||
function drawImg (i) {
|
||||
g.drawImage({
|
||||
width: 80, height: 57, bpp: 8,
|
||||
buffer: require("heatshrink").decompress(imgs[i])
|
||||
}, 0, 0, {scale: 3});
|
||||
}, (g.getWidth()-80*scale)/2, (g.getHeight()-57*scale)/2, {scale: scale});
|
||||
}
|
||||
|
||||
var currImg = 0;
|
||||
g.clear();
|
||||
g.setBgColor(0).clear();
|
||||
drawImg(currImg);
|
||||
setInterval(function() {
|
||||
currImg = (currImg + 1) % imgs.length;
|
||||
|
|
|
@ -221,7 +221,7 @@ function showThemeMenu() {
|
|||
m.draw();
|
||||
}
|
||||
var m = E.showMenu({
|
||||
'':{title:'Theme'},
|
||||
'':{title:/*LANG*/'Theme'},
|
||||
'< Back': ()=>showSystemMenu(),
|
||||
/*LANG*/'Dark BW': ()=>{
|
||||
upd({
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Updated to work with both Bangle.js 1 and 2.
|
||||
0.03: Now also visible on Bangle.js 2
|
||||
0.04: Now work with different themes
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
{
|
||||
"id": "slevel",
|
||||
"name": "Spirit Level",
|
||||
"version": "0.02",
|
||||
"version": "0.04",
|
||||
"description": "Show the current angle of the watch, so you can use it to make sure something is absolutely flat",
|
||||
"icon": "spiritlevel.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"tags": "tool",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
|
@ -2,6 +2,7 @@ g.clear();
|
|||
var old = {x:0,y:0};
|
||||
var W = g.getWidth();
|
||||
var H = g.getHeight();
|
||||
|
||||
Bangle.on('accel',function(v) {
|
||||
var max = Math.max(Math.abs(v.x),Math.abs(v.y),Math.abs(v.z));
|
||||
if (Math.abs(v.y)==max) {
|
||||
|
@ -13,20 +14,25 @@ Bangle.on('accel',function(v) {
|
|||
var d = Math.sqrt(v.x*v.x+v.y*v.y);
|
||||
var ang = Math.atan2(d,Math.abs(v.z))*180/Math.PI;
|
||||
|
||||
g.setColor(1,1,1);
|
||||
g.setFont("6x8",2);
|
||||
g.setFontAlign(0,-1);
|
||||
g.clearRect(W*(1/4),0,W*(3/4),H*(1/16));
|
||||
g.drawString(ang.toFixed(1),W/2,0);
|
||||
g.reset();
|
||||
g.clearRect(W*(1/4),0,W*(3/4),16);// clear behind text
|
||||
g.setFont("6x8",2).setFontAlign(0,-1).drawString(ang.toFixed(1),W/2,0);
|
||||
var n = {
|
||||
x:E.clip(W/2+v.x*256,4,W-4),
|
||||
y:E.clip(H/2+v.y*256,4,H-4)};
|
||||
g.clearRect(old.x-3,old.y-3,old.x+6,old.y+6);
|
||||
g.setColor(1,1,1);
|
||||
g.fillRect(n.x-3,n.y-3,n.x+6,n.y+6);
|
||||
g.setColor(1,0,0);
|
||||
g.clearRect(old.x-3,old.y-3,old.x+6,old.y+6); // clear old marker
|
||||
g.setColor("#0f0");
|
||||
g.fillRect(n.x-3,n.y-3,n.x+6,n.y+6); // draw new marker
|
||||
// draw rings
|
||||
g.setColor("#f00");
|
||||
g.drawCircle(W/2,H/2,W*(1/12));
|
||||
g.drawCircle(W/2,H/2,W*(1/4));
|
||||
g.drawCircle(W/2,H/2,W*(5/12));
|
||||
old = n;
|
||||
});
|
||||
|
||||
setWatch(_=>load(), BTN1);
|
||||
if (global.BTN2) {
|
||||
setWatch(_=>load(), BTN2);
|
||||
setWatch(_=>load(), BTN3);
|
||||
}
|
||||
|
|
|
@ -2,15 +2,37 @@
|
|||
/* Scans for strings that may be in English in each app, and
|
||||
outputs a list of strings that have been found.
|
||||
|
||||
Early work towards internationalisation.
|
||||
See https://github.com/espruino/BangleApps/issues/136
|
||||
See https://github.com/espruino/BangleApps/issues/1311
|
||||
*/
|
||||
|
||||
var IGNORE_STRINGS = [
|
||||
"5x5","6x8","6x8:2","4x6","12x20","6x15","5x9Numeric7Seg", "Vector", // fonts
|
||||
"---","...","*","##","00","GPS","ram",
|
||||
"12hour","rising","falling","title",
|
||||
"sortorder","tl","tr",
|
||||
"function","object", // typeof===
|
||||
"txt", // layout styles
|
||||
"play","stop","pause", // music state
|
||||
];
|
||||
|
||||
var IGNORE_FUNCTION_PARAMS = [
|
||||
"read",
|
||||
"readJSON",
|
||||
"require",
|
||||
"setFont","setUI","setLCDMode",
|
||||
"on",
|
||||
"RegExp","sendCommand",
|
||||
"print","log"
|
||||
];
|
||||
var IGNORE_ARRAY_ACCESS = [
|
||||
"WIDGETS"
|
||||
];
|
||||
|
||||
var BASEDIR = __dirname+"/../";
|
||||
Espruino = require(BASEDIR+"core/lib/espruinotools.js");
|
||||
var fs = require("fs");
|
||||
|
||||
var APPSDIR = BASEDIR+"apps/";
|
||||
|
||||
function ERROR(s) {
|
||||
console.error("ERROR: "+s);
|
||||
process.exit(1);
|
||||
|
@ -18,6 +40,9 @@ function ERROR(s) {
|
|||
function WARN(s) {
|
||||
console.log("Warning: "+s);
|
||||
}
|
||||
function log(s) {
|
||||
console.log(s);
|
||||
}
|
||||
|
||||
var appsFile, apps;
|
||||
try {
|
||||
|
@ -32,38 +57,120 @@ try{
|
|||
}
|
||||
|
||||
// Given a string value, work out if it's obviously not a text string
|
||||
function isNotString(s) {
|
||||
function isNotString(s, wasFnCall, wasArrayAccess) {
|
||||
if (s=="") return true;
|
||||
// wasFnCall is set to the function name if 's' is the first argument to a function
|
||||
if (wasFnCall && IGNORE_FUNCTION_PARAMS.includes(wasFnCall)) return true;
|
||||
if (wasArrayAccess && IGNORE_ARRAY_ACCESS.includes(wasArrayAccess)) return true;
|
||||
if (s=="Storage") console.log("isNotString",s,wasFnCall);
|
||||
|
||||
if (s.length<2) return true; // too short
|
||||
if (s.length>40) return true; // too long
|
||||
if (s[0]=="#") return true; // a color
|
||||
if (s.endsWith(".json") || s.endsWith(".img")) return true; // a filename
|
||||
if (s.endsWith("=")) return true; // probably base64
|
||||
if (s.startsWith("BTN")) return true; // button name
|
||||
if (IGNORE_STRINGS.includes(s)) return true; // one we know to ignore
|
||||
return false;
|
||||
}
|
||||
|
||||
var textStrings = [];
|
||||
function getTextFromString(s) {
|
||||
return s.replace(/^([.<>\-\n ]*)([^<>\!\?]*?)([.<>\!\?\-\n ]*)$/,"$2");
|
||||
}
|
||||
|
||||
console.log("Scanning...");
|
||||
// A string that *could* be translated?
|
||||
var untranslatedStrings = [];
|
||||
// Strings that are marked with 'LANG'
|
||||
var translatedStrings = [];
|
||||
|
||||
function addString(list, str, file) {
|
||||
str = getTextFromString(str);
|
||||
var entry = list.find(e => e.str==str);
|
||||
if (!entry) {
|
||||
entry = { str:str, uses:0, files : [] };
|
||||
list.push(entry);
|
||||
}
|
||||
entry.uses++;
|
||||
if (!entry.files.includes(file))
|
||||
entry.files.push(file)
|
||||
}
|
||||
|
||||
console.log("Scanning apps...");
|
||||
//apps = apps.filter(a=>a.id=="wid_edit");
|
||||
apps.forEach((app,appIdx) => {
|
||||
var appDir = APPSDIR+app.id+"/";
|
||||
app.storage.forEach((file) => {
|
||||
if (!file.url || !file.name.endsWith(".js")) return;
|
||||
var fileContents = fs.readFileSync(appDir+file.url).toString();
|
||||
var filePath = appDir+file.url;
|
||||
var shortFilePath = "apps/"+app.id+"/"+file.url;
|
||||
var fileContents = fs.readFileSync(filePath).toString();
|
||||
var lex = Espruino.Core.Utils.getLexer(fileContents);
|
||||
var lastIdx = 0;
|
||||
var wasFnCall = undefined; // set to 'setFont' if we're at something like setFont(".."
|
||||
var wasArrayAccess = undefined; // set to 'WIDGETS' if we're at something like WIDGETS[".."
|
||||
var tok = lex.next();
|
||||
while (tok!==undefined) {
|
||||
var previousString = fileContents.substring(lastIdx, tok.startIdx);
|
||||
if (tok.type=="STRING") {
|
||||
if (!isNotString(tok.value)) {
|
||||
//console.log(tok.str);
|
||||
if (!textStrings.includes(tok.value))
|
||||
textStrings.push(tok.value);
|
||||
if (previousString.includes("/*LANG*/")) { // translated!
|
||||
addString(translatedStrings, tok.value, shortFilePath);
|
||||
} else { // untranslated - potential to translate?
|
||||
if (!isNotString(tok.value, wasFnCall, wasArrayAccess)) {
|
||||
addString(untranslatedStrings, tok.value, shortFilePath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (tok.value!="(") wasFnCall=undefined;
|
||||
if (tok.value!="[") wasArrayAccess=undefined;
|
||||
}
|
||||
//console.log(wasFnCall,tok.type,tok.value);
|
||||
if (tok.type=="ID") {
|
||||
wasFnCall = tok.value;
|
||||
wasArrayAccess = tok.value;
|
||||
}
|
||||
lastIdx = tok.endIdx;
|
||||
tok = lex.next();
|
||||
}
|
||||
});
|
||||
});
|
||||
console.log("Done");
|
||||
textStrings.sort();
|
||||
console.log(textStrings.join("\n"));
|
||||
untranslatedStrings.sort((a,b)=>a.uses - b.uses);
|
||||
translatedStrings.sort((a,b)=>a.uses - b.uses);
|
||||
|
||||
|
||||
var report = "";
|
||||
|
||||
log("Translated Strings that are not tagged with LANG");
|
||||
log("=================================================================");
|
||||
log("");
|
||||
log("Maybe we should add /*LANG*/ to these automatically?");
|
||||
log("");
|
||||
log(untranslatedStrings.filter(e => translatedStrings.find(t=>t.str==e.str)).map(e=>`${JSON.stringify(e.str)} (${e.files.join(",")})`).join("\n"));
|
||||
log("");
|
||||
//process.exit(1);
|
||||
log("Possible English Strings that could be translated");
|
||||
log("=================================================================");
|
||||
log("");
|
||||
log("Add these to IGNORE_STRINGS if they don't make sense...");
|
||||
log("");
|
||||
// ignore ones only used once or twice
|
||||
log(untranslatedStrings.filter(e => e.uses>2).filter(e => !translatedStrings.find(t=>t.str==e.str)).map(e=>`${JSON.stringify(e.str)} (${e.uses} uses)`).join("\n"));
|
||||
log("");
|
||||
//process.exit(1);
|
||||
|
||||
var languages = JSON.parse(fs.readFileSync(BASEDIR+"/lang/index.json").toString());
|
||||
languages.forEach(language => {
|
||||
if (language.code=="en_GB") {
|
||||
console.log("Ignoring "+language.code);
|
||||
return;
|
||||
}
|
||||
console.log("Scanning "+language.code);
|
||||
log(language.code);
|
||||
log("==========");
|
||||
var translations = JSON.parse(fs.readFileSync(BASEDIR+"/lang/"+language.url).toString());
|
||||
translatedStrings.forEach(str => {
|
||||
if (!translations.GLOBAL[str])
|
||||
console.log(`Missing translation for ${JSON.stringify(str)}`);
|
||||
});
|
||||
log("");
|
||||
});
|
||||
console.log("Done.");
|
||||
|
|
2
core
2
core
|
@ -1 +1 @@
|
|||
Subproject commit 649489412e27ef770bc0c8ed12cfca6a17a98c0d
|
||||
Subproject commit daedea685620abea71c0f876b234fe1dd553d3a2
|
|
@ -141,6 +141,11 @@
|
|||
<input type="checkbox" id="settings-settime">
|
||||
<i class="form-icon"></i> Always update time when we connect
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<select class="form-select form-inline" id="settings-lang" style="width: 10em">
|
||||
<option value="">None (English)</option>
|
||||
</select> <span>Translations (<a href="https://github.com/espruino/BangleApps/issues/1311" target="_blank">BETA - more info</a>)</span>
|
||||
</div>
|
||||
<button class="btn" id="defaultsettings">Default settings</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -14,7 +14,12 @@
|
|||
"Sleep" : "Schlummern",
|
||||
"Alarms" : "Wecker",
|
||||
"New Alarm" : "Neuer Wecker",
|
||||
"ALARM!" : "ALARM!"
|
||||
"ALARM!" : "ALARM!",
|
||||
"Yes" : "Ja",
|
||||
"No" : "Nein",
|
||||
"On" : "Ein",
|
||||
"Off" : "Aus",
|
||||
"Ok" : "OK"
|
||||
},
|
||||
"alarm": {
|
||||
"//":"App-specific overrides",
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"//":"British English language translations - the default strings in apps are all english anyway, so no need to have translations for most things",
|
||||
"GLOBAL": {
|
||||
"//":"Translations that apply for all apps",
|
||||
"//":"Translations that apply for all apps"
|
||||
},
|
||||
"alarm": {
|
||||
"//":"App-specific overrides",
|
||||
"//":"App-specific overrides"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,20 @@
|
|||
"Hours" : "Horas",
|
||||
"Minutes" : "Minutos",
|
||||
"Enabled" : "Activados",
|
||||
"New Alarm" : "Alarma nueva",
|
||||
"Save" : "Grabar",
|
||||
"Back" : "Atrás",
|
||||
"Save" : "Ahorrar",
|
||||
"Back" : "Regresa",
|
||||
"Repeat" : "Repetición",
|
||||
"Delete" : "Borrar",
|
||||
"ALARM!" : "ALARM",
|
||||
"Sleep" : "Dormir"
|
||||
"Sleep" : "Dormir",
|
||||
"Alarms" : "Alarmas",
|
||||
"New Alarm" : "Nueva alarma",
|
||||
"ALARM!" : "ALARM!",
|
||||
"Yes" : "Si",
|
||||
"No" : "No",
|
||||
"On" : "Encendido",
|
||||
"Off" : "Apagado",
|
||||
"Ok" : "OK"
|
||||
},
|
||||
"alarm": {
|
||||
"//":"App-specific overrides",
|
||||
|
|
|
@ -2,20 +2,20 @@
|
|||
"//":"Italian language translations",
|
||||
"GLOBAL": {
|
||||
"//":"Translations that apply for all apps",
|
||||
"Alarms" : "Allarmi",
|
||||
"Alarms" : "Sveglie",
|
||||
"Hours" : "Ore",
|
||||
"Minutes" : "Minuti",
|
||||
"Enabled" : "Attivato",
|
||||
"New Alarm" : "Nuovo allarme",
|
||||
"Save" : "Salvare",
|
||||
"Enabled" : "Attiva",
|
||||
"New Alarm" : "Nuova sveglia",
|
||||
"Save" : "Salva",
|
||||
"Back" : "Indietro",
|
||||
"Repeat" : "Ripetere",
|
||||
"Delete" : "Cancellare",
|
||||
"ALARM!" : "ALARM!",
|
||||
"Sleep" : "Dormire"
|
||||
"Repeat" : "Ripeti",
|
||||
"Delete" : "Cancella",
|
||||
"ALARM!" : "SVEGLIA!",
|
||||
"Sleep" : "Dormi"
|
||||
},
|
||||
"alarm": {
|
||||
"//":"App-specific overrides",
|
||||
"rpt" : "ripetere"
|
||||
"rpt" : "ripeti"
|
||||
}
|
||||
}
|
||||
|
|
42
loader.js
42
loader.js
|
@ -164,6 +164,48 @@ window.addEventListener('load', (event) => {
|
|||
showToast("App Install failed, "+err,"error");
|
||||
});
|
||||
});
|
||||
|
||||
// Load language list
|
||||
httpGet("lang/index.json").then(languagesJSON=>{
|
||||
var languages;
|
||||
try {
|
||||
languages = JSON.parse(languagesJSON);
|
||||
} catch(e) {
|
||||
console.error("lang/index.json Corrupted", e);
|
||||
}
|
||||
|
||||
function reloadLanguage() {
|
||||
LANGUAGE = undefined;
|
||||
if (SETTINGS.language) {
|
||||
var language = languages.find(l=>l.code==SETTINGS.language);
|
||||
if (language) {
|
||||
var langURL = "lang/"+language.url;
|
||||
httpGet(langURL).then(languageJSON=>{
|
||||
try {
|
||||
LANGUAGE = JSON.parse(languageJSON);
|
||||
console.log(`${langURL} loaded successfully`);
|
||||
} catch(e) {
|
||||
console.error(`${langURL} Corrupted`, e);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error(`Language code ${JSON.stringify(SETTINGS.language)} not found in lang/index.json`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var selectLang = document.getElementById("settings-lang");
|
||||
console.log(languages);
|
||||
languages.forEach(lang => {
|
||||
selectLang.innerHTML += `<option value="${lang.code}" ${SETTINGS.language==lang.code?"selected":""}>${lang.name} (${lang.code})</option>`;
|
||||
});
|
||||
selectLang.addEventListener("change",event=>{
|
||||
SETTINGS.language = event.target.value;
|
||||
reloadLanguage();
|
||||
saveSettings();
|
||||
});
|
||||
reloadLanguage();
|
||||
});
|
||||
});
|
||||
|
||||
function onAppJSONLoaded() {
|
||||
|
|
Loading…
Reference in New Issue