diff --git a/apps/gpsinfo/ChangeLog b/apps/gpsinfo/ChangeLog index 414b9d9fb..5bb531bc7 100644 --- a/apps/gpsinfo/ChangeLog +++ b/apps/gpsinfo/ChangeLog @@ -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 diff --git a/apps/gpsinfo/gps-info.js b/apps/gpsinfo/gps-info.js index f4521f265..0eca2ccf5 100644 --- a/apps/gpsinfo/gps-info.js +++ b/apps/gpsinfo/gps-info.js @@ -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(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13)); - SATinView = nofBD + nofGP; + 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)); + 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; + } } diff --git a/apps/gpsinfo/metadata.json b/apps/gpsinfo/metadata.json index 0bbcc0f06..60bd90c03 100644 --- a/apps/gpsinfo/metadata.json +++ b/apps/gpsinfo/metadata.json @@ -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", diff --git a/apps/launch/ChangeLog b/apps/launch/ChangeLog index 0b2f134ad..ceb0177da 100644 --- a/apps/launch/ChangeLog +++ b/apps/launch/ChangeLog @@ -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 diff --git a/apps/launch/app-bangle1.js b/apps/launch/app-bangle1.js deleted file mode 100644 index f779f5de4..000000000 --- a/apps/launch/app-bangle1.js +++ /dev/null @@ -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.nameb.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 (selectedn+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{ - 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); -}); diff --git a/apps/launch/app-bangle2.js b/apps/launch/app.js similarity index 90% rename from apps/launch/app-bangle2.js rename to apps/launch/app.js index 156eecdf4..42aba1bb9 100644 --- a/apps/launch/app-bangle2.js +++ b/apps/launch/app.js @@ -63,8 +63,11 @@ E.showScroller({ } }); -// pressing button goes back -setWatch(_=>load(), BTN1, {edge:"falling"}); +// 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 diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index dba05915b..1abd519ab 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -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.13: Improved battery visualization. \ No newline at end of file +0.12: Added humidity as an option to display. +0.13: Improved battery visualization. +0.14: Added altitude as an option to display. \ No newline at end of file diff --git a/apps/lcars/README.md b/apps/lcars/README.md index 46e134f78..2b86921e5 100644 --- a/apps/lcars/README.md +++ b/apps/lcars/README.md @@ -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 ![](screenshot.png) ![](screenshot_2.png) ## 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). diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 3b9745a7a..81a501481 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -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()){ diff --git a/apps/lcars/lcars.settings.js b/apps/lcars/lcars.settings.js index ba630799a..076dea4d1 100644 --- a/apps/lcars/lcars.settings.js +++ b/apps/lcars/lcars.settings.js @@ -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]; diff --git a/apps/lcars/metadata.json b/apps/lcars/metadata.json index 6882a2e4e..2d04ebdf6 100644 --- a/apps/lcars/metadata.json +++ b/apps/lcars/metadata.json @@ -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.", diff --git a/apps/messages/app.js b/apps/messages/app.js index 80e4a3244..3e692a0cc 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -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 }:{}, ]}, ]}, diff --git a/apps/messages/settings.js b/apps/messages/settings.js index fd8ce8f39..c865a37fb 100644 --- a/apps/messages/settings.js +++ b/apps/messages/settings.js @@ -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) }, }; diff --git a/apps/pparrot/ChangeLog b/apps/pparrot/ChangeLog index 5560f00bc..a7262b0c9 100644 --- a/apps/pparrot/ChangeLog +++ b/apps/pparrot/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Bangle.js 2 compatibility diff --git a/apps/pparrot/metadata.json b/apps/pparrot/metadata.json index 493181e72..9aaeb94ac 100644 --- a/apps/pparrot/metadata.json +++ b/apps/pparrot/metadata.json @@ -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": [ diff --git a/apps/pparrot/party-parrot.js b/apps/pparrot/party-parrot.js index a62b88bc9..4d5dcf9b5 100644 --- a/apps/pparrot/party-parrot.js +++ b/apps/pparrot/party-parrot.js @@ -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; diff --git a/apps/setting/settings.js b/apps/setting/settings.js index a32b83d3c..65e076753 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -221,7 +221,7 @@ function showThemeMenu() { m.draw(); } var m = E.showMenu({ - '':{title:'Theme'}, + '':{title:/*LANG*/'Theme'}, '< Back': ()=>showSystemMenu(), /*LANG*/'Dark BW': ()=>{ upd({ diff --git a/apps/slevel/ChangeLog b/apps/slevel/ChangeLog index 3a6431e50..e77ca4e8b 100644 --- a/apps/slevel/ChangeLog +++ b/apps/slevel/ChangeLog @@ -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 diff --git a/apps/slevel/metadata.json b/apps/slevel/metadata.json index ad7d0f1c3..2a8223498 100644 --- a/apps/slevel/metadata.json +++ b/apps/slevel/metadata.json @@ -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": [ diff --git a/apps/slevel/screenshot.png b/apps/slevel/screenshot.png new file mode 100644 index 000000000..e945414ac Binary files /dev/null and b/apps/slevel/screenshot.png differ diff --git a/apps/slevel/spiritlevel.js b/apps/slevel/spiritlevel.js index 9db54b825..dd6d56bdc 100644 --- a/apps/slevel/spiritlevel.js +++ b/apps/slevel/spiritlevel.js @@ -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) { @@ -12,21 +13,26 @@ 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); +} diff --git a/bin/language_scan.js b/bin/language_scan.js index 2a92fded1..89af7a12b 100755 --- a/bin/language_scan.js +++ b/bin/language_scan.js @@ -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."); diff --git a/core b/core index 649489412..daedea685 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 649489412e27ef770bc0c8ed12cfca6a17a98c0d +Subproject commit daedea685620abea71c0f876b234fe1dd553d3a2 diff --git a/index.html b/index.html index e22a1f9e7..7e818ee72 100644 --- a/index.html +++ b/index.html @@ -141,6 +141,11 @@ Always update time when we connect +
+   Translations (BETA - more info) +
diff --git a/lang/de_DE.json b/lang/de_DE.json index 80d0e74bb..b4acfe675 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -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", diff --git a/lang/en_GB.json b/lang/en_GB.json index e85fe8029..6bf1279d1 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -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" } } diff --git a/lang/es_ES.json b/lang/es_ES.json index 0671c4ab8..a3e7ede3f 100644 --- a/lang/es_ES.json +++ b/lang/es_ES.json @@ -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", diff --git a/lang/it_IT.json b/lang/it_IT.json index 184c80238..96293cbf3 100644 --- a/lang/it_IT.json +++ b/lang/it_IT.json @@ -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" } } diff --git a/loader.js b/loader.js index a0c280634..0355ea89c 100644 --- a/loader.js +++ b/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 += ``; + }); + selectLang.addEventListener("change",event=>{ + SETTINGS.language = event.target.value; + reloadLanguage(); + saveSettings(); + }); + reloadLanguage(); + }); }); function onAppJSONLoaded() {