diff --git a/apps/2047pp/2047pp.app.js b/apps/2047pp/2047pp.app.js index 58738d04a..9163aaf3a 100644 --- a/apps/2047pp/2047pp.app.js +++ b/apps/2047pp/2047pp.app.js @@ -17,7 +17,7 @@ class TwoK { bh = Math.floor(h/4); bw = Math.floor(w/4); g.clearRect(0, 0, g.getWidth()-1, yo).setFontAlign(0, 0, 0); - g.setFont("Vector", 16).setColor("#fff").drawString("Score:"+this.score.toString(), g.getWidth()/2, 8); + g.setFont("Vector", 16).setColor(g.theme.fg).drawString("Score:"+this.score.toString(), g.getWidth()/2, 8); this.drawBRect(xo-3, yo-3, xo+w+2, yo+h+2, 4, "#a88", "#caa", false); for (y=0; y<4; ++y) for (x=0; x<4; ++x) { diff --git a/apps/2047pp/ChangeLog b/apps/2047pp/ChangeLog new file mode 100644 index 000000000..a1f88e5ec --- /dev/null +++ b/apps/2047pp/ChangeLog @@ -0,0 +1,2 @@ +0.01: New app! +0.02: Better support for watch themes diff --git a/apps/2047pp/metadata.json b/apps/2047pp/metadata.json index f0fd6c1e3..033354ac6 100644 --- a/apps/2047pp/metadata.json +++ b/apps/2047pp/metadata.json @@ -2,7 +2,7 @@ "name": "2047pp", "shortName":"2047pp", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "Bangle version of a tile shifting game", "supports" : ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, diff --git a/apps/kbswipe/ChangeLog b/apps/kbswipe/ChangeLog index 5560f00bc..87fb43d3d 100644 --- a/apps/kbswipe/ChangeLog +++ b/apps/kbswipe/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Now keeps user input trace intact by changing how the screen is updated. diff --git a/apps/kbswipe/lib.js b/apps/kbswipe/lib.js index 51f92f510..7837a6984 100644 --- a/apps/kbswipe/lib.js +++ b/apps/kbswipe/lib.js @@ -45,11 +45,39 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); var flashToggle = false; const R = Bangle.appRect; + var Rx1; + var Rx2; + var Ry1; + var Ry2; + + function findMarker(strArr) { + if (strArr.length == 0) { + Rx1 = 4; + Rx2 = 6*4; + Ry1 = 8*4; + Ry2 = 8*4 + 3; + } else if (strArr.length <= 4) { + Rx1 = (strArr[strArr.length-1].length)%7*6*4 + 4 ; + Rx2 = (strArr[strArr.length-1].length)%7*6*4 + 6*4; + Ry1 = (strArr.length)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4); + Ry2 = (strArr.length)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + 3; + } else { + Rx1 = (strArr[strArr.length-1].length)%7*6*4 + 4 ; + Rx2 = (strArr[strArr.length-1].length)%7*6*4 + 6*4; + Ry1 = (4)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4); + Ry2 = (4)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + 3; + } + //print(Rx1,Rx2,Ry1, Ry2); + return {x:Rx1,y:Ry1,x2:Rx2,y2:Ry2}; + } function draw(noclear) { g.reset(); - if (!noclear) g.clearRect(R); - var l = g.setFont("6x8:4").wrapString(text+(flashToggle?"_":" "), R.w-8); + var l = g.setFont("6x8:4").wrapString(text+' ', R.w-8); + if (!l) l = []; + //print(text+':'); + //print(l); + if (!noclear) (flashToggle?(g.fillRect(findMarker(l))):(g.clearRect(findMarker(l)))); if (l.length>4) l=l.slice(-4); g.drawString(l.join("\n"),R.x+4,R.y+4); } @@ -80,6 +108,7 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); var ch = o.stroke; if (ch=="\b") text = text.slice(0,-1); else text += ch; + g.clearRect(R); } flashToggle = true; draw(); @@ -87,7 +116,7 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); Bangle.on('stroke',strokeHandler); g.reset().clearRect(R); show(); - draw(true); + draw(false); var flashInterval; return new Promise((resolve,reject) => { diff --git a/apps/kbswipe/metadata.json b/apps/kbswipe/metadata.json index 635841e62..f1e7cf7d6 100644 --- a/apps/kbswipe/metadata.json +++ b/apps/kbswipe/metadata.json @@ -1,6 +1,6 @@ { "id": "kbswipe", "name": "Swipe keyboard", - "version":"0.01", + "version":"0.02", "description": "A library for text input via PalmOS style swipe gestures (beta!)", "icon": "app.png", "type":"textinput", diff --git a/apps/locale/locales.js b/apps/locale/locales.js index 06e959954..7a2665170 100644 --- a/apps/locale/locales.js +++ b/apps/locale/locales.js @@ -662,7 +662,7 @@ var locales = { thousands_sep: " ", currency_symbol: "kr", int_curr_symbol: "NOK", - speed: "kmh", + speed: "kmt", distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "", 1: "" }, diff --git a/apps/noteify/ChangeLog b/apps/noteify/ChangeLog new file mode 100644 index 000000000..ec66c5568 --- /dev/null +++ b/apps/noteify/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/noteify/README.md b/apps/noteify/README.md new file mode 100644 index 000000000..d3868efcf --- /dev/null +++ b/apps/noteify/README.md @@ -0,0 +1,20 @@ +# WARNING + +This app uses the [Scheduler library](https://banglejs.com/apps/?id=sched) and requires a keyboard such as [Swipe keyboard](https://banglejs.com/apps/?id=kbswipe). + +## Usage + +* Select "New note" and use the onscreen keyboard to type. +* Hit back button to exit back to the main menu. New notes are added to the main menu. If you don't type anything and you hit the back button, no new note will be saved. +* Selecting a note from the main menu will allow you to edit, delete, or change the position of the note (1 being the top of the list). +* By selecting "set as alarm" or "set as timer", you can also use this note as a custom message for alerts from alarms and timers. Once you hit save, the alarm or timer is set. +* Any alarms or timers you set will appear under "edit alarms/timers." If the alarm/timer is set to a note, the note will appear on the top of the menu. If an alarm/timer is set without a custom message, it will simply say Alarm or Timer on the top of the menu. +* On the alarm/timer alert, only the first 30 characters of the note will appear - any more and you run the risk of pushing the sleep/ok buttons off-screen. + +## Images + +![](menu.png) + +![](note.png) + +![](timer-alert.png) diff --git a/apps/noteify/app-icon.js b/apps/noteify/app-icon.js new file mode 100644 index 000000000..7d23b9d9b --- /dev/null +++ b/apps/noteify/app-icon.js @@ -0,0 +1 @@ +E.toArrayBuffer(atob("MDCEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u3d3d3d3d3d3d3d3d3d3u7gAAAAAAAO7u3d3d3d3d3d3d3d3d3d3u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u3d3d3d3d3d3d3d3d3d3u7gAAAAAAAO7u3d3d3d3d3d3d3d3d3d3u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u3d3d3d3d3d3u7u7u7u7u7gAAAAAAAO7u3d3d3d3d3d3u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7/////7gAAAAAAAO7u7u7u7u7u7u7u7u7////+4AAAAAAAAO7u7u7u7u7u7u7u7u7////uAAAAAAAAAO7u7u7u7u7u7u7u7u7///7gAAAAAAAAAO7u7u7u7u7u7u7u7u7//+4AAAAAAAAAAO7u7u7u7u7u7u7u7u7//uAAAAAAAAAAAO7u7u7u7u7u7u7u7u7/7gAAAAAAAAAAAO7u7u7u7u7u7u7u7u7+4AAAAAAAAAAAAO7u7u7u7u7u7u7u7u7uAAAAAAAAAAAAAO7u7u7u7u7u7u7u7u7gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==")) diff --git a/apps/noteify/app.js b/apps/noteify/app.js new file mode 100644 index 000000000..c19694ea4 --- /dev/null +++ b/apps/noteify/app.js @@ -0,0 +1,304 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var notes = require("Storage").readJSON("noteify.json", true) || []; +var alarms = require("sched").getAlarms(); +msg = ""; + +function startNote(idx) { + idx == undefined ? note = "" : note = notes[idx].note; + require("textinput").input({text:note}).then(result => { + if (result != "") { + idx == undefined ? notes.push({"note" : result}) : notes[idx].note = result; + require("Storage").write("noteify.json",JSON.stringify(notes)); + } + showMainMenu(); + }); +} + +function viewNote(idx) { + var textY = 30; + var textBound = g.stringMetrics(g.setColor(g.theme.fg).setFont("6x8:2").setFontAlign(-1, -1).drawString(g.wrapString(notes[idx].note, g.getWidth()).join("\n"), 0, textY)).height; + Bangle.setUI({mode:"custom", drag:e=>{ + textY += e.dy; + g.setClipRect(0, 30, g.getWidth(), g.getHeight()); + if (textY > 30) textY = 30; + if (textY < textBound) textY = textBound; + g.clearRect(0, 30, g.getWidth(), g.getHeight()).setColor(g.theme.fg).setFont("6x8:2").setFontAlign(-1, -1).drawString(g.wrapString(notes[idx].note, g.getWidth()).join("\n"), 0, textY); + },back:()=>{ + Bangle.setUI(); + showEditMenu(idx); + }}); + +} + +function showMainMenu() { + var mainMenu = { + "" : { "title" : "Noteify" }, + "< Back" : function() { load(); }, + "New note" : function() { + E.showMenu(); + startNote(); + }, + "Edit alarms/timers" : function() { showAlarmMenu(); }, + }; + + notes.forEach((a, idx) => { + mainMenu[notes[idx].note.length > 12 ? notes[idx].note.substring(0, 12)+"..." : notes[idx].note] = function () { showEditMenu(idx);}; + }); + msg = ""; + E.showMenu(mainMenu); +} + +function showEditMenu(idx) { + var moveNote = notes[idx].note; + var editMenu = { + "" : { "title" : notes[idx].note.length > 12 ? notes[idx].note.replace(/\n/g, " ").substring(0, 12)+"..." : notes[idx].note.replace(/\n/g, " ") }, + "View note" : function() { + E.showMenu(); + viewNote(idx); + }, + "Edit note" : function() { + E.showMenu(); + startNote(idx); + }, + "Delete note" : function() { + notes.splice(idx,1); + require("Storage").write("noteify.json",JSON.stringify(notes)); + showMainMenu(); + }, + "Set as alarm" : function() { + //limit alarm msg to 30 chars + msg = moveNote.substring(0, 30); + editAlarm(-1); + }, + "Set as timer" : function () { + msg = moveNote.substring(0, 30); + editTimer(-1); + }, + "Change position" : { + value : idx+1, + min : 1, + max : notes.length, + wrap : true, + onchange : function(v) { + //save changes from change position + if (v-1 != idx) { + notes.splice(v-1, 0, notes.splice(idx, 1)[0]); + require("Storage").write("noteify.json",JSON.stringify(notes)); + } + }, + }, + "< Back" : function() { + showMainMenu(); + }, + }; + E.showMenu(editMenu); +} + +function decodeTime(t) { + t = 0|t; // sanitise + var hrs = 0|(t/3600000); + return { hrs : hrs, mins : Math.round((t-hrs*3600000)/60000) }; +} + +// time in { hrs, mins } -> ms +function encodeTime(o) { + return o.hrs*3600000 + o.mins*60000; +} + +function formatTime(t) { + var o = decodeTime(t); + return o.hrs+":"+("0"+o.mins).substr(-2); +} + +function getCurrentTime() { + var time = new Date(); + return ( + time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000 + ); +} + +function saveAndReload() { + require("sched").setAlarms(alarms); + require("sched").reload(); +} + +function showAlarmMenu() { + const menu = { + '': { 'title': 'Alarm/Timer' }, + '< Back' : ()=>{showMainMenu();}, + 'New Alarm': ()=>editAlarm(-1), + 'New Timer': ()=>editTimer(-1) + }; + alarms.forEach((alarm,idx)=>{ + var type,txt; // a leading space is currently required (JS error in Espruino 2v12) + if (alarm.timer) { + type = /*LANG*/"Timer"; + txt = " "+formatTime(alarm.timer); + } else { + type = /*LANG*/"Alarm"; + txt = " "+formatTime(alarm.t); + } + if (alarm.rp) txt += "\0"+atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA="); + // rename duplicate alarms + if (menu[type+txt]) { + var n = 2; + while (menu[type+" "+n+txt]) n++; + txt = type+" "+n+txt; + } else txt = type+txt; + // add to menu + menu[txt] = { + value : "\0"+atob(alarm.on?"EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g":"EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"), + onchange : function() { + if (alarm.timer) editTimer(idx, alarm); + else editAlarm(idx, alarm); + } + }; + }); + if (WIDGETS["alarm"]) WIDGETS["alarm"].reload(); + return E.showMenu(menu); +} + +function editDOW(dow, onchange) { + const menu = { + '': { 'title': 'Days of Week' }, + '< Back' : () => onchange(dow) + }; + for (var i = 0; i < 7; i++) (i => { + var dayOfWeek = require("locale").dow({ getDay: () => i }); + menu[dayOfWeek] = { + value: !!(dow&(1< v ? "Yes" : "No", + onchange: v => v ? dow |= 1< 12) ? a.msg.replace(/\n/g, " ").substring(0, 12)+"..." : msg.replace(/\n/g, " ").substring(0, 12)+"..."; + + const menu = { + '': { 'title': alarmTitle }, + '< Back' : () => showAlarmMenu(), + 'Days': { + value: "SMTWTFS".split("").map((d,n)=>a.dow&(1< editDOW(a.dow, d=>{a.dow=d;editAlarm(alarmIndex,a)}) + }, + 'Hours': { + value: t.hrs, min : 0, max : 23, wrap : true, + onchange: v => t.hrs=v + }, + 'Minutes': { + value: t.mins, min : 0, max : 59, wrap : true, + onchange: v => t.mins=v + }, + 'Enabled': { + value: a.on, + format: v=>v?"On":"Off", + onchange: v=>a.on=v + }, + 'Repeat': { + value: a.rp, + format: v=>v?"Yes":"No", + onchange: v=>a.rp=v + }, + 'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ), + 'Auto snooze': { + value: a.as, + format: v=>v?"Yes":"No", + onchange: v=>a.as=v + } + }; + menu["Save"] = function() { + a.t = encodeTime(t); + a.last = (a.t < getCurrentTime()) ? (new Date()).getDate() : 0; + a.last = 0; + if (newAlarm) alarms.push(a); + else alarms[alarmIndex] = a; + saveAndReload(); + showMainMenu(); + }; + if (!newAlarm) { + menu["Delete"] = function() { + alarms.splice(alarmIndex,1); + saveAndReload(); + showMainMenu(); + }; + } + return E.showMenu(menu); +} + +function editTimer(alarmIndex, alarm) { + var newAlarm = alarmIndex<0; + var a = { + timer : 5*60*1000, // 5 minutes + on : true, + rp : false, + as : false, + dow : 0b1111111, + last : 0, + vibrate : ".." + }; + if (msg != "") a["msg"] = msg; + if (!newAlarm) Object.assign(a, alarms[alarmIndex]); + if (alarm) Object.assign(a,alarm); + var t = decodeTime(a.timer); + + var timerTitle = (a.msg == undefined) ? 'Timer' : (a.msg.length > 12) ? a.msg.replace(/\n/g, " ").substring(0, 12)+"..." : msg.replace(/\n/g, " ").substring(0, 12)+"..."; + + const menu = { + '': { 'title': timerTitle }, + '< Back' : () => showMainMenu(), + 'Hours': { + value: t.hrs, min : 0, max : 23, wrap : true, + onchange: v => t.hrs=v + }, + 'Minutes': { + value: t.mins, min : 0, max : 59, wrap : true, + onchange: v => t.mins=v + }, + 'Enabled': { + value: a.on, + format: v=>v?"On":"Off", + onchange: v=>a.on=v + }, + 'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ), + }; + menu["Save"] = function() { + a.timer = encodeTime(t); + a.t = getCurrentTime() + a.timer; + if (newAlarm) alarms.push(a); + else alarms[alarmIndex] = a; + saveAndReload(); + showMainMenu(); + }; + if (!newAlarm) { + menu["Delete"] = function() { + alarms.splice(alarmIndex,1); + saveAndReload(); + showMainMenu(); + }; + } + return E.showMenu(menu); +} + +showMainMenu(); diff --git a/apps/noteify/app.png b/apps/noteify/app.png new file mode 100644 index 000000000..2e1855069 Binary files /dev/null and b/apps/noteify/app.png differ diff --git a/apps/noteify/menu.png b/apps/noteify/menu.png new file mode 100644 index 000000000..66988d1f6 Binary files /dev/null and b/apps/noteify/menu.png differ diff --git a/apps/noteify/metadata.json b/apps/noteify/metadata.json new file mode 100644 index 000000000..bedff0e5b --- /dev/null +++ b/apps/noteify/metadata.json @@ -0,0 +1,22 @@ +{ + "id": "noteify", + "name": "Noteify", + "version": "0.01", + "description": "Write notes using an onscreen keyboard and use them as custom messages for alarms or timers.", + "icon": "app.png", + "tags": "tool,alarm", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"noteify.app.js","url":"app.js"}, + {"name":"noteify.img","url":"app-icon.js","evaluate":true}, + {"name":"noteify.wid.js","url":"widget.js"} + ], + "data": [{"name":"noteify.json"}], + "dependencies": {"scheduler":"type","textinput":"type"}, + "screenshots": [ + {"url": "menu.png"}, + {"url": "note.png"}, + {"url": "timer-alert.png"} + ] +} diff --git a/apps/noteify/note.png b/apps/noteify/note.png new file mode 100644 index 000000000..ab8f172e4 Binary files /dev/null and b/apps/noteify/note.png differ diff --git a/apps/noteify/timer-alert.png b/apps/noteify/timer-alert.png new file mode 100644 index 000000000..01b4b8f17 Binary files /dev/null and b/apps/noteify/timer-alert.png differ diff --git a/apps/noteify/widget.js b/apps/noteify/widget.js new file mode 100644 index 000000000..052ac9ebd --- /dev/null +++ b/apps/noteify/widget.js @@ -0,0 +1,8 @@ +WIDGETS["alarm"]={area:"tl",width:0,draw:function() { + if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y); + },reload:function() { + // don't include library here as we're trying to use as little RAM as possible + WIDGETS["alarm"].width = (require('Storage').readJSON('sched.json',1)||[]).some(alarm=>alarm.on&&(alarm.hidden!==false)) ? 24 : 0; + } +}; +WIDGETS["alarm"].reload(); diff --git a/apps/openwind/ChangeLog b/apps/openwind/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/openwind/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/openwind/README.md b/apps/openwind/README.md new file mode 100644 index 000000000..1df7ea158 --- /dev/null +++ b/apps/openwind/README.md @@ -0,0 +1,22 @@ +# OpenWind + +Receive and display data from a wireless [OpenWind](https://www.openwind.de/) sailing wind instrument on the Bangle. + +## Usage + +Upon startup, the app will attempt to automatically connect to the wind instrument. This typically only takes a few seconds. + +## Features + +The app displays the apparent wind direction (via a green dot) and speed (green numbers, in knots) relative to the mounting direction of the wind vane. +If "True wind" is enabled in settings and a GPS fix is available, the true wind speed and direction (relative to the mounting direction of the vane) is +additionally displayed in red. In this mode, the speed over ground in knots is also shown at the bottom left of the screen. + +## Controls + +There are no controls in the main app, but there are two settings in the settings app that can be changed: + + * True wind: enables or disables true wind calculations; enabling this will turn on GPS inside the app + * Mounting angle: mounting relative to the boat of the wind instrument (in degrees) + +![](openwind_screenshot.png) diff --git a/apps/openwind/app-icon.js b/apps/openwind/app-icon.js new file mode 100644 index 000000000..b86738955 --- /dev/null +++ b/apps/openwind/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4A/AH4A/AH4AzhMJF94wtF+QwsF/4APnAACF54wZFoYxNF7guHGBQv0GCwuJGBIvFACov/AD4vvd6Yv/GCoumGIwtpAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHoA==")) diff --git a/apps/openwind/app.js b/apps/openwind/app.js new file mode 100644 index 000000000..b1c8fea4b --- /dev/null +++ b/apps/openwind/app.js @@ -0,0 +1,113 @@ +OW_CHAR_UUID = '0000cc91-0000-1000-8000-00805f9b34fb'; +require("Font7x11Numeric7Seg").add(Graphics); +gatt = {}; +cx = g.getWidth()/2; +cy = 24+(g.getHeight()-24)/2; +w = (g.getWidth()-24)/2; + +gps_course = { spd: 0 }; + +var settings = require("Storage").readJSON('openwindsettings.json', 1) || {}; + +i = 0; +hullpoly = []; +for (y=-1; y<=1; y+=0.1) { + hullpoly[i++] = cx - (y<0 ? 1+y*0.15 : (Math.sqrt(1-0.7*y*y)-Math.sqrt(0.3))/(1-Math.sqrt(0.3)))*w*0.3; + hullpoly[i++] = cy - y*w*0.7; +} +for (y=1; y>=-1; y-=0.1) { + hullpoly[i++] = cx + (y<0 ? 1+y*0.15 : (Math.sqrt(1-0.7*y*y)-Math.sqrt(0.3))/(1-Math.sqrt(0.3)))*w*0.3; + hullpoly[i++] = cy - y*w*0.7; +} + +function wind_updated(ev) { + if (ev.target.uuid == "0xcc91") { + awa = settings.mount_angle-ev.target.value.getInt16(1, true)*0.1; + aws = ev.target.value.getInt16(3, true)*0.01; +// console.log(awa, aws); + if (gps_course.spd > 0) { + wv = { // wind vector (in fixed reference frame) + lon: Math.sin(Math.PI*(gps_course.course+awa)/180)*aws, + lat: Math.cos(Math.PI*(gps_course.course+awa)/180)*aws + }; + twv = { lon: wv.lon+gps_course.lon, lat: wv.lat+gps_course.lat }; + tws = Math.sqrt(Math.pow(twv.lon,2)+Math.pow(twv.lat, 2)); + twa = Math.atan2(twv.lat, twv.lon)*180/Math.PI-gps_course.course; + if (twa<0) twa += 360; + if (twa>360) twa -=360; + } + else { + tws = -1; + twa = 0; + } + draw_compass(awa,aws,twa,tws); + } +} + +function draw_compass(awa, aws, twa, tws) { + g.clearRect(0, 24, g.getWidth()-1, g.getHeight()-1); + fh = w*0.15; + g.setColor(0, 0, 1).fillPoly(hullpoly); + g.setFontVector(fh).setColor(g.theme.fg); + g.setFontAlign(0, 0, 0).drawString("0", cx, 24+fh/2); + g.setFontAlign(0, 0, 1).drawString("90", g.getWidth()-12-fh, cy); + g.setFontAlign(0, 0, 2).drawString("180", cx, g.getHeight()-fh/2); + g.setFontAlign(0, 0, 3).drawString("270", 12+fh/2, cy); + for (i=0; i<4; ++i) { + a = i*Math.PI/2+Math.PI/4; + g.drawLineAA(cx+Math.cos(a)*w*0.85, cy+Math.sin(a)*w*0.85, cx+Math.cos(a)*w*0.99, cy+Math.sin(a)*w*0.99); + } + g.setColor(0, 1, 0).fillCircle(cx+Math.sin(Math.PI*awa/180)*w*0.9, cy+Math.cos(Math.PI*awa/180)*w*0.9, w*0.1); + if (tws>0) g.setColor(1, 0, 0).fillCircle(cx+Math.sin(Math.PI*twa/180)*w*0.9, cy+Math.cos(Math.PI*twa/180)*w*0.9, w*0.1); + g.setColor(0, 1, 0).setFont("7x11Numeric7Seg",w*0.06); + g.setFontAlign(0, 0, 0).drawString(aws.toFixed(1), cx, cy-0.32*w); + if (tws>0) g.setColor(1, 0, 0).drawString(tws.toFixed(1), cx, cy+0.32*w); + if (settings.truewind && typeof gps_course.spd!=='undefined') { + spd = gps_course.spd/1.852; + g.setColor(g.theme.fg).setFont("7x11Numeric7Seg", w*0.03).setFontAlign(-1, 1, 0).drawString(spd.toFixed(1), 1, g.getHeight()-1); + } +} + +function parseDevice(d) { + device = d; + console.log("Found device"); + device.gatt.connect().then(function(ga) { + console.log("Connected"); + gatt = ga; + return ga.getPrimaryService("cc90"); +}).then(function(s) { + return s.getCharacteristic("cc91"); +}).then(function(c) { + c.on('characteristicvaluechanged', (event)=>wind_updated(event)); + return c.startNotifications(); +}).then(function() { + console.log("Done!"); +}).catch(function(e) { + console.log("ERROR"+e); +});} + +function connection_setup() { + NRF.setScan(); + NRF.setScan(parseDevice, { filters: [{services:["cc90"]}], timeout: 2000}); + console.log("Scanning for OW sensor"); +} + +if (settings.truewind) { + Bangle.on('GPS',function(fix) { + if (fix.fix && fix.satellites>3 && fix.speed>2) { // only uses fixes w/ more than 3 sats and speed > 2kph + gps_course = + { lon: Math.sin(Math.PI*fix.course/180)*fix.speed/1.852, + lat: Math.cos(Math.PI*fix.course/180)*fix.speed/1.852, + spd: fix.speed, + course: fix.course + }; + } + else gps_course.spd = -1; + }); + Bangle.setGPSPower(1, "app"); +} + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw_compass(0, 0, 0, 0); +connection_setup(); diff --git a/apps/openwind/app.png b/apps/openwind/app.png new file mode 100644 index 000000000..9fd64efba Binary files /dev/null and b/apps/openwind/app.png differ diff --git a/apps/openwind/metadata.json b/apps/openwind/metadata.json new file mode 100644 index 000000000..9229f7f25 --- /dev/null +++ b/apps/openwind/metadata.json @@ -0,0 +1,15 @@ +{ "id": "openwind", + "name": "OpenWind", + "shortName":"OpenWind", + "version":"0.01", + "description": "OpenWind", + "icon": "openwind.png", + "readme": "README.md", + "tags": "ble,outdoors,gps,sailing", + "supports" : ["BANGLEJS", "BANGLEJS2"], + "storage": [ + {"name":"openwind.app.js","url":"app.js"}, + {"name":"openwind.img","url":"app-icon.js","evaluate":true}, + {"name":"openwind.settings.js", "url":"settings.js"} + ] +} diff --git a/apps/openwind/openwind.png b/apps/openwind/openwind.png new file mode 100644 index 000000000..9fd64efba Binary files /dev/null and b/apps/openwind/openwind.png differ diff --git a/apps/openwind/openwind_screenshot.png b/apps/openwind/openwind_screenshot.png new file mode 100644 index 000000000..05143e8a4 Binary files /dev/null and b/apps/openwind/openwind_screenshot.png differ diff --git a/apps/openwind/settings.js b/apps/openwind/settings.js new file mode 100644 index 000000000..a7e3a1abe --- /dev/null +++ b/apps/openwind/settings.js @@ -0,0 +1,44 @@ +// This file should contain exactly one function, which shows the app's settings +/** + * @param {function} back Use back() to return to settings menu + */ +const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off"; +(function(back) { + const SETTINGS_FILE = 'openwindsettings.json' + // initialize with default settings... + let settings = { + 'truewind': false, + 'mount_angle': 0 + } + // ...and overwrite them with any saved values + // This way saved values are preserved if a new version adds more settings + const storage = require('Storage') + const saved = storage.readJSON(SETTINGS_FILE, 1) || {} + for (const key in saved) { + settings[key] = saved[key]; + } + // creates a function to safe a specific setting, e.g. save('color')(1) + function save(key) { + return function (value) { + settings[key] = value; + storage.write(SETTINGS_FILE, settings); + } + } + const menu = { + '': { 'title': 'OpenWind' }, + '< Back': back, + 'True wind': { + value: settings.truewind, + format: boolFormat, + onchange: save('truewind'), + }, + 'Mounting angle': { + value: settings.mount_angle, + min: 0, + max: 355, + step: 5, + onchange: save('mount_angle'), + } + } + E.showMenu(menu); +}) diff --git a/apps/sched/ChangeLog b/apps/sched/ChangeLog index b90f7ebbc..a2d6c370f 100644 --- a/apps/sched/ChangeLog +++ b/apps/sched/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Fix scheduling of other alarms if there is a pending alarm from the past (fix #1667) 0.03: Fix `getTimeToAlarm` for a timer already used at same day, don't set `last` for timers. +0.04: Fix `getTimeToAlarm` to check for next dow if alarm.t lower currentTime. diff --git a/apps/sched/lib.js b/apps/sched/lib.js index d55a05475..48094c86f 100644 --- a/apps/sched/lib.js +++ b/apps/sched/lib.js @@ -37,9 +37,9 @@ exports.setAlarm = function(id, alarm) { exports.getTimeToAlarm = function(alarm, time) { if (!alarm) return undefined; if (!time) time = new Date(); - var active = alarm.on && (alarm.dow>>time.getDay())&1 && (!alarm.date || alarm.date==time.toISOString().substr(0,10)); - if (!active) return undefined; var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000); + var active = alarm.on && (alarm.dow>>((time.getDay()+(alarm.t { + translations.GLOBAL[translationItem.str] = translationItem.str; + resolve() + })) } } }); diff --git a/lang/index.json b/lang/index.json index 2a9ecfd42..341ddd6d1 100644 --- a/lang/index.json +++ b/lang/index.json @@ -21,5 +21,6 @@ {"code":"pl_PL","name":"Polish","url":"pl_PL.json"}, {"code":"ro_RO","name":"Romanian","url":"ro_RO.json"}, {"code":"sk_SK","name":"Slovak","url":"sk_SK.json"}, - {"code":"sl_SL","name":"Slovenian","url":"sl_SL.json"} + {"code":"sl_SL","name":"Slovenian","url":"sl_SL.json"}, + {"code":"nn_NO","name":"Norwegian (Nynorsk)","url":"nn_NO.json"} ] diff --git a/lang/nn_NO.json b/lang/nn_NO.json new file mode 100644 index 000000000..176d82b48 --- /dev/null +++ b/lang/nn_NO.json @@ -0,0 +1,244 @@ +{ + "//": "Norwegian nynorsk language translations", + "GLOBAL": { + "//": "Translations that apply for all apps", + "Alarms": "Alarmar", + "Hours": "Timar", + "Minutes": "Minutt", + "Enabled": "Slått på", + "New Alarm": "Ny alarm", + "Save": "Lagre", + "Back": "Tilbake", + "Repeat": "Gjentaking", + "Delete": "Slett", + "ALARM!": "ALARM!", + "Sleep": "Søvn", + "circle 3": "sirkel 3", + "circle 1": "sirkel 1", + "music": "musikk", + "week": "veke", + "Keep Msgs": "Behald meldingar", + "Auto snooze": "Automatisk slumring", + "step length": "steglengde", + "Circle": "Sirkel", + "data": "data", + "colorize icon": "fargelegg ikon", + "min. confidence": "min. tillit", + "show widgets": "vis widget", + "valid period": "gyldi periode", + "Heartrate": "Puls", + "distance goal": "mål for distanse", + "circle 4": "sirkel 4", + "circle count": "antall sirklar", + "minimum": "minimum", + "maximum": "maksimum", + "New Timer": "Ny nedteljing", + "battery warn": "batteriåtvaring", + "heartrate": "puls", + "circle 2": "sirkel 2", + "(repeat)": "(gjenta)", + "weather circle": "værsirkel", + "Delete All Messages": "Slett alle meldingar", + "No Messages": "Ingen meldingar", + "Show clocks": "Visa klokker", + "STEPS": "STEG", + "TAP right top/bottom": "TRYKK oppe/nede til høgre", + "View Message": "Vis melding", + "Mark Unread": "Marker ulest", + "Are you sure": "Er du sikker", + "Delete all messages": "Slett alle meldingar", + "Record Run": "Rekordlaup", + "Unread timer": "Ulest nedteljing", + "Vibration": "Vibrering", + "Utils": "Verkty", + "Quiet Mode": "Stille modus", + "Passkey BETA": "Passord BETA", + "Dark BW": "Mørk BW", + "BTNs 1:startlap 2:exit 3:reset": "BTN 1:start 2:avslutt 3:nullstill", + "start&lap/reset, BTN1: EXIT": "start&runde/nullstill, BTN1: AVSLUTT", + "BLE": "BLE", + "Programmable": "Programmerbar", + "Launcher Settings": "Innstillingar for oppstartsprogram", + "Vector font size": "Storleik for vektorskrifttype", + "Font": "Skrifttype", + "Yes\ndefinitely": "Ja\ndefinitivt", + "App Source\nNot found": "App-kjelde\nikkje funnet", + "Make Connectable": "Gjer mogleg å kople til", + "HID": "HID", + "Bluetooth": "Bluetooth", + "Apps": "Appar", + "Piezo": "Piezo", + "LCD": "LCD", + "Foreground 2": "Forgrunn 2", + "Light BW": "Lys BW", + "Background": "Bakgrunn", + "Remove": "Fjern", + "Highlight BG": "Marker BG", + "Customize": "Tilpass", + "Highlight FG": "Marker FG", + "Background 2": "Bakgrunn 2", + "LCD Brightness": "Lyusstyrke på LCD-skjermen", + "Add Device": "Legg til eining", + "Wake on BTN1": "Vakne ved KNAPP1", + "Wake on BTN2": "Vakne ved KNAPP2", + "Twist Timeout": "Tidsavbrot for vridning", + "Wake on Touch": "Vakne ved berøring", + "LCD Timeout": "LCD tidsavbrot", + "Foreground": "Forgrunn", + "Connect device\nto add to\nwhitelist": "Kople til eining\nfor å leggje til\ni lista", + "Wake on FaceUp": "Vakne på FaceUp", + "Twist Threshold": "Terskel for vridning", + "Wake on BTN3": "Vakne på BTN3", + "Clock Style": "Klokkestil", + "Time Zone": "Tidssone", + "Twist Max Y": "Vridning Max Y", + "Stay Connectable": "Opne for tilkopling", + "This will remove everything": "Dette vil fjerne alt", + "Turn Off": "Slå av", + "Connectable": "Kan koplast til", + "Flattening battery - this can take hours.\nLong-press button to cancel": "Flatar ut batteriet, dette kan ta fleire timar.\nHald inne knappen for å avbryte", + "Reset to Defaults": "Nullstill", + "Utilities": "Verkty", + "Flatten Battery": "Flat ut batteriet", + "Debug Info": "Feilsøkjingsinfo.", + "Reset Settings": "Nullstill innstillingar", + "Wake on Twist": "Vakne ved vridning", + "Compact Storage": "Trykk saman lagring", + "Log": "Logg", + "Rewrite Settings": "Omskriving av innstillingar", + "Compacting...\nTakes approx\n1 minute": "Trykkar saman lagring...\nTek ca.\n1 minutt", + "Storage": "Lagring", + "Second": "Sekund", + "App Settings": "App-innstillingar", + "Invalid settings": "Ugyldige innstillingar", + "Minute": "Minutt", + "Sleep Phase Alarm": "Søvnfase-alarm", + "No app has settings": "Ingen appar har innstillingar", + "Hour": "Time", + "No Clocks Found": "Fant inga klokke", + "Date": "Dato", + "Month": "Månad", + "Alarm": "Alarm", + "Reset": "Nullstill", + "Reset all widgets": "Nullstill alle widget", + "TIMER": "TIMAR", + "on": "på", + "OFF": "AV", + "Side": "Side", + "Sort Order": "Sortering", + "Left": "Venstre", + "Right": "Høgre", + "Reset All": "Nullstill alle", + "Widgets": "Widget", + "goal": "mål", + "Vibrate": "Vibrer", + "Message": "Melding", + "Beep": "Lag lyd", + "Disable": "Slå av", + "Select Clock": "Vel klokke", + "Locale": "Språk", + "Alerts": "Varslingar", + "System": "System", + "Set Time": "Still tid", + "Factory Reset": "Nullstill til fabrikkinnstillingar", + "Messages": "Meldingar", + "Timer": "Nedteljing", + "BACK": "TILLBAKE", + "Error in settings": "Feil i innstillingar", + "Whitelist": "Tillatelsesliste", + "ALARM": "ALARM", + "Hide": "Skjul", + "Connected": "Kopla til", + "Show": "Vis", + "On": "På", + "Ok": "Ok", + "No": "Nei", + "Settings": "Innstillingar", + "steps": "steg", + "back": "tilbake", + "Steps": "Steg", + "Year": "År", + "Yes": "Ja", + "Loading": "Lastar", + "Music": "Musikk", + "color": "farge", + "off": "av", + "Off": "Av", + "Theme": "Drakt", + "Select App": "Vel App", + "No Apps Found": "Fant inga appar", + "Days of Week": "Vekedagar", + "Days": "Dagar", + "ALTITUDE (m)": "HØGDE (m)", + "ZERO": "NULL", + "No tokens": "Ingea token", + "Not supported": "Ikkje støtta", + "weather data": "vêrdata", + "Uncalibrated\nturn 360° around": "Ikkje kalibrert\nsnu 360° grader", + "RESET": "NULLSTILL", + "Mark all read": "Marker alle som lest", + "Min Font": "Minste skriftstorleik", + "Small": "Liten", + "Medium": "Medium", + "Auto-Open Music": "Opne musikk automatisk", + "Unlock Watch": "Lås opp klokke", + "Flash Icon": "Blink Ikon", + "Silent": "Stille", + "Exit": "Avslutt", + "Current Mode": "Gjeldande modus", + "Switch Theme": "Byt drakt", + "Edit Schedule": "Rediger tidsplan", + "Switch to": "Byt til", + "No apps": "Ingen appar", + "Recorder": "Ta opp", + "RECORD": "TA OPP", + "File #": "Fil #", + "View Tracks": "Sjå spor", + "Time Period": "Tidsperiode", + "Tracks": "Spor", + "No Tracks found": "Fant inga spor", + "Erase": "Slett", + "Delete Track": "Slett spor", + "Drawing": "Teikning", + "Altitude (m)": "Høgd (m)", + "Speed (m/s)": "Hastigheit (m/s)", + "Notifications": "Varsel", + "Snooze": "Slumre", + "settings": "innstillingar", + "Show date": "vis dato", + "locale": "språk", + "M": "M", + "m.Y #W": "m.Y #W", + "today": "i dag", + "Border": "Omriss", + "show": "vis", + "Color": "Farge", + "Marker": "Marker", + "circle": "sirkel", + "rectangle": "rektangel", + "Connection\nlost": "Mista\ntilkopling", + "Calculating": "Reknar ut", + "Add Schedule": "Legg til tidsskjema", + "LCD Settings": "LCD-innstillingar", + "Today settings": "Innstillingar for i dag", + "Cancel": "Avbryt", + "red": "raud", + "green": "grøn", + "blue": "blå", + "Track": "Spor", + "none": "inga", + "Plot Map": "Plott Kart", + "Plot OpenStMap": "Plott OpenStMap", + "Plot Alt": "Plott Høgde", + "Plot Speed": "Plott Hastigheit", + "Dist Pattern": "Avstandsmønster", + "Step Pattern": "Stegmønster", + "Time Pattern": "Tidsmønster", + "Boxes": "Bokstar", + "Start wday": "Start vdag", + "Su color": "Su farge", + "filled": "fylt", + "Mrk.Color": "Mrk.Farge", + "Mrk.Size": "Mrk.Storleik" + } +}