diff --git a/apps.json b/apps.json index 0826bbf81..c1f51f272 100644 --- a/apps.json +++ b/apps.json @@ -77,7 +77,7 @@ { "id": "messages", "name": "Messages", - "version": "0.11", + "version": "0.12", "description": "App to display notifications from iOS and Gadgetbridge", "icon": "app.png", "type": "app", @@ -92,13 +92,14 @@ {"name":"messages","url":"lib.js"} ], "data": [{"name":"messages.json"},{"name":"messages.settings.json"}], + "screenshots": [{"url":"screenshot.png"},{"url":"screenshot-notify.gif"}], "sortorder": -9 }, { "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.04", + "version": "0.05", "description": "Display notifications/music/etc from Gadgetbridge on Android. This replaces the old Gadgetbridge widget.", "icon": "app.png", "tags": "tool,system,messages,notifications", @@ -115,7 +116,7 @@ { "id": "ios", "name": "iOS Integration", - "version": "0.06", + "version": "0.07", "description": "Display notifications/music/etc from iOS devices", "icon": "app.png", "tags": "tool,system,ios,apple,messages,notifications", @@ -217,7 +218,7 @@ { "id": "locale", "name": "Languages", - "version": "0.13", + "version": "0.14", "description": "Translations for different countries", "icon": "locale.png", "type": "locale", @@ -2167,14 +2168,15 @@ { "id": "snek", "name": "The snek game", "shortName":"Snek", - "version": "0.01", + "version": "0.02", "description": "A snek game where you control a snek to eat all the apples!", - "icon": "snek-icon.js", + "screenshots": [{"url":"screenshot_snek.png"}], + "icon": "snek.png", "supports": ["BANGLEJS2"], "tags": "game,fun", "storage": [ {"name":"snek.app.js","url":"snek.js"}, - {"name":"snek.img","url":"snek-icon.js","evaluate":true} + {"name":"snek.img","url":"snek.icon.js","evaluate":true} ] }, { @@ -3294,7 +3296,7 @@ { "id": "dtlaunch", "name": "Desktop Launcher", - "version": "0.05", + "version": "0.07", "description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.", "screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}], "icon": "icon.png", @@ -3305,8 +3307,11 @@ "storage": [ {"name":"dtlaunch.app.js","url":"app-b1.js", "supports": ["BANGLEJS"]}, {"name":"dtlaunch.app.js","url":"app-b2.js", "supports": ["BANGLEJS2"]}, + {"name":"dtlaunch.settings.js","url":"settings-b1.js", "supports": ["BANGLEJS"]}, + {"name":"dtlaunch.settings.js","url":"settings-b2.js", "supports": ["BANGLEJS2"]}, {"name":"dtlaunch.img","url":"app-icon.js","evaluate":true} - ] + ], + "data": [{"name":"dtlaunch.json"}] }, { "id": "HRV", @@ -4008,11 +4013,11 @@ { "id": "thermom", "name": "Thermometer", - "version": "0.03", + "version": "0.04", "description": "Displays the current temperature in degree Celsius, updated every 20 seconds", "icon": "app.png", "tags": "tool", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS", "BANGLEJS2"], "allow_emulator": true, "storage": [ {"name":"thermom.app.js","url":"app.js"}, @@ -4734,7 +4739,7 @@ { "id": "pooqroman", "name": "pooq Roman watch face", "shortName":"pooq Roman", - "version":"0.02", + "version":"0.03", "description": "A classic watch face with a certain dynamicity. Most amusing in 24h mode. Slide up to show more hands, down for less(!). By design does not support standard widgets, sorry!", "icon": "app.png", "type": "clock", @@ -4870,7 +4875,7 @@ "id": "rebble", "name": "Rebble Clock", "shortName": "Rebble", - "version": "0.01", + "version": "0.02", "description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion", "readme": "README.md", "icon": "rebble.png", @@ -4931,5 +4936,40 @@ {"name":"awairmonitor.app.js","url":"app.js"}, {"name":"awairmonitor.img","url":"app-icon.js","evaluate":true} ] + }, + { "id": "pooqround", + "name": "pooq Round watch face", + "shortName":"pooq Round", + "version":"0.00", + "description": "A 24 hour analogue watchface with high legibility and a novel style.", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "allow_emulator":true, + "readme": "README.md", + "storage": [ + {"name":"pooqround.app.js","url":"app.js"}, + {"name":"pooqround.img","url":"app-icon.js","evaluate":true} + ], + "data": [ + {"name":"pooqround.json"} + ] + }, + { + "id": "coretemp", + "name": "Core Temp Display", + "version": "0.01", + "description": "Display CoreTemp device sensor data", + "icon": "coretemp.png", + "type": "app", + "tags": "health", + "readme": "README.md", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"coretemp.boot.js","url":"boot.js"}, + {"name":"coretemp.app.js","url":"coretemp.js"}, + {"name":"coretemp.img","url":"coretemp-icon.js","evaluate":true} + ] } ] diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index 35fa0e386..c2c4ea6be 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -3,3 +3,4 @@ Fix music control 0.03: Handling of message actions (ok/clear) 0.04: Android icon now goes to settings page with 'find phone' +0.05: Fix handling of message actions diff --git a/apps/android/boot.js b/apps/android/boot.js index 97e3a5641..59ffe006d 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -65,7 +65,7 @@ // Message response Bangle.messageResponse = (msg,response) => { if (msg.id=="call") return gbSend({ t: "call", n:response?"ACCEPT":"REJECT" }); - if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS" }); + if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id }); // error/warn here? }; })(); diff --git a/apps/awairmonitor/README.md b/apps/awairmonitor/README.md index 8d6e25633..69894fea2 100644 --- a/apps/awairmonitor/README.md +++ b/apps/awairmonitor/README.md @@ -14,6 +14,8 @@ Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awai ![](screenshot.png) +![](awair-monitor-photo.jpg) + ## Creator [@alainsaas](https://github.com/alainsaas) diff --git a/apps/awairmonitor/awair-monitor-photo.jpg b/apps/awairmonitor/awair-monitor-photo.jpg new file mode 100644 index 000000000..8b62faa24 Binary files /dev/null and b/apps/awairmonitor/awair-monitor-photo.jpg differ diff --git a/apps/coretemp/ChangeLog b/apps/coretemp/ChangeLog new file mode 100644 index 000000000..c7b309a74 --- /dev/null +++ b/apps/coretemp/ChangeLog @@ -0,0 +1 @@ +0.1: New app diff --git a/apps/coretemp/README.md b/apps/coretemp/README.md new file mode 100644 index 000000000..fac25df21 --- /dev/null +++ b/apps/coretemp/README.md @@ -0,0 +1,20 @@ +# CoreTemp display + +Basic bare-bones example of connecting to a bluetooth [CoreTemp](https://corebodytemp.com/) device and displaying the current body core temperature readings. + +## Usage + +On startup connects to a CoreTemp device (1809/2A1C) and emits a "Core, temp" value for each reading. +The app simply displays these readings on screen. + +## TODO + +* Integrate with other tracking/sports apps to log data. +* Add device selection +* Provide enable/disable option +* Check status, add Retry/reconnect +* Also provide skin temp reading + +## Creator + +Ivor Hewitt diff --git a/apps/coretemp/boot.js b/apps/coretemp/boot.js new file mode 100644 index 000000000..59e227dad --- /dev/null +++ b/apps/coretemp/boot.js @@ -0,0 +1,23 @@ +(function() { + var gatt; + + //Would it be better to scan by uuid rather than name? + NRF.requestDevice({ timeout: 20000, filters: [{ name: 'CORE [a]' }] }).then(function(device) { + return device.gatt.connect(); + }).then(function(g) { + gatt = g; + return gatt.getPrimaryService("1809"); + }).then(function(service) { + return service.getCharacteristic("2A1C"); + }).then(function(characteristic) { + characteristic.on('characteristicvaluechanged', function(event) { + var dv = event.target.value; + var core = (dv.buffer[2]*256+dv.buffer[1])/100; + Bangle.emit('Core',{ + temp:core + }); + }); + return characteristic.startNotifications(); + }).then(function() { + }); +})(); diff --git a/apps/coretemp/coretemp-icon.js b/apps/coretemp/coretemp-icon.js new file mode 100644 index 000000000..5f36b9090 --- /dev/null +++ b/apps/coretemp/coretemp-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///k0DxUFgsDCY8KwAfJlQLHhWglWq1WgBIcCA4QCB1WoComq0+iBYWqCwl//4OBAAQxChWlv/2BYIlCBYUqv9VvQLBwA9BBYWlqtV/QLBGoRIBgQLBr9aBYQ2BBYMKroLBtQLCgALClIKC1AXG1NVuoFBF4sC09V+woCBAJHCgWXq9oPQZrDgWdq9gBZG9rqgCTwSbCgVVqysDBYkK6tWYoa/DkEJ6vaaIgWBaAILCbQhUCBYXoc4wNBBZWqBfBtB1ALKKZILCR4J3FToQLBU4KPEWoQLNZYILIa4NVcYReEcYOnqtaDAbvDgALBcg4EBlNVqtqDoOgd4YoBBYNWytWCwQdCgQLBAAVaBYkA0oLDuwLFkv1BgZGDAAMJuoKCroWEGAOnDAVftShGr////1tDdG14LB+wiEAAdqHAjTHBYgA==")) diff --git a/apps/coretemp/coretemp.js b/apps/coretemp/coretemp.js new file mode 100644 index 000000000..226508c83 --- /dev/null +++ b/apps/coretemp/coretemp.js @@ -0,0 +1,19 @@ +Bangle.setLCDPower(1); +Bangle.setLCDTimeout(0); +var btm = g.getHeight()-1; + +function onCore(c) { + var px = g.getWidth()/2; + g.setFontAlign(0,0); + g.clearRect(0,24,g.getWidth(),80); + var str = c.temp + "C"; + g.setFontVector(40).drawString(str,px,45); +} +Bangle.on('Core', onCore); + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +g.reset().setFont("6x8",2).setFontAlign(0,0); +g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16); diff --git a/apps/coretemp/coretemp.png b/apps/coretemp/coretemp.png new file mode 100644 index 000000000..a573828f8 Binary files /dev/null and b/apps/coretemp/coretemp.png differ diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog index c3102b4b9..c414c1ddc 100644 --- a/apps/dtlaunch/ChangeLog +++ b/apps/dtlaunch/ChangeLog @@ -3,3 +3,5 @@ 0.03: cycle thru pages 0.04: reset to clock after 2 mins of inactivity 0.05: add Bangle 2 version +0.06: Adds settings page (hide clocks or launchers) +0.06: Adds setting for directly launching app on touch for Bangle 2 diff --git a/apps/dtlaunch/app-b1.js b/apps/dtlaunch/app-b1.js index 9bbf3e219..ec0569127 100644 --- a/apps/dtlaunch/app-b1.js +++ b/apps/dtlaunch/app-b1.js @@ -2,6 +2,11 @@ * */ +var settings = Object.assign({ + showClocks: true, + showLaunchers: true, +}, require('Storage').readJSON("dtlaunch.json", true) || {}); + function wdog(handle,timeout){ if(handle !== undefined){ wdog.handle = handle; @@ -17,7 +22,13 @@ function wdog(handle,timeout){ wdog(load,120000) 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)); +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" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type)); + apps.sort((a,b)=>{ var n=(0|a.sortorder)-(0|b.sortorder); if (n) return n; // do sortorder first diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js index 674fe3677..800ec456c 100644 --- a/apps/dtlaunch/app-b2.js +++ b/apps/dtlaunch/app-b2.js @@ -2,8 +2,20 @@ * */ +var settings = Object.assign({ + showClocks: true, + showLaunchers: true, + direct: false, +}, require('Storage').readJSON("dtlaunch.json", true) || {}); + 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)); +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" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type)); + apps.sort((a,b)=>{ var n=(0|a.sortorder)-(0|b.sortorder); if (n) return n; // do sortorder first @@ -28,7 +40,7 @@ const YOFF = 30; function draw_icon(p,n,selected) { var x = (n%2)*72+XOFF; var y = n>1?72+YOFF:YOFF; - (selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+10,y+2,x+60,y+52); + (selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52); g.clearRect(x+12,y+4,x+59,y+51); g.setColor(g.theme.fg); try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){} @@ -52,7 +64,7 @@ function drawPage(p){ } for (var i=0;i<4;i++) { if (!apps[p*4+i]) return i; - draw_icon(p,i,selected==i); + draw_icon(p,i,selected==i && !settings.direct); } g.flip(); } @@ -81,9 +93,9 @@ Bangle.on("touch",(_,p)=>{ for (i=0;i<4;i++){ if((page*4+i)=0) { - if (selected!=i){ + draw_icon(page,i,true && !settings.direct); + if (selected>=0 || settings.direct) { + if (selected!=i && !settings.direct){ draw_icon(page,selected,false); } else { load(apps[page*4+i].src); diff --git a/apps/dtlaunch/settings-b1.js b/apps/dtlaunch/settings-b1.js new file mode 100644 index 000000000..f3101da16 --- /dev/null +++ b/apps/dtlaunch/settings-b1.js @@ -0,0 +1,33 @@ +(function(back) { + var FILE = "dtlaunch.json"; + + var settings = Object.assign({ + showClocks: true, + showLaunchers: true + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + E.showMenu({ + "" : { "title" : "Desktop launcher" }, + "< Back" : () => back(), + 'Show clocks': { + value: settings.showClocks, + format: v => v?"On":"Off", + onchange: v => { + settings.showClocks = v; + writeSettings(); + } + }, + 'Show launchers': { + value: settings.showLaunchers, + format: v => v?"On":"Off", + onchange: v => { + settings.showLaunchers = v; + writeSettings(); + } + } + }); +}) diff --git a/apps/dtlaunch/settings-b2.js b/apps/dtlaunch/settings-b2.js new file mode 100644 index 000000000..7f667d213 --- /dev/null +++ b/apps/dtlaunch/settings-b2.js @@ -0,0 +1,42 @@ +(function(back) { + var FILE = "dtlaunch.json"; + + var settings = Object.assign({ + showClocks: true, + showLaunchers: true, + direct: false + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + E.showMenu({ + "" : { "title" : "Desktop launcher" }, + "< Back" : () => back(), + 'Show clocks': { + value: settings.showClocks, + format: v => v?"On":"Off", + onchange: v => { + settings.showClocks = v; + writeSettings(); + } + }, + 'Show launchers': { + value: settings.showLaunchers, + format: v => v?"On":"Off", + onchange: v => { + settings.showLaunchers = v; + writeSettings(); + } + }, + 'Direct launch': { + value: settings.direct, + format: v => v?"On":"Off", + onchange: v => { + settings.direct = v; + writeSettings(); + } + } + }); +}) diff --git a/apps/ios/ChangeLog b/apps/ios/ChangeLog index 28ad78dec..5e60068aa 100644 --- a/apps/ios/ChangeLog +++ b/apps/ios/ChangeLog @@ -4,4 +4,7 @@ 0.04: Added common bundleId's 0.05: Added more bundleId's (app-id's which can be used to determine a friendly app name in the notifications) -0.06: Fix (not) popupping up old messages \ No newline at end of file +0.06: Fix (not) popupping up old messages +0.07: Added more details from music (instead of Undefined) + Added more app identifiers + diff --git a/apps/ios/README.md b/apps/ios/README.md new file mode 100644 index 000000000..b4c2c6ac9 --- /dev/null +++ b/apps/ios/README.md @@ -0,0 +1,31 @@ +# iOS integration app + +This is the iOS integration app for Bangle.js. This app allows you to receive +notifications from your iPhone. The Apple Notification Center Service (ANCS) +sends all the messages to your watch. + +You can allow this if you connect your Bangle to your iPhone. It will be +prompted for immediatly after you connect the Bangle to the iPhone. + +### Connecting your Bangle(2).js to your iPhone +The Bangle watches are Bluetooth Low Energy (BLE) devices. Sometimes they +will not be seen/detected by the Bluetooth scanner in your iPhone settings +menu. + +To resolve this, you can download numerous apps who can actually scan +for BLE devices. There are great ones out there, free and paid. + +We really like WebBLE, which we also recommend to load apps on your +watch with your iOS device, as Safari does not support WebBluetooth +for now. It's just a few bucks/pounds/euro's. + +If you like to try a free app first, you can always use NRF Toolbox or +Bluetooth BLE Device Finder to find and connect your Bangle. + +## Requests + +Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=ios%20app + +## Creator + +Gordon Williams diff --git a/apps/ios/boot.js b/apps/ios/boot.js index d402facbb..8ccfb617d 100644 --- a/apps/ios/boot.js +++ b/apps/ios/boot.js @@ -65,14 +65,16 @@ E.on('notify',msg=>{ "com.apple.facetime": "FaceTime", "com.apple.mobilecal": "Calendar", "com.apple.mobilemail": "Mail", + "com.apple.mobilephone": "Phone", "com.apple.MobileSMS": "SMS Message", "com.apple.Passbook": "iOS Wallet", + "com.apple.podcasts": "Podcasts", "com.apple.reminders": "Reminders", "com.apple.shortcuts": "Shortcuts", "com.atebits.Tweetie2": "Twitter", "com.burbn.instagram" : "Instagram", "com.facebook.Facebook": "Facebook", - "com.facebook.Messenger": "FB Messenger", + "com.facebook.Messenger": "Messenger", "com.google.Chromecast" : "Google Home", "com.google.Gmail" : "GMail", "com.google.hangouts" : "Hangouts", @@ -81,22 +83,26 @@ E.on('notify',msg=>{ "com.ifttt.ifttt" : "IFTTT", "com.jumbo.app" : "Jumbo", "com.linkedin.LinkedIn" : "LinkedIn", + "com.microsoft.Office.Outlook" : "Outlook Mail", "com.nestlabs.jasper.release" : "Nest", "com.netflix.Netflix" : "Netflix", "com.reddit.Reddit" : "Reddit", "com.skype.skype": "Skype", "com.skype.SkypeForiPad": "Skype", "com.spotify.client": "Spotify", + "com.strava.stravaride": "Strava", "com.tinyspeck.chatlyio": "Slack", "com.toyopagroup.picaboo": "Snapchat", "com.ubercab.UberClient": "Uber", "com.ubercab.UberEats": "UberEats", + "com.vilcsak.bitcoin2": "Coinbase", "com.wordfeud.free": "WordFeud", "com.zhiliaoapp.musically": "TikTok", "net.whatsapp.WhatsApp": "WhatsApp", "nl.ah.Appie": "Albert Heijn", "nl.postnl.TrackNTrace": "PostNL", "ph.telegra.Telegraph": "Telegram", + "tv.twitch": "Twitch", // could also use NRF.ancsGetAppInfo(msg.appId) here }; @@ -104,7 +110,7 @@ E.on('notify',msg=>{ '2019':"'" }; var replacer = ""; //(n)=>print('Unknown unicode '+n.toString(16)); - if (appNames[msg.appId]) msg.a + //if (appNames[msg.appId]) msg.a require("messages").pushMessage({ t : msg.event, id : msg.uid, @@ -122,9 +128,10 @@ E.on('AMS',a=>{ function push(m) { var msg = { t : "modify", id : "music", title:"Music" }; if (a.id=="artist") msg.artist = m; - else if (a.id=="album") msg.artist = m; - else if (a.id=="title") msg.tracl = m; - else return; // duration? need to reformat + else if (a.id=="album") msg.album = m; + else if (a.id=="title") msg.track = m; + else if (a.id=="duration") msg.dur = m; + else return; require("messages").pushMessage(msg); } if (a.truncated) NRF.amsGetMusicInfo(a.id).then(push) diff --git a/apps/locale/ChangeLog b/apps/locale/ChangeLog index 509d67077..448f8119a 100644 --- a/apps/locale/ChangeLog +++ b/apps/locale/ChangeLog @@ -13,3 +13,4 @@ 0.11: Added translations for nl_NL and changes one formatting 0.12: Fixed nl_NL formatting, because the full months won't fit on the Bangle.js2's screen 0.13: Now use shorter de_DE date format to more closely match other languages for size +0.14: Added some first translations for Messages in nl_NL diff --git a/apps/locale/locales.js b/apps/locale/locales.js index 2e1429ef8..1d659f161 100644 --- a/apps/locale/locales.js +++ b/apps/locale/locales.js @@ -198,7 +198,8 @@ var locales = { day: "zondag,maandag,dinsdag,woensdag,donderdag,vrijdag,zaterdag", abmonth: "jan,feb,mrt,apr,mei,jun,jul,aug,sep,okt,nov,dec", month: "januari,februari,maart,april,mei,juni,juli,augustus,september,oktober,november,december", - trans: { yes: "ja", Yes: "Ja", no: "nee", No: "Nee", ok: "ok", on: "aan", off: "uit", "< Back": "< Terug" } + trans: { yes: "ja", Yes: "Ja", no: "nee", No: "Nee", ok: "ok", on: "aan", off: "uit", + "< Back": "< Terug", "Delete": "Verwijderen", "Mark Unread": "Markeer als ongelezen" } }, "en_NL": { // English date units with Dutch number, currency and navigation units. lang: "en_NL", diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index a3a4f377c..6109daf7a 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -13,4 +13,8 @@ 0.09: Message now disappears after 60s if no action taken and clock loads (fix 922) Fix phone icon (#1014) 0.10: Respect the 'new' attribute if it was set from iOS integrations -0.11: Open app when touching the widget (Bangle.js 2 only) \ No newline at end of file +0.11: Open app when touching the widget (Bangle.js 2 only) +0.12: Extra app-specific notification icons + New animated notifcationicon (instead of large blinking 'MESSAGES') + Added screenshots + diff --git a/apps/messages/README.md b/apps/messages/README.md index e9aa128d1..4952b1877 100644 --- a/apps/messages/README.md +++ b/apps/messages/README.md @@ -1,7 +1,5 @@ # Messages app -**THIS APP IS CURRENTLY BETA** - This app handles the display of messages and message notifications. It stores a list of currently received messages and allows them to be listed, viewed, and responded to. @@ -17,7 +15,17 @@ and `Messages`: * `Repeat` - How often should buzzes repeat - the default of 4 means the Bangle will buzz every 4 seconds * `Unread Timer` - when a new message is received we go into the Messages app. If there is no user input for this amount of time then the app will exit and return -to the clock where `MESSAGES` will be shown in the Widget bar. +to the clock where a ringing bell will be shown in the Widget bar. + +## Images +_1. Screenshot of a notification_ + +![](screenshot.png) + +_2. What the notify icon looks like (it's touchable on Bangle.js2!)_ + +![](screenshot-notify.gif) + ## Requests @@ -27,3 +35,11 @@ Please file any issues on https://github.com/espruino/BangleApps/issues/new?titl ## Creator Gordon Williams + +## Contributors + +[Jeroen Peters](https://github.com/jeroenpeters1986) + +## Attributions + +Icons used in this app are from https://icons8.com diff --git a/apps/messages/app.js b/apps/messages/app.js index c609acb4b..965c50b85 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -65,6 +65,12 @@ function saveMessages() { function getBackImage() { return atob("FhYBAAAAEAAAwAAHAAA//wH//wf//g///BwB+DAB4EAHwAAPAAA8AADwAAPAAB4AAHgAB+AH/wA/+AD/wAH8AA=="); } +function getNotificationImage() { + return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A=="); +} +function getFBIcon() { + return atob("GBiBAAAAAAAAAAAYAAD/AAP/wAf/4A/48A/g8B/g+B/j+B/n+D/n/D8A/B8A+B+B+B/n+A/n8A/n8Afn4APnwADnAAAAAAAAAAAAAA=="); +} function getPosImage() { return atob("GRSBAAAAAYAAAcAAAeAAAfAAAfAAAfAAAfAAAfAAAfBgAfA4AfAeAfAPgfAD4fAA+fAAP/AAD/AAA/AAAPAAADAAAA=="); } @@ -74,18 +80,28 @@ function getNegImage() { function getMessageImage(msg) { if (msg.img) return atob(msg.img); var s = (msg.src||"").toLowerCase(); + if (s=="calendar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA=="); + if (s=="facebook") return getFBIcon(); + if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); + if (s=="instagram") return atob("GBiBAf////////////////wAP/n/n/P/z/f/b/eB7/c87/d+7/d+7/d+7/d+7/c87/eB7/f/7/P/z/n/n/wAP////////////////w=="); + if (s=="gmail") return getNotificationImage(); + if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA=="); + if (s=="mail") return getNotificationImage(); + if (s=="messenger") return getFBIcon(); + if (s=="outlook mail") return getNotificationImage(); if (s=="phone") return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA="); if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA=="); - if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); - if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA=="); - if (s=="telegram") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA=="); + if (s=="slack") return atob("GBiBAAAAAAAAAABAAAHvAAHvAADvAAAPAB/PMB/veD/veB/mcAAAABzH8B3v+B3v+B3n8AHgAAHuAAHvAAHvAADGAAAAAAAAAAAAAA=="); + if (s=="sms message") return getNotificationImage(); if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA"); + if (s=="telegram") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA=="); + if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA=="); + if (s=="wordfeud") return atob("GBgCWqqqqqqlf//////9v//////+v/////++v/////++v8///Lu+v8///L++v8///P/+v8v//P/+v9v//P/+v+fx/P/+v+Pk+P/+v/PN+f/+v/POuv/+v/Ofdv/+v/NvM//+v/I/Y//+v/k/k//+v/i/w//+v/7/6//+v//////+v//////+f//////9Wqqqqqql"); if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); if (msg.id=="back") return getBackImage(); - return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A=="); + return getNotificationImage(); } - function showMapMessage(msg) { var m; var distance, street, target, eta; @@ -127,7 +143,7 @@ function showMapMessage(msg) { function showMusicMessage(msg) { function fmtTime(s) { var m = Math.floor(s/60); - s = (s%60).toString().padStart(2,0); + s = (parseInt(s%60)).toString().padStart(2,0); return m+":"+s; } @@ -141,7 +157,7 @@ function showMusicMessage(msg) { {type:"h", fillx:1, bgCol:colBg, c: [ { type:"btn", src:getBackImage, cb:back }, { type:"v", fillx:1, c: [ - { type:"txt", font:fontLarge, label:msg.artist, pad:2 }, + { type:"txt", font:fontMedium, label:msg.artist, pad:2 }, { type:"txt", font:fontMedium, label:msg.album, pad:2 } ]} ]}, @@ -223,7 +239,7 @@ function showMessage(msgid) { var body = (lines.length>4) ? lines.slice(0,4).join("\n")+"..." : lines.join("\n"); layout = new Layout({ type:"v", c: [ {type:"h", fillx:1, bgCol:colBg, c: [ - { type:"btn", src:getMessageImage(msg), cb:()=>{ + { type:"btn", src:getMessageImage(msg), pad: 3, cb:()=>{ cancelReloadTimeout(); // don't auto-reload to clock now showMessageSettings(msg); }}, diff --git a/apps/messages/screenshot-notify.gif b/apps/messages/screenshot-notify.gif new file mode 100644 index 000000000..3d0ed0b32 Binary files /dev/null and b/apps/messages/screenshot-notify.gif differ diff --git a/apps/messages/screenshot.png b/apps/messages/screenshot.png new file mode 100644 index 000000000..a95045400 Binary files /dev/null and b/apps/messages/screenshot.png differ diff --git a/apps/messages/widget.js b/apps/messages/widget.js index 6403c6b8d..f01d22ec7 100644 --- a/apps/messages/widget.js +++ b/apps/messages/widget.js @@ -2,9 +2,8 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() { Bangle.removeListener('touch', this.touch); if (!this.width) return; var c = (Date.now()-this.t)/1000; - g.reset().setBgColor((c&1) ? "#0f0" : "#030").setColor((c&1) ? "#000" : "#fff"); - g.clearRect(this.x,this.y,this.x+this.width,this.y+23); - g.setFont("6x8:1x2").setFontAlign(0,0).drawString("MESSAGES", this.x+this.width/2, this.y+12); + g.reset().clearRect(this.x,this.y,this.x+this.width,this.y+23); + g.drawImage((c&1) ? atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+DAADDAADDAADDwAPD8A/DOBzDDn/DA//DAHvDAPvjAPvjAPvjAPvh///gf/vAAD+AAB8AAAAA==") : atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+D///D///A//8CP/xDj/HD48DD+B8D/D+D/3vD/vvj/vvj/vvj/vvh/v/gfnvAAD+AAB8AAAAA=="), this.x, this.y); //if (c<60) Bangle.setLCDPower(1); // keep LCD on for 1 minute let settings = require('Storage').readJSON("messages.settings.json", true) || {}; if (settings.repeat===undefined) settings.repeat = 4; diff --git a/apps/pebble/ChangeLog b/apps/pebble/ChangeLog index 76f90de8b..b3d37f841 100644 --- a/apps/pebble/ChangeLog +++ b/apps/pebble/ChangeLog @@ -2,3 +2,4 @@ 0.02: included deployment of pebble.settings.js in apps.json 0.03: Changed time+calendar font to LECO1976Regular, changed to slanting boot 0.04: Fix widget hiding code (fix #1046) +0.05: Fix typo in settings - Purple diff --git a/apps/pebble/pebble.settings.js b/apps/pebble/pebble.settings.js index b60600316..ff408907d 100644 --- a/apps/pebble/pebble.settings.js +++ b/apps/pebble/pebble.settings.js @@ -18,7 +18,7 @@ storage.write(SETTINGS_FILE, settings) } - var color_options = ['Green','Orange','Cyan','Perple','Red','Blue']; + var color_options = ['Green','Orange','Cyan','Purple','Red','Blue']; var bg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f']; E.showMenu({ diff --git a/apps/pooqroman/ChangeLog b/apps/pooqroman/ChangeLog index 9debf0efe..c4f3171d3 100644 --- a/apps/pooqroman/ChangeLog +++ b/apps/pooqroman/ChangeLog @@ -1,2 +1,3 @@ -0.01: New App! -0.02: Make internal menu time out + small fixes +0.01: Initial check-in. +0.02: Make internal menu time out + small fixes. +0.03: Autolight feature. diff --git a/apps/pooqroman/README.md b/apps/pooqroman/README.md index b41a4a316..87acea9ca 100644 --- a/apps/pooqroman/README.md +++ b/apps/pooqroman/README.md @@ -13,9 +13,12 @@ you can alter the number of hands on the display. When the watch is unlocked, sl There's also a setting that displays the second hand, but only if the watch is perfectly face-to-the-sky, in case you want the ability to check the _exact_ time, hands free, without the impact on battery life this usually entails. -Although we genrally obey the system-wide theming, you can long press on the display for a menu of additional options specific to the face. +Although we generally obey the system-wide theming, you can long press on the display for a menu of additional options specific to the face. You can also override the system 12/24 hour setting just for this face here, since it's, well, a rather different experience than with numeric displays. +By default, there is a backlight that comes on when you twist your wrist. This, of course, somewhat increases power draw and could be +annoying in an intentionally dark environment, so there is an option to disable it. + One other thing: there's some integration with system timers and alarms; they will show as small pips at the appropriate places in the day around the display. When they come within an hour, the pips turn to crosses relating to the minute hand, and the minute hand turns itself on. When timers are mere seconds away, the display changes again and the second hand activates itself, so you diff --git a/apps/pooqroman/app.js b/apps/pooqroman/app.js index d59d4ef6c..bed8ef3d2 100644 --- a/apps/pooqroman/app.js +++ b/apps/pooqroman/app.js @@ -138,6 +138,10 @@ class RomanOptions extends Options { onchange: x => this.calendric = x, format: x => ['none', 'day', 'date'][x] }, + 'Auto-Illum.': { + init: _ => this.autolight, + onchange: x => this.autolight = x + }, Defaults: _ => {this.reset(); this.interact();} }; } @@ -164,6 +168,7 @@ RomanOptions.defaults = { alarmFg: '#f00', timerFg: '#0f0', activeFg: g.theme.fg2, + autolight: true, }; ////////////////////////////////////////////////////////////////////////////// @@ -663,10 +668,10 @@ class Clock { this.options.on('done', () => this.start()); this.listeners = { - lcdPower: on => on ? this.active() : this.inactive(), - charging: () => {face.doIcons('charging'); this.active();}, - lock: () => {face.doIcons('locked'); this.active();}, + charging: _ => {face.doIcons('charging'); this.active();}, + lock: _ => {face.doIcons('locked'); this.active();}, faceUp: up => {this.conservative = !up; this.active();}, + twist: _ => this.options.autolight && Bangle.setLCDPower(true), drag: e => { if (this.t0) { if (e.b) { @@ -728,7 +733,6 @@ class Clock { } const delay = rate - now % rate + 1; this.refresh = true; - if (rate !== prev) { this.inactive(); this.redraw(rate); diff --git a/apps/pooqround/ChangeLog b/apps/pooqround/ChangeLog new file mode 100644 index 000000000..12876f71a --- /dev/null +++ b/apps/pooqround/ChangeLog @@ -0,0 +1 @@ +0.00: Initial check-in. diff --git a/apps/pooqround/README.md b/apps/pooqround/README.md new file mode 100644 index 000000000..3c651ed67 --- /dev/null +++ b/apps/pooqround/README.md @@ -0,0 +1,39 @@ +# pooq Round: a maximally readable, naturally 24hr, analogue watch face + +This is a normal watch face for telling the time. +It is unusual in that it uses a pie chart for the hour hand. This is much easier and +more precise to read at a glance than a conventional hand, and as a bonus can distinguish +midnight (all black) from noon (all white). + +The day and date are optionally displayed, typographically smooshed into the corners. +Either you'll like that, or you won't. + +## Options + +Because sometimes I don't want to burn what I'm cooking and others I'm lazy and just want to know if it's afternoon yet, +you can alter the number of ‘hands’ on the display. When the watch is unlocked, slide up to add dots representing the minute and second, +or down to remove the distraction. There's also a setting that displays the second hand, but only if the watch is perfectly face-to-the-sky, +in case you want the ability to check the _exact_ time, hands free, without the impact on battery life this usually entails. + +Although we generally obey the system-wide theming, you can long press on the display for a menu of additional options specific to the face. +We don't observe the system 12/24 setting, since it the design of the face is equally good in either interpretation. + +By default, there is a backlight that comes on when you twist your wrist. This, of course, somewhat increases power draw and could be +annoying in an intentionally dark environment, so there is an option to disable it. + +## Limitations + +Since this is intended as a design exercise, it does not and will probably never support the Bangle's standard widgets. +Sorry about that, but control of all the pixels was just too important to me. + +There's also no support for internationalisation at present. This irks me, but since every month and day name is hand-drawn, +there's no fix other than hard work. Talk to me about it if there's a language you'd like. + +## Feedback + +[I'd be happy to hear your feedback](https://www.github.com/stephenPspackman) if you have comments or find any bugs, or (most especially) +if you find this work interesting. + +## By + +Made by [Stephen P Spackman](https://www.github.com/stephenPspackman). diff --git a/apps/pooqround/app-icon.js b/apps/pooqround/app-icon.js new file mode 100644 index 000000000..f3db61936 --- /dev/null +++ b/apps/pooqround/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkB/4AW+ABCgAJEE4IXMh8ADAMPCoYXUK4gXMAAJHCN4oSG+I+CC4gEBC5gNBO4wuGC44IBGASPEC5ovHIox3JL4hdIR5xdIC54uIC5wWIC5hGKC5pGJC5QKCC6YKDCxIXIBQTCBC6IKDC6QKEC6IKFh52KC4gLHC5wLIC5oLJC5gLKC5YALC/4XfQZYAKh4X/C5B4V/4XYJChGBC7JIT/4wVh/wGCouBC6vwI4hIQagQWDDB5dBC45JNEwIXHGBhFDLwgYNCQQXKDBCgEC5QZFB4oGBA4IA=")) diff --git a/apps/pooqround/app.js b/apps/pooqround/app.js new file mode 100644 index 000000000..29fae6ee6 --- /dev/null +++ b/apps/pooqround/app.js @@ -0,0 +1,600 @@ +/* -*- mode: Javascript; c-basic-offset: 2; indent-tabs-mode: nil; coding: latin-1 -*- */ +// pooqRound + +// Copyright (c) 2021 Stephen P Spackman +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Notes: +// +// This only works for Bangle 2. + +const isString = x => typeof x === 'string'; +const imageWidth = i => isString(i) ? i.charCodeAt(0) : i.width; + +////////////////////////////////////////////////////////////////////////////// +/* System integration */ + +const storage = require('Storage'); + +////////////////////////////////////////////////////////////////////////////// +/* Face-specific options */ + +class Options { + // Protocol: subclasses must have static id and defaults fields. + // Only fields named in the defaults will be saved. + constructor() { + this.id = this.constructor.id; + this.file = `${this.id}.json`; + this.backing = storage.readJSON(this.file, true) || {}; + Object.setPrototypeOf(this.backing, this.constructor.defaults); + this.reactivator = _ => this.active(); + Object.keys(this.constructor.defaults).forEach(k => this.bless(k)); + } + + writeBack(delay) { + if (this.timeout) clearTimeout(this.timeout); + this.timeout = setTimeout( + () => { + this.timeout = null; + storage.writeJSON(this.file, this.backing); + }, + delay + ); + } + + bless(k) { + Object.defineProperty(this, k, { + get: () => this.backing[k], + set: v => { + this.backing[k] = v; + // Ten second writeback delay, since the user will roll values up and down. + this.writeBack(10000); + } + }); + } + + showMenu(m) { + if (m instanceof Function) m = m(); + if (m) { + for (const k in m) if ('init' in m[k]) m[k].value = m[k].init(); + m[''].selected = -1; // Workaround for self-selection bug. + Bangle.on('drag', this.reactivator); + this.active(); + } else { + if (this.bored) clearTimeout(this.bored); + this.bored = null; + Bangle.removeListener('drag', this.reactivator); + this.emit('done'); + } + g.clear(true); + E.showMenu(m); + } + + active() { + if (this.bored) clearTimeout(this.bored); + this.bored = setTimeout(_ => this.showMenu(), 15000); + } + + reset() { + this.backing = {__proto__: this.constructor.defaults}; + this.writeBack(0); + } +} + +class RoundOptions extends Options { + constructor() { + super(); + this.menu = () => ({ + '': {title: '* face options *'}, + '< Back': _ => this.showMenu(), + Ticks: { + init: _ => this.resolution, + min: 0, max: 3, + onchange: x => this.resolution = x, + format: x => ['seconds', 'seconds (up)', 'minutes', 'hours'][x] + }, + Calendar: { + init: _ => this.calendric, + min: 0, max: 5, + onchange: x => this.calendric = x, + format: x => ['none', 'day', 'date', 'both', 'month', 'full'][x], + }, + 'Auto-Illum.': { + init: _ => this.autolight, + onchange: x => this.autolight = x + }, + Defaults: _ => {this.reset(); this.interact();} + }); + } + + interact() {this.showMenu(this.menu);} +} + +RoundOptions.id = 'pooqround'; + +RoundOptions.defaults = { + resolution: 1, + calendric: 5, + dayFg: '#fff', + nightFg: '#000', + autolight: true, +}; + +////////////////////////////////////////////////////////////////////////////// +/* Assets (generated by resourcer.js, in this directory) */ + +const heatshrink = require('heatshrink'); +const dec = x => E.toString(heatshrink.decompress(atob(x))); +const y10F = [ + dec( + 'g///EAh////AA4IIBgPwgE+gAOBg/AngXB+EPAYM8gfggEfgF8D4OAj4dB8EDAYI' + + 'fBBAISBAAMOAYUB4AECnEAkAuBgEQBAPgIYX8IYX/wYDCEwIiMMgUYgECCIZlBAY' + + 'N4CoRUBIoMP8AZBge8CgMB8+BCAPw+F/gf8jxDB/0D4BGBEQMPAYIeBoAfBnEwge' + + 'Ah0cB4MDx4PBgHn4EB8E7LQM8h/eJ4MDBgIpB+H+g/wnE/WwMMO4P8LwM/XAJLBT' + + 'gY7BAAN/wC9CQwV+jwDB/4pBgP/EQKYBBIIxBPQP+SATfCIYIiCO4I9BBwM//hlB' + + 'PQJlCwYGBTAPgIgM4CYM8hwKBMoODegPA8F+gZlBewP4hz/BE4QrBGgM/LAV//4+' + + 'BAYJyBPwM/KQMeGQMPFwM8H4UHBIPwGQNwn4yBnhxBGQJxBGQK5BGQKWDOwUACAM' + + 'D/BDCNYPg///8E5HwR2BIwMDSgK0FSocMAYTLBAAYpBQAPnDwJGBEwK+B/hlB+F8' + + 'TARABTAJABTAPBMoR+BMoKXBDoX5DwIuBMoUPS4THCGwJbBhAaBvh5B+EHwPAOwP' + + 'guA1BvCcB4E8nxlBn1/VoIyBwDKBO4SGCgA=' + ), 48, dec('hgAI'), 34 +];const y1F = [ + dec( + 'g//AAPggE/AoX8gF/AoX+gF8CoU+gHwAoUPgAZBEIQFGCIodFFIo1FIIoADnAFEj' + + 'gFEh0AhA1EiAFCgeAFIf/4A1DFQIED/5MDGB6OEjAECHIIYDhkAuAFCjwFEj6DEn' + + '+AAod74AFD/PgvAtC+Hwv/wgZSBvEfLwc8RISOBGAJsBVAXgggEBE4PgIgJLC8E8' + + 'I4fgXQS/B8IhBGwOA8YFCgfA9+eAoMB4H/j/ACIPA/kPCQJCB/DMDMoMBboYVBKo' + + 'IDBSYeAAoYlCAATpEg/4Xwc/QIcPFoJcBQIP8GILXCDYLXBbId//BeCL4QwDgIwD' + + 'AAIXBDAQfCEYSPBAoaPCPQKPCAoZgBAoYvBAoIXBBAIFB/ALDEoJHBAoaPDaQSPB' + + 'AoKcBJgY9DTQX/EoKmCC4SyCYYJJB+CHBj+Aj8ASYJNBBINwIIOAM4ILDAYN/wAB' + + 'BB4JBBI45vCRYgADApEHL4pHB8AECFIPhAYLCCAggFBAgaNCYwgFEbAkAwAFEc4S' + + 'PCj/+LIKPBv6PEAoRnBFIMDFYLXCKoTLDa4YRDBYIdDh4FDMoQ1DK4ZBBMQIDBJY' + + 'bWBFIMEIIQpBgxxBgZRBh8AAYN8AoQVBjgbBAoTZBvwRCvEBF4IdB+E/OIp9CJgZ' + + 'BCQQUAA=' + ), 48, dec('hgAI'), 48 +];const y10sF = [ + dec( + 'j/+gP//0PgE8mEAmHwgfBBQINB8AWDgcAoEGAYMMj///H///wBwNgAQPAAQMgg8B' + + 'wE+hkA9kwg8Y+F4mP/4Fg/AVD4EBgcCg0MnEMmfgmH94PD4f+hkHIIgbBg44B/ng' + + 'h/H/H8n4IBg4QBhwUC//Bgf+FYMwAIPAjHDwPjg//gEPLgUAOYMAn/+DAM8j1gmH' + + 'h8fDBAMIHIRwDQAJtBg/8mH+gHPwEDCII/DAAM+n8B/v+h0+jkwuEw8fhV4UD8Yr' + + 'DjxDB/0Ch88CoLEB+fPwK0BKIOACoQA=' + ), 48, dec('hAAI'), 22 +];const y1sF = [ + dec( + 'j///0A/4ABgfAgEPgwNBg0MAYMMjwDBvAWB//gh4DBEAUDgEgAYQeBgcDEwQSCCY' + + 'oDCiACBwFgGoOBwEAnODBwPhw/Ag+Bw/gv0Bwf/+EBwAkBgPgCYOA4EQgIeB8ASB' + + 'g/AgcGnuAg0N8fAnkfIwPwnEB/40BgE8IYX8AYN/7hDB/kcg4xBv4TBC4kcLgUcv' + + '4ZBIgJIBHoNgHoJ8BgOGKQMHhijBnkYHoQlEv4DBRYWAv+eOgPwmEDg4mBXIXwni' + + 'SBDwRICSwIABWIM/HoM//57BEoMGv7dC/DrCLoU4eYfAv4kB8f/wPB98HLgP4TQM' + + 'B+EGh0PvE8QwN/+EP8E/LAK6CBIMAwPg+EDDwNgh8GJQP8h8Hz/gN4P+gBMBJIMA' + ), 48, dec('hEHhAAGA'), 31 +];const d10F = [ + dec( + 'AAXgjEAjkHgEDwPAgFwvEAh0f///44CB/ICB/4aDAQMcAQMDwAhBuAhBj0B4EH4E' + + 'wgP4h0Av4JBj3gnEHzkHgPjwF4/Fwh/+CQP/HwMD4E4gJLCvAuBj0ADgOGg+B8fA' + + 'uF5FoMeDQPH/l4vP8g/+vg4BzkAg/gA=' + ), 49, dec('hcMhYA=='), 27 +];const d1F = [ + dec( + 'AB1/+AECj///4FCAgP/8EAgf/4F//EAg4CBgf8gEPwAUBn0AhwaCAYMeAoUPgEcA' + + 'oUHAowRFDoopFGopBFJopZGBgIKCABlAIIcA4AFDgIFEgZBCAoMHAohVBAoY6CHg' + + 'U/Aol/AogADGoQFUABEMAQM/AQN8bIRZBRgJ5BLILhBgP3LIcD84rDg/HWYcPw4F' + + 'Dj4PBAoU+Aol8Aon4PocB+CJDgfgAoXgh/ATYX4v+AU4X//w/DbYQFCCwJ3PvDIE' + + 'NYQCCdoJ6CgfAiCGCI4NwgEeFwISCLoMeJwJdCnkfHYd4v4FD+f5AoUB9/BAoUD/' + + '4jCh8HG4IpCh5DBAIMeE4Q/BvjMCfoP8Z4Uf//wCgInB/5lCABs+AoicBAAUDAok' + + 'P9wFDv+OCAjUCHQP4AoY5BAoUHEIIFCv5JBAoLQBLQYqEApQpDArIAJv5IBnBTCV' + + '4McJAQFBcYLvBB4IkBd4N4cYQBBeoLdBCYIFDngFECoIFDOwIdCc4QpCFwIZCjwu' + + 'BEoU8FwIxCvAIBEIPB+AUBJIP/8AmBLYWAd4RnBdx4XCcYf/Dgn//AuEP4LjBXoJ' + + 'AC//vQYT0BBIKDC+CZBOIM/wAFDVYIFCgIrBAoUDPoIdCO4QnBaQYnBGoQVBIIZI' + + 'CJoTNCLIY4CAYIaDAAKRCAASRDAAIaEYAQtDYAI5DRgZFCAAYuCQoQuBAgIFBvEH' + + 'AgIFB+CgBAAMB86lE76EBFwX/GocPNoYmBIwk/HQl8LpIAQRId/SoYDB4ZJCUoPn' + + 'VoUHwP3Y4YYBY4k+Y4h5BdILhBd4YFFCIodFFIo1FIIpNFLIplGAArMFn6oBHYMA' + + 'DYQFBgP5E4IFBgfgUgIFCwBZBEAL1BPYZbDA4Z7DLYRtCBYYlDBoIxCEYMBHoIvC' + + 'HAI7Dh5PBI4X/LIX//7+Dn52Eh4QCA==' + ), 48, dec('ikPigAGA'), 48 +];const dowF = [ + dec( + 'gf8AYNwgEP/4FBvEAj//wEAnkAn0H4EAjwNBgPgAoQZBAoMOgHwAongCIQFDDoIF' + + 'FDoPggYFBF4IFBGoI7B+AFCE4NwCIIlCuAdBIYU4gPwn5VBjEA//+M4d//AFDh4W' + + 'BB4IgBAAX/B4n/PoQACJQIcEAokHAqAXFEYhLF/6tCApIADn4ED/zFBAAX8gaGBA' + + 'AZZFQIR2GdQQYRBYgXFEYoWRKQQWCLoRrEHgoAIg7LEj7LEn4bEvk+AodwhwFD+C' + + '5E8DFEAqIdFFIo1FIIpNFLIoEEAtShCVwQEDVwIFDKAJBvAAv/Bgn/RIjzGjwFEW' + + 'YicBAqAXFEYh6CRIgFKTYzjEAwt/AxxvDHAkf//AAgMDPIgVBGAnwAoYRBIYk/S4' + + 'kDMIgeBFIQEBBYRTBCAZ3FAggAMg4zEj7LEn7LEv++AodzxwFD+ePAofjw4FVDoo' + + 'pFv+eIImcJomYLImAAoZeEAtTyBAAQFEVYIFDSQIvhAojaCFwgABh4YEngFEuAqJ' + + 'gPAAocDApYuEgP/fgl/+B9HAAv+Aon8HQMOIAkeAokcAohaDAoM4Aol4AohmDAoJ' + + 'BDAoJsDAo7vhABbJDAo9/AojEFMYbKMArCBDFI41FWIYABggFEgbuCDYMPLIQbBj' + + '//wBdCn0H4DZCvEBb4YZBdYZBBAofgCIQFDDoIFFDoPggYFBF4IFBGoI7B+AFCE4' + + 'NwCIIlCuAdBIYU4gPwn5VBjC7B/y0Dv/4YwcPCwMAjJlCAAM584FDufDCAUA8eBA' + + 'p/zC4n5EYj1BAoc//4RDU4IFDA==' + ), 48, dec('kElkMljsljw='), 48 +];const mF = [ + dec( + '/AEDvEH4AFCgPAnwMDh0B+AGD8EPwAFCg8AvgMDuED8AMEj4MDDwI0DhwOB/4ACC' + + '4M/AoX8HgIMDCoI0EAAI0EgA0DnACBGgXHL4Q0Bjn+IYXAgfOCwRpBnPHEQmcuAG' + + 'DBg3csAGDj4mCAAX/QwhkBWSEDDIp3BAoZ3BBgkeDIp9FOYQMJDIomGh5NFv/wVo' + + 'YABYIgZBYIYABgKWBHAcPHAKsCgF4VoJDD4AVCIYbtBfAnwgYDBg+Ag6bBEQM8EQ' + + 'KoCDwMDwP9EQI0Bnk9540DZ4Y/CZ4Y0BbggwBDIY0BgP8JIbcB7yBE/pjDEAOQbZ' + + '8fRwT7DAAL7E/4zEjh9EKwLCEnB9BBhIZFgPzEwkP/jcFe4iYBdYLcEAwr5CBgYj' + + 'Hh65BAxU/AwjNCIhEH/BkEGYqTCRwYMFACE4AonHZ4kcIQkB5yOEnPHIYmcuAMK7' + + 'lgNJJQBJojkBKSB3BDIk/DIkBBgseDIpmEOYwMGDIsAOYkAgxBGGYjzBIwoMDXYI' + + 'tCaAQFCCwP8jiECCwMBBhAZGEwwzHIAxNGTY5UKTYIMEjkORwomEnEHBhQZFgPzT' + + 'gkP/hBEv+ACYivFe3adBAAfwAwoNFGYJkGh/+Axc/AwkfAoggFg/4ZgwzDj4GDiD' + + '7CAAPxRQswNIp1FBgnH4TPE/0gC4fO8wMDnPHsAMDzl2BhXcsxpFBgZQB+xqE/4z' + + 'DAAMCLRJ3BwaWFBgvjDAkfuAGEu4MFfoYZBW4v/eIn/8CzEvEHBocB4E+BgcOgIn' + + 'DgHgh+ANAcAvgMDuED8AMEj4MDDwI0DhyECAAQXBn4FCf4MBBgYVBGggABGghrBD' + + 'gQqCGgJ0BL4QJBTYJDCBIMBJYRpCJoIAEUIoMGPIgmDVAYMFKgQAODJh3BBgkeDI' + + 'p9FnAMLDIomGh5NFv/we372/exgZDe0BpCDIbBBDIl/EwonBAogMEHIIZDD4KUBH' + + 'wYFDCAPBOwQWCjgMHDI4mGGYwcC+JNFiDAFOIswEAmDDAn8kAME8QYEjwMDAAN2Y' + + 'QtgTonmYQoMDEwP2YQoZEgECJoozEv5NEj/+LQaYB8YMDn0fM4mAu4MDnEHuAMD8' + + 'KVEIAPgEwn+WAuAK4LABj7PDwEAvhJBCwUB8EP8EffQMOgH4C4ITB+EHAYN4RwMA' + + 'ng/BE4PwDYITCnw2BF4YKBF4LwDgInBKYLoFFQIAJgZCBAAZdCTYjOE/p6DgE954' + + 'fEziUDgE544ME7gtEj/OExUP7hAEnJTKAAxuBFoa4BOokfBgkB4AzEniZBewhaEB' + + 'goZGj61BRxMHWQIADjwJCIgLICJQQABDIL9BAAKoBg4iCgYTBKoZABhwnDJoJCDg' + + '4OCAAQXBewIABJoI5DHQSLBAAP8B4I6CcQgANgbVEOg0fEAkB8KOEnBNBVBIMMjh' + + 'yEWo0MhhSPgJoBwCZDNwp2BJor2LJpjAFAAImEJwI2BAAfwj4GEXYgMBAwKlFv4G' + + 'GFQpYFXQx0BAwx6DLQIGCIIgeCIAkHBgoAPn4FEh/8HQpPEn0fVCPhO4kfZ4hvGg' + + 'YSEgRGFngFEgf4AwkfSws/EwgtBBhQZFEw0cOwIHEuF4AocHWIL2LBgsHGoaBBn7' + + 'SD+DZEnzIFI4MPAoS1CAwbVRTYqoGWosB/p7EnvPD4mcbgk544ME7jcF5wmKh/cI' + + 'Ak5LUvhGYk4VAIfDwBaEBgsB4AZEjkOGYnA4AA==' + ), 49, dec('k0jk0kksmj0lk8lAwIA='), 52 +]; +const lockI = dec('hURwMAj0P485w1h3/4g15wFgjPmgOAs+Yg0B//AA'); +const lockSI = dec('hMNwMAjkfjHMt/8g1zgOc4FnmEf/AA=='); +const batteryI = dec('hERwMAjH/ABw'); +const chargeI = dec('g8NwMAgkYsHDh0fw8MmFhwUA'); +const HRMI = dec('iERwMAjk4l10t/29/3AIfn+ek6VTlPX9d3/U3/Ef/EP+EH8ED4EBwAA='); +const compassI = dec('hMJwMAhEEg8Dwfh2Pc43BwA='); +const y100I = dec('h8RwMAvk5/n6nOwm9w9lnzH+mO4sc4405xk7jE2mEssEd4EbgE+gE4A='); +const y100sI = dec('hcKwMAsOWvHZ+c2s1s4uYmcD4EwA'); + +////////////////////////////////////////////////////////////////////////////// +/* Status */ + +const status = (p, i) => function (g, x, y, rl) { // Nested arrows are currently broken! + if (!p()) return x; + if (rl) x -= imageWidth(i); + g.setColor(g.theme.fg).drawImage(i, x, y); + return rl ? x - 1 : x + imageWidth(i) + 1; +}; + +const doLocked = status(_ => Bangle.isLocked(), lockI); +const doPower = (g, x, y, rl) => { + const c = Bangle.isCharging(); + const b = E.getBattery(); + if (!c && b > 50) return x; + if (rl) x -= imageWidth(batteryI); + g.setColor(g.theme.fg).drawImage(batteryI, x, y); + g.setColor(b <= 10 ? '#f00' : b <= 30 ? '#ff0' : '#0f0'); + let h = 13 * (100 - b) / 100; + g.fillRect(x + 1, y + 2 + h, x + 6, y + 15); + if (c) g.setColor(g.theme.bg).drawImage(chargeI, x, y + 2); + return rl ? x - 1 : x + imageWidth(batteryI) + 1; +}; + +const doHRM = status(_ => Bangle.isHRMOn(), HRMI); // Might show Bangle.getHRM().bpm if confident? + +////////////////////////////////////////////////////////////////////////////// +/* Watch face */ + +class Round { + constructor(g) { + this.g = g; + this.b = Graphics.createArrayBuffer(g.getWidth(), g.getHeight(), 1, {msb: true}); + this.bI = { + width: this.b.getWidth(), height: this.b.getHeight(), bpp: this.b.getBPP(), + buffer: this.b.buffer, transparent: 0 + }; + this.c = Graphics.createArrayBuffer(g.getWidth(), g.getHeight(), 1, {msb: true}); + this.cI = { + width: this.c.getWidth(), height: this.c.getHeight(), bpp: this.c.getBPP(), + buffer: this.c.buffer, transparent: 0 + }; + this.options = new RoundOptions(); + this.timescales = [1000, 0, 60000, 900000]; + this.state = {}; + // Precomputed polygons for the border areas. + this.tl = [0, 0, 58, 0, 0, 58]; + this.tr = [176, 0, 176, 58, 119, 0]; + this.bl = [0, 176, 0, 119, 58, 176]; + this.br = [176, 176, 119, 176, 176, 119]; + this.xc = g.getWidth() / 2; + this.yc = g.getHeight() / 2; + this.minR = 5; + this.secR = 3; + this.r = this.xc - this.minR; + } + + reset() {this.state = {}; this.g.clear(true);} + + doIcons(which) { + this.state[which] = null; + this.render(new Date()); // Not quite right, I think. + } + + pie(f, a0, a1, invert) { + if (!invert) return this.pie(f, a1, a0 + 1, true); + let t0 = Math.tan(a0 * 2 * Math.PI), t1 = Math.tan(a1 * 2 * Math.PI); + let i0 = Math.floor(a0 * 4 + 0.5), i1 = Math.floor(a1 * 4 + 0.5); + let x = f.getWidth() / 2, y = f.getHeight() / 2; + let poly = [ + x + (i1 & 2 ? -x : x) * (i1 & 1 ? 1 : t1), + y + (i1 & 2 ? y : -y) / (i1 & 1 ? t1 : 1), + x, + y, + x + (i0 & 2 ? -x : x) * (i0 & 1 ? 1 : t0), + y + (i0 & 2 ? y : -y) / (i0 & 1 ? t0 : 1), + ]; + if (i1 - i0 > 4) i1 = i0 + 4; + for (i0++; i0 <= i1; i0++) poly.push( + 3 * i0 & 2 ? f.getWidth() : 0, i0 & 2 ? f.getHeight() : 0 + ); + f.setColor(0).fillPoly(poly); + } + + hand(t, d, c0, r0, c1, r1) { + t *= Math.PI / 30; + const r = this.r; + const z = 2 * r0 + 1; + const x = this.xc + r * Math.sin(t), y = this.yc - r * Math.cos(t); + const x0 = x - r0, y0 = y - r0; + d = d ? d[0] : Graphics.createArrayBuffer(z, z, 16, {msb: true}); + for (let i = 0; i < z; i++) for (let j = 0; j < z; j++) { + d.setPixel(i, j, g.getPixel(x0 + i, y0 + j)); + } + g.setColor(c0).fillCircle(x, y, r0); + if (c1 !== undefined) g.setColor(c1).fillCircle(x, y, r1); + return [d, x0, y0]; + } + + render(d) { + const g = this.g; + const b = this.b, bI = this.bI; + const c = this.c, cI = this.cI; + const state = this.state; + const options = this.options; + const cal = options.calendric; + const res = options.resolution; + const dow = (cal == 1 || cal > 2) && d.getDay(); + const ts = res < 2 && d.getSeconds(); + const tm = res < 3 && d.getMinutes() + ts / 60; + const th = d.getHours() + d.getMinutes() / 60; + const dd = cal > 1 && d.getDate(); + const dm = cal > 3 && d.getMonth(); + const dy = cal > 4 && d.getFullYear(); + const xc = this.xc, yc = this.yc, r = this.r; + const dlr = xc * 3/4, dlw = 8, dlhw = 4; + + // Restore saveunders for fast-moving, overdrawing indicators. + if (state.sd) g.drawImage.apply(g, state.sd); + if (state.md) g.drawImage.apply(g, state.md); + + if (dow !== state.dow) { + g.setColor(g.theme.bg).fillPoly(this.tl); + if (dow === +dow) { + g.setColor(g.theme.fg).setFontCustom.apply(g, dowF).drawString(dow, 5, 5); + } + state.dow = dow; + } + + const locked = Bangle.isLocked(); + const charging = Bangle.isCharging(); + const battery = E.getBattery(); + const HRMOn = Bangle.isHRMOn(); + if (dy !== state.dy || + locked !== state.locked || + charging !== state.charging || + Math.abs(battery - state.battery) > 2 || + HRMOn !== state.HRMOn + ) { + g.setColor(g.theme.bg).fillPoly(this.tr); + const u = dy % 10; + if (charging || battery < 50 || HRMOn || locked && dy !== +dy) { + let x = 172, y = 5; + x = doLocked(g, x, y, true); + x = doPower(g, x, y, true); + x = doHRM(g, x, y, true); + if (dy === +dy) { + g.setColor(g.theme.fg).drawImage(y100sI, 145, 23); + g.setFontCustom.apply(g, y10sF).drawString((dy - u) / 10 % 10, 157, 23); + g.setFontCustom.apply(g, y1sF).drawString(u, 165, 23); + } + } else if (dy === +dy) { + g.setColor(g.theme.fg); + if (locked) g.drawImage(lockSI, 136, 5); + else g.drawImage(y100I, 130, 5); + g.setFontCustom.apply(g, y10F).drawString((dy - u) / 10 % 10, 146, 5); + g.setFontCustom.apply(g, y1F).drawString(u, 160, 5); + } + state.dy = dy; + state.locked = Bangle.isLocked(); + state.charging = Bangle.isCharging(); + state.battery = E.getBattery() - E.getBattery() % 2; + state.HRMOn = Bangle.isHRMOn(); + } + if (dm !== state.dm) { + g.setColor(g.theme.bg).fillPoly(this.bl); + if (dm === +dm) { + g.setColor(g.theme.fg).setFontCustom.apply(g, mF); + g.drawString(String.fromCharCode(49 + dm), 5, 124); + } + state.dm = dm; + } + if (dd !== state.dd) { + g.setColor(g.theme.bg).fillPoly(this.br); + if (dd === +dd) { + let u = dd % 10; + g.setColor(g.theme.fg).setFontAlign(1, 1); + g.setFontCustom.apply(g, d10F).drawString((dd - u) / 10, 152, 172); + g.setFontAlign(-1, 1); + g.setFontCustom.apply(g, d1F).drawString(u, 152, 172); + g.setFontAlign(-1, -1); + } + } + if (th !== state.th) { + state.th = th; + b.clear(true).fillCircle(88, 88, r - 1); + g.setColor(options.nightFg).drawImage(bI); + if (th < 12) this.pie(b, th / 12, 1, true); + else this.pie(b, 1, th / 12, true); + g.setColor(options.dayFg).drawImage(bI); + } + state.md = tm === +tm ? + this.hand(tm, state.md, g.theme.bg, this.minR, g.theme.fg, this.minR - 1) : + null; + state.sd = ts === +ts ? + this.hand(ts, state.sd, g.theme.fg2, this.secR) : + null; + } +} + +////////////////////////////////////////////////////////////////////////////// +/* Master clock */ + +class Clock { + constructor(face) { + this.face = face; + this.timescales = face.timescales; + this.options = face.options; + this.rates = {}; + this.faceUp = null; + + this.options.on('done', () => this.start()); + + this.listeners = { + lcdPower: on => on ? this.active() : this.inactive(), + charging: () => {face.doIcons('charging'); this.active();}, + lock: () => {face.doIcons('locked'); this.active();}, + faceUp: up => { + this.conservative = !up; + this.faceUp = up; + this.active(); + }, + twist: _ => this.options.autolight && Bangle.setLCDPower(true), + drag: e => { + if (this.t0) { + if (e.b) { + e.x < this.xN && (this.xN = e.x) || e.x > this.xX && (this.xX = e.x); + e.y < this.yN && (this.yN = e.y) || e.y > this.yX && (this.yX = e.y); + } else if (this.xX - this.xN < 20) { + if (e.y - this.e0.y < -50) { + this.options.resolution > 0 && this.options.resolution--; + this.rates.clock = this.timescales[this.options.resolution]; + this.active(); + } else if (e.y - this.e0.y > 50) { + this.options.resolution < this.timescales.length - 1 && + this.options.resolution++; + this.rates.clock = this.timescales[this.options.resolution]; + this.active(); + } else if (this.yX - this.yN < 20 && Date.now() - this.t0 > 500) { + this.stop(); + this.options.interact(); + } + this.t0 = null; + } + } else if (e.b) { + this.t0 = Date.now(); this.e0 = e; + this.xN = this.xX = e.x; this.yN = this.yX = e.y; + } + } + }; + } + + redraw(rate) { + const now = this.updated = new Date(); + if (this.refresh) this.face.reset(); + this.refresh = false; + rate = this.face.render(now, rate); + if (rate !== this.rates.face) { + this.rates.face = rate; + this.active(); + } + return this; + } + + inactive() { + this.timeout && clearTimeout(this.timeout); + this.exception && clearTimeout(this.exception); + this.interval && clearInterval(this.interval); + this.timeout = this.exception = this.interval = this.rate = null; + this.face.reset(); // Cancel any ongoing background rendering + return this; + } + + active() { + const prev = this.rate; + const now = Date.now(); + let rate = Infinity; + for (const k in this.rates) { + let r = this.rates[k]; + r === +r || (r = r[+this.conservative]) + r < rate && (rate = r); + } + const delay = rate - now % rate + 1; + this.refresh = true; + + if (rate !== prev) { + this.inactive(); + this.redraw(rate); + if (rate < 31622400000) { // A year! + this.timeout = setTimeout( + () => { + this.inactive(); + this.interval = setInterval(() => this.redraw(rate), rate); + if (delay > 1000) this.redraw(rate); + this.rate = rate; + }, delay + ); + } + } else if (rate > 1000) { + if (!this.exception) this.exception = setTimeout(() => { + this.redraw(rate); + this.exception = null; + }, this.updated + 1000 - Date.now()); + } + return this; + } + + stop() { + this.inactive(); + for (const l in this.listeners) { + Bangle.removeListener(l, this.listeners[l]); + } + return this; + } + + start() { + this.inactive(); // Reset to known state. + this.conservative = false; + this.rates.clock = this.timescales[this.options.resolution]; + this.active(); + for (const l in this.listeners) { + Bangle.on(l, this.listeners[l]); + } + Bangle.setUI('clock'); + return this; + } +} + +////////////////////////////////////////////////////////////////////////////// +/* Main */ + +const clock = new Clock(new Round(g)).start(); diff --git a/apps/pooqround/app.png b/apps/pooqround/app.png new file mode 100644 index 000000000..b7ea36fb8 Binary files /dev/null and b/apps/pooqround/app.png differ diff --git a/apps/pooqround/resourcer.js b/apps/pooqround/resourcer.js new file mode 100644 index 000000000..44186e658 --- /dev/null +++ b/apps/pooqround/resourcer.js @@ -0,0 +1,1671 @@ +// pooqRoman resource maker +// +// Copyright (c) 2021 Stephen P Spackman +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Notes: +// +////////////////////////////////////////////////////////////////////////////// +/* ==ASSETS== */ + +const heatshrink = require('heatshrink'); + +const enc = x => { + const d = btoa(require("heatshrink").compress(x)); + var r = "'" + d.substr(0, 64); + for (let i = 64; i < d.length; i += 64) r += "' +\n '" + d.substr(i, 64); + return r + "'"; +}; + +const prepBitmap = (name, data) => { + const image = Graphics.createImage(data); + const raw = String.fromCharCode(image.width, image.height, 0x81, 0) + image.buffer; + const x = ` +const ${name}I = dec(${enc(raw)}); +`; + return x; +}; + +const prepFont = (name, data) => { + const image = Graphics.createImage(data); + const lengths = Uint8Array(256); + const offsets = Uint16Array(256); + const adjustments = Uint16Array(256); + let min = Infinity, max = -Infinity; + const lines = data.split('\n'); + let m; + // This regexp is clearly suboptimal, but Espruino's regexp engine is really wonky + // and doesn't process nested parentheses or alternation correctly. + for (let i = 0; i < 5 && !(m = /^(<*)=([*\d]+)(=*)(>*)$/.exec(lines[i])); i++); + if (!m) throw new Error('Missing or incorrect header'); + const desc = m[1].length, body = 1 + m[2].length + m[3].length, asc = m[4].length; + const h = desc + body + asc; + let width = m[2] == '*' ? null : +m[2]; + let c = null, o = 0; + lines.forEach((line, l) => { + if (m = /^(<*)(=)([*\d]*)(=*)(>*)$/.exec(line) || /^(<*)(-)(.)(-*)(>*)$/.exec(line)) { + const h = m[2] == '='; + if (m[1].length > desc || h && m[1].length != desc) + throw new Error('Invalid descender height at ' + l); + if (m[2].length + m[3].length + m[4].length != body) + throw new Error('Invalid body height at ' + l); + if (m[5].length > asc || h && m[5].length != asc) + throw new Error('Invalid ascender height at ' + l); + if (c != null) { + lengths[c] = l - o; + if (width !== null && width !== lengths[c]) + throw new Error( + `Character has width ${lengths[c]} != ${width} at ${offsets[c]}` + ); + c = null + } + if (!h) { + c = m[3].charCodeAt(0); + if (c < min) min = c; + if (c > max) max = c; + o = l + 1; + offsets[c] = l; + adjustments[c] = m[1].length + } + } + }); + const xoffs = Uint8Array(lines.length); + const ypos = Uint16Array(lines.length); + ypos.fill(0xffff); + const w0 = lengths[min]; + let widths = ''; + for (c = min, o = 0; c <= max; c++) { + for (i = 0, j = offsets[c]; i < lengths[c]; i++) { + xoffs[j] = asc + body + adjustments[c] - 1; + ypos[j++] = o++; + } + widths += String.fromCharCode(lengths[c]); + } + const raster = Graphics.createArrayBuffer(h, o, 1, {msb: true}); + const writer = Graphics.createCallback( + image.width, image.height, 1, + (x, y, col) => raster.setPixel(xoffs[y] - x, ypos[y], col) + ); + writer.drawImage(image); + if (width === null) width = `dec(${enc(widths)})`; + const x = `const ${name}F = [ + dec( + ${enc(raster.buffer)} + ), ${min}, ${width}, ${h} +];`; + return x; +}; + +res = ` +const heatshrink = require('heatshrink'); +const dec = x => E.toString(heatshrink.decompress(atob(x))); +`; + +res += prepFont('y10', ` +=*================================ +-0-------------------------------- + xxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxx + xxxxx xxxxx + xxxxx xxxxx + xxxx xxxx + xxxx xxxx + xxxxx xxxxx + xxxxx xxxxx + xxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxx +-1-------------------------------- + xxx + xxx + xxx + xxx x + xxx x + xxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxx + xxx + xxx +xxx +-2-------------------------------- + x xx + xx xxx + xxxx xxx + xxxxx xxx + xxxxxxx xxx + xxxx xxx xxx + xxxx xxxx xxx + xxxx xxxx xxx + xxxx xxxxxxxx xxxxxxx + xxxx xxxxxxxxxxxxxxxxxxx + xxxx xxxxxxxxxxxxxx +xxxx xxxxxxxxxx +-3-------------------------------- + xxx x xxx + xxx xx xxx + xxxx xxx xxx + xxxx xxxx xxx + xxxx xxxxx xxx + xxxx xxxxxx xxx + xxx xxxx xxxxxx + xxxx xxxxx xxxxx + xxxxxxx xxxxxxx xxxx + xxxxxxxxxxxxxxxxxxx xxx + xxxxxxxxxxxxxxxxx xx + xxxxxxxxxxx x +-4-------------------------------- + xxxx + xxxxxx + xxxxxxxx + xxxx xxxx + xxxx xxxxxx + xxxx xxxxxxxx + xxxxxxxxxxxx xxxxxxxxx + xxxxxxxxxxxx xxxxxxxxxx + xxxxxxxxxxxx xxxxxxx + xxxx xxx + xxxx + xxxx +-5-------------------------------- + xxx xxxxxxxxxxxxx + xxx xxxxxxxxxxxx + xxxx xxxxxxxxxxxx + xxxx xxx xxx + xxxx xxx xxx + xxxx xxx xxx + xxx xxxx xxx + xxxx xxxxx xxx + xxxxxx xxxxxx xxx + xxxxxxxxxxxxxxxxxx xxx + xxxxxxxxxxxxxxxx xxx + xxxxxxxxxxxx xxx +-6-------------------------------- + xxxxxxxxxx + xxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxx + xxxxxx xxxxxxxxxxxxxx + xxxx xxxx xxxxx + xxxx xxxx xxxx + xxx xxx xxx + xxxx xxxx xxxx + xxxxxx xxxxxx xxx + xxxxxxxxxxxxxxxx xxxx + xxxxxxxxxxxxxx xxx + xxxxxxxxxx xx +-7-------------------------------- + xxx + xxx + xxxxxxx xxx + xxxxxxxxxxxxxx xxx + xxxxxxxxxxxxxxxxxxx xxx + xxxxxxxxxxxxxx xxx + xxxxxxxxxxxxx + xxxxxxxxx + xxxxxx + xxxx + xx + x +-8-------------------------------- + xxxxxxx + xxxxxxxxxxxx xxxxxx + xxxxxxxxxxxxxxxxxxxxxxxx + xxxxxx xxxxxxxxxxxxxxx + xxxx xxxxx xxxx + xxxx xxx xxx + xxx xxx xxx + xxxx xxxxx xxxx + xxxxxx xxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxx xxxxxx + xxxxxxxxxx +-9-------------------------------- + xxxxxxxx + xxxxxxxxxxxx + x xxxxxxxxxxxxxx + xx xxxxx xxxxxx + xxx xxxx xxxx + xxx xxx xxx + xxxx xxxx xxx + xxxxx xxxx xxxx + xxxxxxx xxxxx xxxxxx + xxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxx +=*================================ +`); + +res += prepFont('y1', ` +=*============================================== +-0---------------------------------------------- + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxx xxxxx + xxxx xxxx + xxx xxx + xxx xxx + xxxx xxxx + xxxxx xxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +-1---------------------------------------------- + xxx + xxx + xxx + xxx x + xxx x + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxx + xxx + xxx +xxx +-2---------------------------------------------- + x xx + xx xxx + xxxx xxx + xxxxx xxx + xxxxxxx xxx + xxxx xxxx xxx + xxxx xxxxx xxx + xxxx xxxxxxx xxxx + xxxx xxxxxxxxxxxxx xxxxxxxxxxx + xxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxx xxxxxxxxxxxxxxxxxxxxxxxxx +xxxx xxxxxxxxxxxxxx +-3---------------------------------------------- + xxx x xxx + xxx xx xxx + xxxx xxxx xxx + xxxx xxxxx xxx + xxxx xxxxxxx xxx + xxxx xxxxxxxx xxx + xxx xxxx xxxxx xxx + xxxxx xxxxxxx xxxxxxxx + xxxxxxxxxx xxxxxxxxxx xxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxx + xxxxxxxxxxxxxxxxxxxxxxxx xxx + xxxxxxxxxxxxxxx xx +-4---------------------------------------------- + xxxx + xxxxxxx + xxxxxxxxxxx + xxxx xxxxxxxx + xxxx xxxxxxxxx + xxxx xxxxxxxxxxxxx + xxxxxxxxxxxx xxxxxxxxxxxxxxxx + xxxxxxxxxxxx xxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxx xxxxxxxxxxxx + xxxx xxxxxx + xxxx + xxxx +-5---------------------------------------------- + xxx xxxxxxxxxxxxxxxxxxxx + xxx xxxxxxxxxxxxxxxxxxx + xxxx xxxxxxxxxxxxxxxxxxx + xxxx xxx xxx + xxxx xxx xxx + xxxx xxx xxx + xxx xxxx xxx + xxxx xxxxxx xxx + xxxxxxxxx xxxxxxxx xxx + xxxxxxxxxxxxxxxxxxxxxxxxx xxx + xxxxxxxxxxxxxxxxxxxxx xxx + xxxxxxxxxxxxxx xxx +-6---------------------------------------------- + xxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxx xxxxxxxxxxxxxxxxxxxxx + xxxxxx xxxx xxxxxxxx + xxxx xxxx xxxxx + xxx xxx xxxx + xxxx xxxx xxx + xxxxxxxx xxxxxxxx xxxx + xxxxxxxxxxxxxxxxxxxxxxx xxx + xxxxxxxxxxxxxxxxxxxxx xxx + xxxxxxxxxxxxxxx xx +-7---------------------------------------------- + xxx + xxx + xxxxxxxxxx xxx + xxxxxxxxxxxxxxxxxxx xxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxx xxx + xxxxxxxxxxxxxxxxxxxxxxx xxx + xxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxx + xxxxxxxx + xxxx + xx + x +-8---------------------------------------------- + xxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxx xxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxx xxxxxxxxxxxxxxxxxxxxxxx + xxxx xxxxxx xxxx + xxxx xxx xxx + xxx xxx xxx + xxxx xxxxxx xxxx + xxxxxx xxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxx + xxxxxxxxxxxxxxxx +-9---------------------------------------------- + xxxxxxxxxx + x xxxxxxxxxxxxxxxx + xx xxxxxxxxxxxxxxxxxxx + xxxx xxxxxxx xxxxxxxx + xxxx xxxxx xxxxx + xxxxx xxx xxx + xxxxxx xxxx xxx + xxxxxxx xxxx xxxxx + xxxxxxxxxxx xxxxxx xxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxx +=*============================================== +`); + +res += prepFont('y10s', ` +=*==================== +-0-------------------- + xxxxxxxxxxxx + xxxxxxxxxxxxxxxx + xxxx xxxx + xx xx + xxxx xxxx + xxxxxxxxxxxxxxxx + xxxxxxxxxxxx + +-1-------------------- + xx + xx x + xx xx + xxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxx + xx +xx + +-2-------------------- + xxx x + xxxxx xx + xx xxx xx + xx xxx xx + xx xxxx xxxx + xx xxxxxxxxxxx +xx xxxxx + +-3-------------------- + x xx + xx x xx + xx xxx xx + xx xxxxx xx + xxx xxx xxxxxx + xxxxxxxxx xxxx + xxxxxx xx + +-4-------------------- + xxxxx + xxxxxxx + xxx xxxxxx + xxxxxxxx xxxxxx + xxxxxxxxxx xxxxxx + xxx + xxx + +-5-------------------- + x xxxxxxxxxx + xx xxxxxxxxx + xx xx xx + xx xx xx + xxxx xxx xx + xxxxxxxxxxx xx + xxxxxxxx + +-6-------------------- + xxxxxxxxx + xxxxxxxxxxxxx + xxxx xxxxxxxxx + xx xx xxxx + xxxx xxxx xxx + xxxxxxxxx xx + xxxxxxx x + +-7-------------------- + xx + xxxxx xx + xxxxxxxxx xx + xxxxxxx xx + xxxxx xx + xxxxxx + xxx + +-8-------------------- + xxxxxx xxxxx + xxxxxxxx xxxxxxx + xxx xxxxx xxx + xx xxx xx + xxx xxxxx xxx + xxxxxxxxxxxxxxxx + xxxxxx xxxxx + +-9-------------------- + xxxx + x xxxxxxxx + xxx xxxx xxxx + xxxx xx xx + xxxxx xxxx xxxx + xxxxxxxxxxxxxx + xxxxxxxxxx + +=*==================== +`); + +res += prepFont('y1s', ` +=*============================= +-0----------------------------- + xxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxx + xxxx xxxx + xx xx + xx xx + xxxx xxxx + xxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxx +-1----------------------------- + xx + xx x + xx xx + xxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxx + xx + xx +-2----------------------------- + xxx x + xxxxx xx + xx xxx xx + xx xxx xx + xx xxxx xxxxx + xx xxxxxx xxxxxx + xx xxxxxxxxxxxxxx +xx xxxxxxxx +-3----------------------------- + x xx + xx x xx + xx xxx xx + xx xxxxx xx + xx xx xxxx xx + xxxx xxxx xxxx xx + xxxxxxxxxxxxxx xxxxx + xxxxxxxxxx xxx +-4----------------------------- + xxxx + xxxxxxxx + xxx xxxxxxxxx + xxx xxxxxxxxxx + xxxxxxxx xxxxxxxxxxx + xxxxxxxxxx + xxx + xxx +-5----------------------------- + x xxxxxxxxxxxxx + xx xxxxxxxxxxxx + xx xx xx + xx xx xx + xxx xx xx + xxxx xxxx xx + xxxxxxxxxxxxxx xx + xxxxxxxxxx +-6----------------------------- + xxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxx + xxxx xxxx xxxxxxxx + xx xx xxxxx + xx xx xxxx + xxxx xxxx xx + xxxxxxxxxxxx x + xxxxxxxx +-7----------------------------- + xx + xxxxxxx xx + xxxxxxxxxxxxxxx xx + xxxxxxxxxxxx xx + xxxxxxxxx xx + xxxxxxxxx + xxxxxx + xxx +-8----------------------------- + xxxxxxx xxxxxxx + xxxxxxxxxxx xxxxxxxxxx + xxxx xxxx xxxx xxx + xx xxxxx xx + xx xxxxx xx + xxxx xxxx xxxx xxx + xxxxxxxxxxx xxxxxxxxxx + xxxxxxx xxxxxxx +-9----------------------------- + xxxxxxx + x xxxxxxxxxxx + xxx xxxx xxxx + xxxx xx xx + xxxxxx xx xx + xxxxxxxx xxxx xxxx + xxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxx +=*============================= +`); + +res += prepFont('d10', ` +=*========================= +-1------------------------- +xxx +xxx xx +xxx xxx +xxx xxx +xxx xxxx +xxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxx +xxx +xxx +xxx +-2------------------------- +xxx xx +xxxx xxx +xxxxx xxx +xxxxxx xx +xxxxxxx xxx +xxx xxxx xxx +xxx xxxx xxx +xxx xxxx xxx +xxx xxxxx xxxx +xxx xxxxxxxxxxx +xxx xxxxxxxxx +xxx xxxx +-3------------------------- + xx xxxx + xxx xxxx + xxx xxxx +xxxx xx xxxx +xxx xxxx xxxx +xxx xxxxxx xxxx +xxx xxxxxxxxx xxxx +xxxx xxxx xxxxxxxxx + xxxxxxxxxx xxxxxxx + xxxxxxxx xxxxx + xxxxxx xxx +=*========================= +`); + +res += prepFont('d1', ` +=*============================================== +-0---------------------------------------------- + + + xxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxx xxxxxxxxxxxx + xxxxxxxx xxxxxxxx + xxxxx xxxxx + xxx xxx +xxxx xxxx +xxx xxx +xxx xxx +xxxx xxxx + xxx xxx + xxxxx xxxxx + xxxxxxxx xxxxxxxx + xxxxxxxxxxxx xxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxx +-1---------------------------------------------- + + +xxx x +xxx xx +xxx xxx +xxx xxx +xxx xxxx +xxx xxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxx +xxx +xxx +xxx +-2---------------------------------------------- + + +xxxxxx xx +xxxxxxx xxxxx +xxxxxxxx xxxxxxx +xxx xxxxx xxxxxx +xxx xxxxx xxxx +xxx xxxxx xxx +xxx xxxxx xxx +xxx xxxxx xxx +xxx xxxxx xxx +xxx xxxxx xxx +xxx xxxxx xxx +xxx xxxxx xxxx +xxx xxxxx xxxx +xxx xxxxxx xxxxxx +xxx xxxxxxxx xxxxxxx +xxx xxxxxxxxxxxxxxxx +xxx xxxxxxxxxxxx +xxx xxxxxx +-3---------------------------------------------- + + + xxx xxxx + xxxxx xxxx + xxxxxxx xxxx + xxxxx x xxxx + xxxx xxx xxxx + xxx xxxx xxxx +xxxx xxxxxx xxxx +xxx xxxxxxx xxxx +xxx xxxxxxxxx xxxx +xxx xxx xxxxxxx xxxx +xxx xxx xxxxxx xxxx +xxxx xxxx xxxxxxxxxx + xxx xxx xxxxxxxxx + xxxx xxxx xxxxxxxx + xxxxx xxxxx xxxxxxx + xxxxxxxxxxxxxxxxxxxxx xxxxxx + xxxxxxxxxxxxxxxxx xxxxx + xxxxxxxxxxxxx xxxx +-4---------------------------------------------- + + + xxxxx + xxxxxxx + xxxxxxxxx + xxx xxxxxxx + xxx xxxxxxxx + xxx xxxxxxxxx + xxx xxxxxxxxxx + xxx xxxxxxxxxx + xxx xxxxxxxxxx + xxx xxxxxxxxxxx +xxxxxxxxxxxxxxx xxxxxxxxx +xxxxxxxxxxxxxxx +xxxxxxxxxxxxxxx + xxx + xxx + xxx + xxx + xxx +-5---------------------------------------------- + + + xxx xxxxxxxxxxxxxx + xxx xxxxxxxxxxxxxx + xxxx xxxxxxxxxxxxxxx + xxx xxx xxxx +xxxx xxxx xxxx +xxx xxx xxxx +xxx xxx xxxx +xxx xxx xxxx +xxx xxx xxxx +xxxx xxxx xxxx + xxx xxx xxxx + xxxx xxxx xxxx + xxxx xxxx xxxx + xxxx xxxx xxxx + xxxxx xxxxx xxxx + xxxxxxxxxxxxx xxxx + xxxxxxxxx xxxx + xxxxxxx xxxx +-6---------------------------------------------- + + + xxxxxxxxx + xxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxx + xxxxxx xxxxxxxxxxxxx + xxxx xxxxxxxxxxxxx + xxx xxxx xxxxxxxxxxx +xxxx xxxx xxxxxxxxx +xxx xxx xxxxxxxx +xxx xxx xxxxxxx +xxx xxx xxxxxxx +xxx xxx xxxxxx +xxxx xxxx xxxxx + xxx xxx xxxx + xxxx xxxx + xxxxxx xxxxxx + xxxxxxxxxxxxxxx + xxxxxxxxxxxxx + xxxxxxx +-7---------------------------------------------- + xxxx + xxxx + xxxx + xxxx + xxxx + xxxx + xxxx + xxxx +xxxxxxxxxxxxx xxxx +xxxxxxxxxxxxxxxxxxxxx xxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxx + xxxxxxxxxxxxxxxx xxxx + xxxxxxxxxxxx xxxx + xxxxxxxxxxxxx + xxxxxxxxxx + xxxxxxxx + xxxxxx + xxxxx + xxxx +-8---------------------------------------------- + + + xxxxxxx + xxxxxxxxxxxxx xxxxxxx + xxxxxxxxxxxxxxx xxxxxxxxxxx + xxxxxx xxxxxx xxxxxxxxxxxxx + xxxx xxx xxxxx xxxx + xxx xxxxxxx xxxx +xxxx xxxxx xxx +xxx xxxx xxxx +xxx xxx xxx +xxx xxx xxx +xxx xxxx xxxx +xxxx xxxxx xxx + xxx xxxxxxx xxxx + xxxx xxx xxxxx xxxx + xxxxxx xxxxxx xxxxxxxxxxxxx + xxxxxxxxxxxxxxx xxxxxxxxxxx + xxxxxxxxxxxxx xxxxxxx + xxxxxxx +-9---------------------------------------------- + + xxxx +xxx xxxxxxxx +xxx xxxxxxxxxxxx +xxx xxxxxx xxxxxx +xxx xxxxx xxxxx +xxxx xxx xxx +xxxx xxxx xxxx + xxxx xxx xxx + xxxx xxxx xxxx + xxxx xxx xxx + xxxxx xxxx xxxx + xxxxx xxx xxx + xxxxx xxxx xxxx + xxxxx xxx xxx + xxxxxx xxxxx xxxxx + xxxxxxxx xxxxxx xxxxxx + xxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxx + xxxxxxxxxxxx +=*============================================== +`); + +res += prepFont('dow', ` +=*============================================== +-1---------------------------------------------- +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxx + xxxxxx + xxxxxx + xxxxxx + xxxxxx + xxxxxx + xxxxxx + xxxxxx + xxxxxx + xxxxxx + xxxxxx + xxxxxx + xxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + + + xxxxxx + xxxxxxxxxx + xxxxxxxxxxxx + xxxxx xxxxx + xxx xxx + xxxx xxxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxxx xxxx + xxx xxx + xxxxx xxxxx + xxxxxxxxxxxx + xxxxxxxxxx + xxxxxx +-2---------------------------------------------- + xxx + xxx + xxx + xxx + xxx + xxx + xxx + xxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxx + xxx + xxx + xxx + xxx + xxx + xxx + xxx + + xxxxxxxxxxx + xxxxxxxxxxxxx + xxxxxxxxxxxxxx + xxxx + xxxx + xxx + xxx + xxx + xxxx + xxxx + xxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxx +-3---------------------------------------------- + xxxxxxxx + xxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxx + xxxxxxxxxxxxxxx + xxxxxxxxxxxxxxx + xxxxxxxxxxxxxxx + xxxxxxxxxx + xxxxxxxxxx + xxxxxxxxx + xxxxxxxxx + xxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxx + xxxxxxx + + + xxxxxx + xxxxxxxxxx + xxxxxxxxxxxx + xxxxx xxxxxxxx + xxx xxx xxx + xxxx xxx xxxx + xxx xxx xxx + xxx xxx xxx + xxx xxx xxx + xxx xxx xxx + xxxx xxx xxxx + xxx xxx xxx + xxxx xxxxxxxx + xxx xxxxxxx + xx xxxxxx + xxxx +-4---------------------------------------------- + xxx + xxx + xxx + xxx + xxx + xxx + xxx + xxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxx + xxx + xxx + xxx + xxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxx + xxxx + xxx + xxx + xxx + xxxx + xxxx + xxxxxxxxxxxxxxxx + xxxxxxxxxxxxxx + xxxxxxxxxxx +-5---------------------------------------------- +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxx xxx + xxxx xxx + xxx xxx + xxxx xxx + xxx xxx + xxxx xxx + xxx xxx + xxxx xxx + xxx xxx + xxx xxx + xxx + xxx + xxx + xxx + xxx + xxx + + + xxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxx + xxxx + xxxx + xxx + xxx + xxx + xxx + xxx + xxx + xxxx + xxx + xxxx + xxx + x +-6---------------------------------------------- + xxx xxxxxxxx + xxxx xxxxxxxxxxxx + xxxx xxxxxxxxxxxxxx + xxxx xxxxx xxxxx + xxxx xxxx xxxx + xxx xxx xxx + xxx xxx xxxx + xxx xxxx xxx + xxx xxx xxx + xxxx xxx xxx + xxxx xxxx xxx + xxxx xxx xxx + xxxx xxx xxxx + xxxx xxxx xxx + xxxxx xxx xxx + xxxxxx xxxxx xxx + xxxxxxxxxxxxxxx xx + xxxxxxxxxxxx xxxx + xxxxxx xxxxxxxx + xxxxxxxxxx xx + xxxx xxxx xxx + xxxx xxxx xxx + xxx xxx xxx + xxx xxx xxx + xxx xxx xxx + xxxx xxxx xxxx + xxxx xxxx xxxxx + xxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxx + xxxxxxxxxxxxxx +-0---------------------------------------------- + xxx xxxxxxxx + xxxx xxxxxxxxxxxx + xxxx xxxxxxxxxxxxxx + xxxx xxxxx xxxxx + xxxx xxxx xxxx + xxx xxx xxx + xxx xxx xxxx + xxx xxxx xxx + xxx xxx xxx + xxxx xxx xxx + xxxx xxxx xxx + xxxx xxx xxx + xxxx xxx xxxx + xxxx xxxx xxx + xxxxx xxx xxx + xxxxxx xxxxx xxx + xxxxxxxxxxxxxxx xx + xxxxxxxxxxxx + xxxxxx + xxxxxxxxxxx + xxxxxxxxxxxxx + xxxxxxxxxxxxxx + xxxx + xxxx + xxx + xxx + xxx + xxxx + xxxx + xxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxx +=*============================================== +`); + +res += prepFont('m', ` +<<<<=*============================================== +-1---------------------------------------------- + xxxx xxxxx + xxx xxxxx + xxx xxxxx + xxx xxxxx +xxx xxxxx +xxx xxxxx +xxx xxxxx +xxxx xxxxx + xxx xxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxx + xxxxx + xxx xxxxx + xxxxx xx xxxxx + xxxxxxx xxx xxxxx + xxx xxx xxxx xxxxx +xxx xxx xxx xxxxx +xxx xxx xxx +xxx xxx xxx + xx xxx xxxx + xxxxxxxxxxxx +xxxxxxxxxxxx +xxxxxxxxxxx + + +xxxxxxxxxxxxxx +xxxxxxxxxxxxxx +xxxxxxxxxxxxx + xxxx + xxx + xxx + xxx + xxxx +xxxxxxxxxxxxx +xxxxxxxxxxxx +xxxxxxxxxxx +-2---------------------------------------------- +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxx xxxx + xxxx xxxx + xxx xxxx + xxxx xxxx + xxx xxxx + xxxxxx xxxx xxxx + xxxxxxxxxx xxx xxxx + xxxxxxxxxxxx xxxx xxxx + xxxx xxxxxxx xxx xxxx +xxxx xxx xxxx xxxx xxxx +xxx xxx xxx xxxx +xxx xxx xxx xxxx +xxx xxx xxx xxxx +xxxx xxx xxxx xxxx + xxx xxxxxxx xxxx + xxxx xxxxxxx + xxx xxxxxx + x xxx + + +xxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxx + xxx xxx +xxxx xxxx +xxx xxx +xxxx xxxx + xxx xxx + xxxxx xxxxx + xxxxxxxxxx + xxxxxxxx + xxxx +-3---------------------------------------------- +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxx + xxxxxxxxxxx + xxxxxxxxxxx + xxxxxxxxxxxxx + xxxxxxxxxxxx + xxxxxxxxxx + xxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + + + xxx + xxxxx xx + xxxxxxx xxx + xxx xxx xxx +xxx xxx xxx +xxx xxx xxx +xxx xxx xxx + xx xxx xxxx + xxxxxxxxxxxx +xxxxxxxxxxxx +xxxxxxxxxxx + + +xxxxxxxxxxxxxx +xxxxxxxxxxxxxx +xxxxxxxxxxxxx + xxxx + xxxx + xxx + xxxx + xxxx + xxx + xx +<<<<-4---------------------------------------------- + xxxxxx + xxxxxxxxxxxx + xxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxx + xxx xxxxxxxxxxxxxxxxxx + xxx xxxxxxxxxxxxxxxxx + xxx xxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxx + xxxxxxxxxxxx + xxxxxx + +xxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxx + xxx xxx + xxxx xxxx + xxx xxx + xxxx xxxx + xxx xxx + xxxxx xxxxx + xxxxxxxxxx + xxxxxxxx + xxxx + + + xxxxxxxxxxxxxx + xxxxxxxxxxxxxx + xxxxxxxxxxxxx + xxxx + xxxx + xxx + xxxx + xxxx +<<<<-5---------------------------------------------- + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxx + xxxxxxxxxxx + xxxxxxxxxxx + xxxxxxxxxxxxx + xxxxxxxxxxxx + xxxxxxxxxx + xxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +x xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +x xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xx +xx +xx xxx + x xxxxx xx + x xxxxxxx xxx + xx xxx xxx xxxx + xx xxx xxx xxx + xx xxx xxx xxx + xx xxx xxx xxx + xx xx xxx xxxx + xx xxxxxxxxxxxx + xx xxxxxxxxxxxx + x xxxxxxxxxxx + x +xx +xx xxxxxxxxxxx +xx xxxxxxxxxxxxx +xx xxxxxxxxxxxxx +xxx xxxxx +xxx xxx +xxxxxxxxx + xxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxx + xxxxxxxxxxx +-6---------------------------------------------- + xxxx xxxxx + xxx xxxxx + xxx xxxxx + xxx xxxxx +xxx xxxxx +xxx xxxxx +xxx xxxxx +xxxx xxxxx + xxx xxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxx + xxxxx + xxxxxxxxxxx xxxxx + xxxxxxxxxxxx xxxxx + xxxxxxxxxxxxx xxxxx + xxxx xxxxx +xxx xxxxx +xxx +xxx +xxxx + xxxxxxxxxxxxx +xxxxxxxxxxxxxx +xxxxxxxxxxxxxx + + +xxxxxxxxxxxxxx +xxxxxxxxxxxxxx +xxxxxxxxxxxxx + xxxx + xxx + xxx + xxx + xxxx +xxxxxxxxxxxxx +xxxxxxxxxxxx +xxxxxxxxxxx +-7---------------------------------------------- + xxxx xxxxx + xxx xxxxx + xxx xxxxx + xxx xxxxx +xxx xxxxx +xxx xxxxx +xxx xxxxx +xxxx xxxxx + xxx xxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxx + xxxxx + xxxxxxxxxxx xxxxx + xxxxxxxxxxxx xxxxx + xxxxxxxxxxxxx xxxxx + xxxx xxxxx +xxx xxxxx +xxx +xxx + xxxx + xxxxxxxxxxxxx +xxxxxxxxxxxxxx +xxxxxxxxxxxxxx + + +xxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxx +<<<<-8---------------------------------------------- + xxxxxx + xxxxxxxxxxxx + xxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxx + xxx xxxxxxxxxxxxxxxxxx + xxx xxxxxxxxxxxxxxxxx + xxx xxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxx +x xxxxxxxxxxxx +x xxxxxx +xx +xx +xx xxxxxxxxxxx + x xxxxxxxxxxxx + x xxxxxxxxxxxxx + x xxxx + xx xxx + xx xxx + xx xxxx + xx xxxxxxxxxxxxx + xx xxxxxxxxxxxxxx + xx xxxxxxxxxxxxxx + x + x +xx xxxxxxxx +xx xxxxxxxxxxxx +xx xxxxxxxxxxxx +xx xxxxx xxxxx +xxx xxx xxx +xxx xxx xxx +xxxxxxxxx xxxxx + xxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxx + xxxxxxxxxxxxx +<<<<-9---------------------------------------------- + xxxxx xxxxxxxxxxxx + xxxxx xxxxxxxxxxxxxxxxxx + xxxx xxxxxxxxxxxxxxxxxxxxxx + xxx xxxxxxxxx xxxxxxx + xxxx xxxxxx xxxxx + xxx xxxx xxxx + xxxx xxxx xxxx + xxxxx xxxxxx xxxx + xxxxxxxxxxxxxxxxxxxx xxxx + xxxxxxxxxxxxxxxxxx xxxxx + xxxxxxxxxxxxxx xxxxxx + xxxxxxx + + xxxxxx + xxxxxxxxxx + xxxxxxxxxxxx + xxxx xxxxxxx + xxxx xxx xxxx + xxx xxx xxx + xxx xxx xxx + xxxx xxx xxxx + xxx xxxxxxx + xxxx xxxxxxx + xxx xxxxxx + x xxx + + +xxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxx + xxx xxx + xxxx xxxx + xxx xxx + xxxx xxxx + xxx xxx + xxxxx xxxxx + xxxxxxxxxx + xxxxxxxx + xxxx +-:---------------------------------------------- + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxx xxxxx +xxxx xxxx +xxx xxx +xxx xxx +xxxx xxxx + xxxxx xxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + + + xxxxxx + xxxxxxxxxx + xxxxxxxxxxxx + xxxx xxxx +xxx xxx +xxx xxx +xxx xxx +xxx xxx + xxx xxx + xxx xxx + xx xx + + + xxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxx +xxxx xxx +xxx xxx +xxx xxx +xxx xxx + xxx + xxx + xx +-;---------------------------------------------- +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxx + xxxxxxxxxxx + xxxxxxxxxx + xxxxxxxxxxx + xxxxxxxxxx + xxxxxxxxxxx + xxxxxxxxxx + xxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + + + xxxxxx + xxxxxxxxxx + xxxxxxxxxxxx +xxxxx xxxxx +xxx xxx +xxx xxx +xxxxx xxxxx + xxxxxxxxxxxx + xxxxxxxxxx + xxxxxx + x + xxxx + xxxxxxx + xxxxxxxx + xxxxxxxx +xxxxxxxx + xxxxxxxx + xxxxxxxx + xxxxxxx + xxx +-<---------------------------------------------- +xxx xxxx +xxx xxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxx xxxxx +xxxx xxxxxxx + xxxxx xxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxx + + + xxxxxx + xxxxxxxxxx + xxxxxxxxxxxx + xxxx xxxxxxx +xxxx xxx xxxx +xxx xxx xxx +xxx xxx xxx +xxxx xxx xxxx + xxx xxxxxxx + xxxx xxxxxxx + xxx xxxxxx + x xxx + + + xxxxxx + xxxxxxxxxx + xxxxxxxxxxxx + xxxx xxxx +xxx xxx +xxx xxx +xxx xxx + xxx xxx + xxx xxx + xx xx +<<<<=*============================================== +`); + +res += prepBitmap('lock', ` + xxxx + xxxxxx + xxx xxx + xx xx + xx xx +xxxxxxxxxx +x x +x xxxx x +x x +x x +x xxxx x +x x +x x +x xxxx x +x x +x x +xxxxxxxxxx +`); + +res += prepBitmap('lockS', ` + xxx + xxxxx + xx xx + xx xx +xxxxxxxxx +x x +x xxx x +x x +x xxx x +x x +x xxx x +x x +xxxxxxxxx +`); + +res += prepBitmap('battery', ` + xx +xxxxxxxx +xxxxxxxx +xxxxxxxx +xxxxxxxx +xxxxxxxx +xxxxxxxx +xxxxxxxx +xxxxxxxx +xxxxxxxx +xxxxxxxx +xxxxxxxx +xxxxxxxx +xxxxxxxx +xxxxxxxx +xxxxxxxx +xxxxxxxx +`); + +res += prepBitmap('charge', ` + x + xx + xx + xx + xx + xxx + xxxxxx + xxx + xx + xx + xx + xx + x +`); + +res += prepBitmap('HRM', ` + xxx xxx + x xxx xxx x + xx xxxxxxxx xx +xxx xxxxxxxx xxx +xxx xxxxxxxx xxx +xxx xxxxxxxx xxx +xxx xxxxxxx xx +x x x xx x x + x x xx x x x +xx x xxxxxx x xx + xxx xxxxxxxx x + xx xxxxxxxx + xxxxxxxxxx + xxxxxxxx + xxxxxx + xxxx + xx +`); + +res += prepBitmap('compass', ` + x + x + xxx + xxx + xxxxx + xx xx + xxx xxx + xx xx +xx xx +`); + +res += prepBitmap('y100', ` + xxxxx xxx +xxxxxxx xxxxx +x xxx xx xx + xx xxx xxx + xxx xx xx + xxxx xx xx +xxxxx xx xx + xxx xx xx + xxx xx xx + xxx xx xx + xxx xx xx + xx xx xx + x xx xx + xxx xxx + xx xx + xxxxx + xxx +`); + +res += prepBitmap('y100s', ` + xx xx +x xx xxxx + xx xx xx +xxx xx xx + xx xx xx + xx xx xx + x xx xx + xx xx + xxxx + xx +`); + +print(res); diff --git a/apps/rebble/ChangeLog b/apps/rebble/ChangeLog index 7b83706bf..3e7094eab 100644 --- a/apps/rebble/ChangeLog +++ b/apps/rebble/ChangeLog @@ -1 +1,2 @@ 0.01: First release +0.02: Fix dependancies, fix type to Purple diff --git a/apps/rebble/rebble.settings.js b/apps/rebble/rebble.settings.js index d06867efa..db3bab878 100644 --- a/apps/rebble/rebble.settings.js +++ b/apps/rebble/rebble.settings.js @@ -18,7 +18,7 @@ storage.write(SETTINGS_FILE, settings) } - var color_options = ['Green','Orange','Cyan','Perple','Red','Blue']; + var color_options = ['Green','Orange','Cyan','Purple','Red','Blue']; var bg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f']; E.showMenu({ diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 8f95eb3bb..f55f9937f 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -58,7 +58,7 @@ function resetSettings() { settings = storage.readJSON('setting.json', 1); if (!settings) resetSettings(); -const boolFormat = v => v ? "On" : "Off"; +const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off"; function showMainMenu() { var beepMenuItem; @@ -77,7 +77,7 @@ function showMainMenu() { }; } else { // Bangle.js 1 var beepV = [false, true, "vib"]; - var beepN = ["Off", "Piezo", "Vibrate"]; + var beepN = [/*LANG*/"Off", /*LANG*/"Piezo", /*LANG*/"Vibrate"]; beepMenuItem = { value: Math.max(0 | beepV.indexOf(settings.beep),0), min: 0, max: beepV.length-1, @@ -95,10 +95,10 @@ function showMainMenu() { const mainmenu = { '': { 'title': 'Settings' }, '< Back': ()=>load(), - 'App Settings': ()=>showAppSettingsMenu(), - 'BLE': ()=>showBLEMenu(), - 'Beep': beepMenuItem, - 'Vibration': { + /*LANG*/'App Settings': ()=>showAppSettingsMenu(), + /*LANG*/'BLE': ()=>showBLEMenu(), + /*LANG*/'Beep': beepMenuItem, + /*LANG*/'Vibration': { value: settings.vibrate, format: boolFormat, onchange: () => { @@ -110,7 +110,7 @@ function showMainMenu() { } } }, - "Quiet Mode": { + /*LANG*/"Quiet Mode": { value: settings.quiet|0, format: v => ["Off", "Alarms", "Silent"][v%3], onchange: v => { @@ -120,13 +120,13 @@ function showMainMenu() { if ("qmsched" in WIDGETS) WIDGETS["qmsched"].draw(); }, }, - 'Locale': ()=>showLocaleMenu(), - 'Select Clock': ()=>showClockMenu(), - 'Set Time': ()=>showSetTimeMenu(), - 'LCD': ()=>showLCDMenu(), - 'Theme': ()=>showThemeMenu(), - 'Utils': ()=>showUtilMenu(), - 'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }, + /*LANG*/'Locale': ()=>showLocaleMenu(), + /*LANG*/'Select Clock': ()=>showClockMenu(), + /*LANG*/'Set Time': ()=>showSetTimeMenu(), + /*LANG*/'LCD': ()=>showLCDMenu(), + /*LANG*/'Theme': ()=>showThemeMenu(), + /*LANG*/'Utils': ()=>showUtilMenu(), + /*LANG*/'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }, }; return E.showMenu(mainmenu); diff --git a/apps/snek/ChangeLog b/apps/snek/ChangeLog new file mode 100644 index 000000000..7c93db451 --- /dev/null +++ b/apps/snek/ChangeLog @@ -0,0 +1,2 @@ +0.01: First release +0.02: Fixed snek.png and snek.icon.js to 64x64 to display in launcher, added screenshots, updated apps.json diff --git a/apps/snek/screenshot_snek.png b/apps/snek/screenshot_snek.png new file mode 100644 index 000000000..01a15bb27 Binary files /dev/null and b/apps/snek/screenshot_snek.png differ diff --git a/apps/snek/screenshot_snek2.png b/apps/snek/screenshot_snek2.png new file mode 100644 index 000000000..d2cb938bc Binary files /dev/null and b/apps/snek/screenshot_snek2.png differ diff --git a/apps/snek/snek-icon.js b/apps/snek/snek-icon.js deleted file mode 100644 index c919c429e..000000000 --- a/apps/snek/snek-icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("pFIwkB+MRAEH/EUIA7i93u9xEUIABEf4AC+93/4CBETpDBv//gEHEf6MB9wkB/8HSDl3vwjCAAfnErIjiEQYdBAAXuAoSNXEYIdDAAoj4j/3DpN3v6NWAA3/fDYjgRgIjLu9xESj2BAAN/SQwLBEe4XDdwghDBQQjSCgN+C5D9FEebTDEZJEWEQSDVEdZpDZYPnETYhDAAhpeEbzREI0rTbEdXuETb4Bvz1BAYIj/EYxrg9yQDv/3JoS9WEcoaGAAQtBOwYABEaSMBWoYeFJKgjjiIUD9ySEEjwAJFogj0SQgAFBQ4jRABcfXoQj/TowjCOgIkeEf7lHvz+CEb93Ef4jHR8Rr/fY4jCuIifAAQj/EIojbeohGeEcQhHfLRFDeoIicEcQbDv3uEYiNXIgnu87SgEcf/DKnxBJEf/4ACESf/EcYA==")) \ No newline at end of file diff --git a/apps/snek/snek.icon.js b/apps/snek/snek.icon.js new file mode 100644 index 000000000..b820ffcf7 --- /dev/null +++ b/apps/snek/snek.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("oFA4X/AAOJksvr2rmokYgWqB7sq/2AB5krgYPMgW8ioPc1X9i/oLplVqv+1BdK1OV//q9QPMv4PL1eqy/q1SRK3tVu+AgWCFxP96t+Vhn9qoPLgWr/+//wFBSBEq3/qlW+JwJ/I3eXDQIOBB5OrB5sC3xMD1WAH4+r6xsOtSpKLoYPN1fV1bpKTYf+RJAeDytXFxoPOdQYPNPpkCy1VtQPc6wvO62Vu+CbhfVN4P//+q//uMgwPH9QPH3tqqtpqoABv4wHfoOpBoP/6tVUg7uBFwIvB3xlIB4v+OpJsC1WA1fVQpiGCB52+uzlMB58A31XB5sqy4PNlYPfH50rywPN3++BxgPPgW9V5kCZ4L/HBwmq/tX1APM/4PMBwNVvxuKgW/tP/HxUq1X+1eqFxQPRAAKsLB4KqNAFY=")) diff --git a/apps/snek/snek.png b/apps/snek/snek.png index 8cf8d05e3..6e31cfcbb 100644 Binary files a/apps/snek/snek.png and b/apps/snek/snek.png differ diff --git a/apps/thermom/ChangeLog b/apps/thermom/ChangeLog index 6ab6ba8e5..6183ac17b 100644 --- a/apps/thermom/ChangeLog +++ b/apps/thermom/ChangeLog @@ -1,2 +1,3 @@ 0.02: New App! 0.03: Improved messages and added Celsius sign +0.04: Make temperature value readable on smaller screens diff --git a/apps/thermom/app.js b/apps/thermom/app.js index 7eae9b3d4..145854765 100644 --- a/apps/thermom/app.js +++ b/apps/thermom/app.js @@ -4,7 +4,7 @@ function onTemperature(p) { var x = g.getWidth()/2; var y = g.getHeight()/2 + 10; g.drawString("Temperature:", x, y - 45); - g.setFontVector(70).setFontAlign(0,0); + g.setFontVector(g.getWidth() > 200 ? 70 : 40).setFontAlign(0,0); g.drawString(p.temperature.toFixed(1) + " °C", x, y); } diff --git a/bin/language_scan.js b/bin/language_scan.js new file mode 100755 index 000000000..2a92fded1 --- /dev/null +++ b/bin/language_scan.js @@ -0,0 +1,69 @@ +#!/usr/bin/nodejs +/* 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 +*/ + +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); +} +function WARN(s) { + console.log("Warning: "+s); +} + +var appsFile, apps; +try { + appsFile = fs.readFileSync(BASEDIR+"apps.json").toString(); +} catch (e) { + ERROR("apps.json not found"); +} +try{ + apps = JSON.parse(appsFile); +} catch (e) { + ERROR("apps.json not valid JSON"); +} + +// Given a string value, work out if it's obviously not a text string +function isNotString(s) { + 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 + return false; +} + +var textStrings = []; + +console.log("Scanning..."); +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 lex = Espruino.Core.Utils.getLexer(fileContents); + var tok = lex.next(); + while (tok!==undefined) { + if (tok.type=="STRING") { + if (!isNotString(tok.value)) { + //console.log(tok.str); + if (!textStrings.includes(tok.value)) + textStrings.push(tok.value); + } + } + tok = lex.next(); + } + }); +}); +console.log("Done"); +textStrings.sort(); +console.log(textStrings.join("\n")); diff --git a/core b/core index 23854083e..5a6cf40b4 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 23854083e0c3f83c649073a2d85e8079efc471d3 +Subproject commit 5a6cf40b4030ba909bf5f88b646ac05e41ce4c03 diff --git a/lang/de_DE.json b/lang/de_DE.json new file mode 100644 index 000000000..02dd72189 --- /dev/null +++ b/lang/de_DE.json @@ -0,0 +1,15 @@ +{ + "//":"German language translations", + "GLOBAL": { + "//":"Translations that apply for all apps", + "Alarm" : "Wecker", + "Hours" : "Stunden", + "Minutes" : "Minuten", + "Enabled" : "Aktiviert", + "Settings" : "Einstellungen" + }, + "alarm": { + "//":"App-specific overrides", + "Alarm" : "Alarm" + } +} diff --git a/lang/index.json b/lang/index.json new file mode 100644 index 000000000..7f4bbee37 --- /dev/null +++ b/lang/index.json @@ -0,0 +1,3 @@ +[ + "de_DE.json" +]