diff --git a/apps.json b/apps.json index 89c5f7b70..f116049dc 100644 --- a/apps.json +++ b/apps.json @@ -77,7 +77,7 @@ { "id": "messages", "name": "Messages", - "version": "0.16", + "version": "0.17", "description": "App to display notifications from iOS and Gadgetbridge", "icon": "app.png", "type": "app", @@ -116,7 +116,7 @@ { "id": "ios", "name": "iOS Integration", - "version": "0.07", + "version": "0.08", "description": "Display notifications/music/etc from iOS devices", "icon": "app.png", "tags": "tool,system,ios,apple,messages,notifications", @@ -167,7 +167,7 @@ { "id": "setting", "name": "Settings", - "version": "0.39", + "version": "0.40", "description": "A menu for setting up Bangle.js", "icon": "settings.png", "tags": "tool,system", @@ -936,7 +936,7 @@ "id": "widbatpc", "name": "Battery Level Widget (with percentage)", "shortName": "Battery Widget", - "version": "0.14", + "version": "0.15", "description": "Show the current battery level and charging status in the top right of the clock, with charge percentage", "icon": "widget.png", "type": "widget", @@ -1749,8 +1749,9 @@ "icon": "grocery.png", "type": "app", "tags": "tool,outdoors,shopping,list", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS", "BANGLEJS2"], "custom": "grocery.html", + "allow_emulator": true, "storage": [ {"name":"grocery.app.js","url":"app.js"}, {"name":"grocery.img","url":"grocery-icon.js","evaluate":true} @@ -2970,11 +2971,11 @@ { "id": "cprassist", "name": "CPR Assist", - "version": "0.01", + "version": "0.02", "description": "Provides assistance while performing a CPR", "icon": "cprassist-icon.png", "tags": "tool,firstaid", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "allow_emulator": true, "screenshots": [{"url":"bangle1-CPR-assist-screenshot.png"}], @@ -4210,13 +4211,13 @@ "id": "pastel", "name": "Pastel Clock", "shortName": "Pastel", - "version": "0.09", - "description": "A Configurable clock with custom fonts and background. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times", + "version": "0.10", + "description": "A Configurable clock with custom fonts, background and weather display. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times", "icon": "pastel.png", - "dependencies": {"mylocation":"app", "widpedom":"app"}, - "screenshots": [{"url":"screenshot_pastel.png"}], + "dependencies": {"mylocation":"app", "widpedom":"app","weather":"app"}, + "screenshots": [{"url":"screenshot_pastel.png"}, {"url":"weather_icons.png"}], "type": "clock", - "tags": "clock", + "tags": "clock, weather, tool", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ @@ -4382,7 +4383,7 @@ { "id": "gpstouch", "name": "GPS Touch", - "version": "0.01", + "version": "0.02", "description": "A touch based GPS watch, shows OS map reference", "icon": "gpstouch.png", "screenshots": [{"url":"screenshot4.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot1.png"}], @@ -4621,7 +4622,7 @@ "shortName":"93 Dub", "icon": "93dub.png", "screenshots": [{"url":"screenshot.png"}], - "version":"0.05", + "version":"0.06", "description": "Fan recreation of orviwan's 91 Dub app for the Pebble smartwatch. Uses assets from his 91-Dub-v2.0 repo", "tags": "clock", "type": "clock", @@ -4710,7 +4711,7 @@ "icon": "mylocation.png", "type": "app", "screenshots": [{"url":"screenshot_1.png"}], - "version":"0.01", + "version":"0.02", "description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README", "readme": "README.md", "tags": "tool,utility", @@ -4727,7 +4728,7 @@ "id": "pebble", "name": "Pebble Clock", "shortName": "Pebble", - "version": "0.06", + "version": "0.07", "description": "A pebble style clock to keep the rebellion going", "dependencies": {"widpedom":"app"}, "readme": "README.md", @@ -4735,7 +4736,7 @@ "screenshots": [{"url":"pebble_screenshot.png"}], "type": "clock", "tags": "clock", - "supports": ["BANGLEJS2"], + "supports": ["BANGLEJS", "BANGLEJS2"], "storage": [ {"name":"pebble.app.js","url":"pebble.app.js"}, {"name":"pebble.settings.js","url":"pebble.settings.js"}, @@ -4769,7 +4770,7 @@ "screenshots": [{"url":"screenshot_widbata_1.png"}], "version":"0.01", "type": "widget", - "supports": ["BANGLEJS2"], + "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "description": "Shows the current battery level status in the top right using the clocks colour theme", "tags": "widget,battery", @@ -4914,7 +4915,7 @@ "id": "rebble", "name": "Rebble Clock", "shortName": "Rebble", - "version": "0.03", + "version": "0.04", "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", @@ -5099,7 +5100,7 @@ "tags": "clock", "allow_emulator":true, "supports" : ["BANGLEJS2"], - "type": "clock", + "type": "clock", "storage": [ {"name":"contourclock.app.js","url":"app.js"}, {"name":"contourclock.img","url":"app-icon.js","evaluate":true} @@ -5334,7 +5335,7 @@ "icon": "andark_icon.png", "type": "clock", "tags": "clock", - "supports" : ["BANGLEJS2"], + "supports" : ["BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"andark.app.js","url":"app.js"}, @@ -5357,5 +5358,36 @@ { "name": "diract.app.js", "url": "diract.js" }, { "name": "diract.img", "url": "diract-icon.js", "evaluate": true } ] + }, + { + "id": "sonicclk", + "name": "Sonic Clock", + "version": "1.01", + "description": "A classic sonic clock featuring run, stop and wait animations.", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "readme": "README.md", + "storage": [ + {"name":"sonicclk.app.js","url":"app.js"}, + {"name":"sonicclk.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "touchmenu", + "name": "TouchMenu", + "version": "0.01", + "description": "Redesigned menu that uses the full touchscreen on the Bangle.js 2", + "screenshots": [{"url":"touchmenu.gif"}], + "icon": "touchmenu.png", + "type": "bootloader", + "tags": "tool", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"touchmenu.boot.js","url":"touchmenu.boot.js"} + ] } ] diff --git a/apps/93dub/ChangeLog b/apps/93dub/ChangeLog index c1b2588bb..1c18ca59b 100644 --- a/apps/93dub/ChangeLog +++ b/apps/93dub/ChangeLog @@ -3,3 +3,4 @@ 0.03: Code style cleanup 0.04: Set 00:00 to 12:00 for 12 hour time 0.05: Display time, even on Thursday +0.06: Fix light theme issue, where widgets would end up on a light strip diff --git a/apps/93dub/app.js b/apps/93dub/app.js index 1b0f69a94..f970eec5d 100644 --- a/apps/93dub/app.js +++ b/apps/93dub/app.js @@ -122,7 +122,13 @@ function draw(){ queueDraw(); } - +/** + * This watch is mostly dark, it does not make sense to respect the + * light theme as you end up with a white strip at the top for the + * widgets and black watch. So set the colours to the dark theme. + * + */ +g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); draw(); //the following section is also from waveclk diff --git a/apps/cprassist/ChangeLog b/apps/cprassist/ChangeLog index 5560f00bc..529010aa8 100644 --- a/apps/cprassist/ChangeLog +++ b/apps/cprassist/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Ported to Banglejs2 diff --git a/apps/cprassist/cprassist.js b/apps/cprassist/cprassist.js index 862ae54d6..128ae7407 100644 --- a/apps/cprassist/cprassist.js +++ b/apps/cprassist/cprassist.js @@ -35,23 +35,24 @@ function provideFeedback() { } function drawHeart() { - g.fillCircle(40, 92, 12); - g.fillCircle(60, 92, 12); - g.fillPoly([29, 98, 50, 120, 71, 98]); + var lowestPoint = g.getHeight()*3/5; + g.fillCircle(40, lowestPoint-29, 12); + g.fillCircle(60, lowestPoint-29, 12); + g.fillPoly([29, lowestPoint-22, 50, lowestPoint, 71, lowestPoint-22]); } function updateScreen() { - const colors = [0xFFFF, 0x9492]; - g.reset().clearRect(0, 50, 250, 150); + const colors = [0xFFFF-g.getBgColor(), 0x9492]; + g.reset().clearRect(0, 24, g.getWidth(), g.getHeight()*5/6); if (counter > 0) { g.setFont("Vector", 40).setFontAlign(0, 0); g.setColor(colors[counter%2]); drawHeart(); - g.drawString(counter + "", g.getWidth()/2, 100); + g.drawString(counter, 120, g.getHeight()*3/5-20); } else { g.setFont("Vector", 20).setFontAlign(0, 0); - g.drawString("RESCUE", g.getWidth()/2, 70); - g.drawString("BREATHS", g.getWidth()/2, 120); + g.drawString("RESCUE", g.getWidth()/2, g.getHeight()/3); + g.drawString("BREATHS", g.getWidth()/2, g.getHeight()*3/5); } } @@ -73,7 +74,7 @@ function tick() { interval = setInterval(tick, 60000/setting('compression_rpm')); g.clear(1).setFont("6x8"); -g.drawString(setting('compression_count') + ' / ' + setting('breath_count'), 30, 200); +g.drawString(setting('compression_count') + ' / ' + setting('breath_count'), 30, g.getHeight()*5/6); Bangle.loadWidgets(); Bangle.drawWidgets(); diff --git a/apps/gpstouch/Changelog b/apps/gpstouch/Changelog index 7f837e50e..e4a0bdfe8 100644 --- a/apps/gpstouch/Changelog +++ b/apps/gpstouch/Changelog @@ -1 +1,2 @@ 0.01: First version +0.02: Enchanced contrast of icon image diff --git a/apps/gpstouch/gpstouch.icon.js b/apps/gpstouch/gpstouch.icon.js index c4cf85676..3e05da0ff 100644 --- a/apps/gpstouch/gpstouch.icon.js +++ b/apps/gpstouch/gpstouch.icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEw4UA///j+EAYO/uYDB//wCYcPBA4AFh/ABZMDBbkX6gLIgtX6tQBY9VBYNVBY0BBYdABYsFqoACEgQLDitVtWpqtUBYtVq2q1WVGAQLErQLB0oLFHQNqBYIkBHgMDIwYKBAAJIDIweqz/2BYJtDBYI6Bv/9HgILHYwILGh4gBBYWfbooLF6AjPBYW//wLGL4Wv/RfGNZaDIBYibEBYizIBYjLDBYzXBd4TXCBZ60BBYRqEBZpUBBYRSFJAQLCA4b7BHgQLFgYLGIwYLEgoLBHQYLEgILBHQYLEgALBAoYLFi/UBZMHBZUD6ALKApQAFBbHwBZMP/4ABBwgIDA=")) +require("heatshrink").decompress(atob("mEw4UA///iADCn+EqoAWqAuJgoLcn/8BZENGwNwBY/VBYNXBY0DJ4fABYoiCEggLDmtX1Wq6tcBYvVrQLB0owCBYdVtQLB1NVBYg6BBQIABHgQLCgIuCGAVABYcNqwtBGIOVJAILFyoCCBY5eBBdo7IgIIB1t6BYJfENZaDB9QKB1aDFBYKbEBYizBrwLB2qnFdwSmCX401cYdUBZTjGfYgHCBZB2BBYhUBAARSBBYhICAAIGCBYkVBQJSCBYpICIwQLFHgQ6CBYo8CHQQLFHgQFDBYsVQIQLHgo6DBY0BHQYLGgY6DBYwAFBbCjDACY")) diff --git a/apps/gpstouch/gpstouch.png b/apps/gpstouch/gpstouch.png index c411356ae..a40419a3f 100644 Binary files a/apps/gpstouch/gpstouch.png and b/apps/gpstouch/gpstouch.png differ diff --git a/apps/ios/ChangeLog b/apps/ios/ChangeLog index 5e60068aa..1873649f9 100644 --- a/apps/ios/ChangeLog +++ b/apps/ios/ChangeLog @@ -2,9 +2,7 @@ 0.02: Remove messages on disconnect 0.03: Handling of message actions (ok/clear) 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.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 -0.07: Added more details from music (instead of Undefined) - Added more app identifiers - +0.07: Added more details from music (instead of Undefined), added more app identifiers +0.08: Added more app identifiers, added 'cannot display' in case a message goes empty because of replacements diff --git a/apps/ios/boot.js b/apps/ios/boot.js index 8ccfb617d..d317c23b0 100644 --- a/apps/ios/boot.js +++ b/apps/ios/boot.js @@ -66,6 +66,7 @@ E.on('notify',msg=>{ "com.apple.mobilecal": "Calendar", "com.apple.mobilemail": "Mail", "com.apple.mobilephone": "Phone", + "com.apple.mobileslideshow": "Pictures", "com.apple.MobileSMS": "SMS Message", "com.apple.Passbook": "iOS Wallet", "com.apple.podcasts": "Podcasts", @@ -83,6 +84,7 @@ E.on('notify',msg=>{ "com.ifttt.ifttt" : "IFTTT", "com.jumbo.app" : "Jumbo", "com.linkedin.LinkedIn" : "LinkedIn", + "com.marktplaats.iphone": "Marktplaats", "com.microsoft.Office.Outlook" : "Outlook Mail", "com.nestlabs.jasper.release" : "Nest", "com.netflix.Netflix" : "Netflix", @@ -90,6 +92,7 @@ E.on('notify',msg=>{ "com.skype.skype": "Skype", "com.skype.SkypeForiPad": "Skype", "com.spotify.client": "Spotify", + "com.storytel.iphone": "Storytel", "com.strava.stravaride": "Strava", "com.tinyspeck.chatlyio": "Slack", "com.toyopagroup.picaboo": "Snapchat", @@ -98,6 +101,8 @@ E.on('notify',msg=>{ "com.vilcsak.bitcoin2": "Coinbase", "com.wordfeud.free": "WordFeud", "com.zhiliaoapp.musically": "TikTok", + "io.robbie.HomeAssistant": "Home Assistant", + "net.weks.prowl": "Prowl", "net.whatsapp.WhatsApp": "WhatsApp", "nl.ah.Appie": "Albert Heijn", "nl.postnl.TrackNTrace": "PostNL", @@ -118,7 +123,7 @@ E.on('notify',msg=>{ new : msg.new, title : msg.title&&E.decodeUTF8(msg.title, unicodeRemap, replacer), subject : msg.subtitle&&E.decodeUTF8(msg.subtitle, unicodeRemap, replacer), - body : msg.message&&E.decodeUTF8(msg.message, unicodeRemap, replacer) + body : msg.message&&E.decodeUTF8(msg.message, unicodeRemap, replacer) || "Cannot display" }); // TODO: posaction/negaction? }); diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index fe46ba97a..4f0498e92 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -23,3 +23,4 @@ 0.14: Hide widget when all unread notifications are dismissed from phone 0.15: Don't buzz when Quiet Mode is active 0.16: Fix text wrapping so it fits the screen even if title is big (fix #1147) +0.17: Fix: Get dynamic dimensions of notify icon, fixed notification font diff --git a/apps/messages/widget.js b/apps/messages/widget.js index e831e5b68..1239ef262 100644 --- a/apps/messages/widget.js +++ b/apps/messages/widget.js @@ -1,8 +1,9 @@ -WIDGETS["messages"]={area:"tl",width:0,draw:function() { +WIDGETS["messages"]={area:"tl", width:0, iconwidth:23, +draw:function() { Bangle.removeListener('touch', this.touch); if (!this.width) return; var c = (Date.now()-this.t)/1000; - g.reset().clearRect(this.x,this.y,this.x+this.width,this.y+23); + g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+this.iconwidth); 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) || {}; @@ -17,7 +18,7 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() { WIDGETS["messages"].t=Date.now(); // first time WIDGETS["messages"].l=Date.now()-10000; // last buzz if (quiet) WIDGETS["messages"].t -= 500000; // if quiet, set last time in the past so there is no buzzing - WIDGETS["messages"].width=64; + WIDGETS["messages"].width=this.iconwidth; Bangle.drawWidgets(); Bangle.setLCDPower(1);// turns screen on },hide:function() { @@ -37,7 +38,7 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() { b(); },touch:function(b,c) { var w=WIDGETS["messages"]; - if (!w||!w.width||c.xw.x+w.width||c.yw.y+23) return; + if (!w||!w.width||c.xw.x+w.width||c.yw.y+w.iconwidth) return; load("messages.app.js"); }}; /* We might have returned here if we were in the Messages app for a @@ -46,4 +47,4 @@ want to buzz but should still show that there are unread messages. */ if (global.MESSAGES===undefined) (function() { var messages = require("Storage").readJSON("messages.json",1)||[]; if (messages.some(m=>m.new)) WIDGETS["messages"].show(true); -})(); +})(); \ No newline at end of file diff --git a/apps/mylocation/ChangeLog b/apps/mylocation/ChangeLog index 7b83706bf..653f859ae 100644 --- a/apps/mylocation/ChangeLog +++ b/apps/mylocation/ChangeLog @@ -1 +1,2 @@ 0.01: First release +0.02: Enhanced icon, make it bolder diff --git a/apps/mylocation/mylocation.icon.js b/apps/mylocation/mylocation.icon.js index bfb38d5ac..b79f5875f 100644 --- a/apps/mylocation/mylocation.icon.js +++ b/apps/mylocation/mylocation.icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEw4UA///t/7j/P3/vB4cBqtVoAbHBQIABBQ0FBYdQBYsVBYdUERIkGHIQADHoguEGAwuEGAwKFBZg8DHQw8EBYNf/1Vq3/8oLDIwNf/Wpv//0oLG9Wq3/qBYJUCBYuqBaBqBBYW+BepHEBbybCBYP+BYSnErYLDyoLFAANq/r8Ga5T7MBZZUBAAhSCfhA6DBZhIGBQg8FHQg8GHQgwGFwowFBQwwDFwwLMlS7Bqta1AKEn2q1K1C1WgBYf/1WqBYIDB1QKCgYLC0taBYoXB/QICBY0//7vBAAQ8EEgIABCwwME9QVEA")) +require("heatshrink").decompress(atob("mEw4UA///gH4AYPO/QPDgNVqtADY/1BYNfBQ0PBQIAB+ALFmoLDrgLF6oLDq4KEgYKDBYPABYcNBYlVuAuIGAwuEAANUBYYKFHgg6Bq4ZCr4DBHgQLBvWq2te1WlBYZGBBYOr1Wq1qSDBYNqBIILDKgQLLgoLHqBqDBfJHLBZBrOgKPCBYiPCU4NaBYe1WYrABBQLCCfgYGCrwVBa4kAirvKNgIAErgLDKgIAEKQQ8EAAY6DBZhIDIww8GHQg8GHQgwGFwowEFwx5EOog8GHQ0AlWpBYNq1AKFWIILBAYOgBYbICytWAgQKCgTgDcwYXGAAgvGAAY8EEgYWGBgoVEA==")) diff --git a/apps/mylocation/mylocation.png b/apps/mylocation/mylocation.png index 7148990a4..038ee177e 100644 Binary files a/apps/mylocation/mylocation.png and b/apps/mylocation/mylocation.png differ diff --git a/apps/pastel/ChangeLog b/apps/pastel/ChangeLog index afeb305c5..627531f03 100644 --- a/apps/pastel/ChangeLog +++ b/apps/pastel/ChangeLog @@ -7,3 +7,4 @@ 0.07: Added info line that cycles on BTN1/BTN3 (or vitual buttons on a bangle 2) 0.08: Added dependancy on MyLocation 0.09: Added dependancy on Pedometer Widget +0.10: Added Weather line, fixed issues on a Bangle 1, update every minute diff --git a/apps/pastel/README.md b/apps/pastel/README.md index 66ae0e189..b396386af 100644 --- a/apps/pastel/README.md +++ b/apps/pastel/README.md @@ -1,45 +1,83 @@ # Pastel Clock - *a configurable clock with custom fonts and background. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times* + *a configurable clock with custom fonts, background and optional weather icons. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times* * Designed specifically for Bangle 1 and Bangle 2 * A choice of 7 different custom fonts * Supports the Light and Dark themes -* Has a settings menu, change font, enable/disable the grid +* Has a settings menu, change font, enable/disable the grid, weather icons * On Bangle 1 use BTN1,BTN3 to cycle through the info display (Date, ID, Batt %, Ram % etc) * On Bangle 2 touch the top right/top left to cycle through the info display (Date, ID, Batt %, Ram % etc) +* The information display will cycle on each screen update * Uses mylocation.json from MyLocation app to calculate sunrise and sunset times for your location * Uses pedometer widget to get latest step count +* Use the weather widget to get weather status * Dependant apps are installed when Pastel installs +* The screen is updated every minute to save battery power +* The weather display will display temperature and wind speed on alternate screen refreshes I came up with the name Pastel due to the shade of the grid background. Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/) -## Lato + +## Weather Support + + +![](screenshot_pastel.png) + +Pastel installs the weather app and weather widget. You may want to +hide the weather widget display, this can be done through the weather +widget settings. You should first get the weather app working. If +the weather App is not working, then it is not going to work for +Pastel. + +The following weather icons are supported. + +![](weather_icons.png) + +Mostly cloudy, Sunny, Mostly Sunny, Snow, Rain. + +The triangle icon shows there is a problem connecting to GadgetBridge and the weather service. +You should follow the setup and trouble shooting guide for the Weather App. + +If you find the weather / gadgetbridge service unreliable you can +disable weather updates to pastel through the settings app. + + +## Fonts + +### Lato ![](screenshot_lato.png) -## Architect +### Architect ![](screenshot_architect.png) -## Gochihand +### Gochihand ![](screenshot_gochihand.png) -## Monoton +### Monoton ![](screenshot_monoton.png) -## Elite +### Elite ![](screenshot_elite.png) -## Cabin Sketch +### Cabin Sketch ![](screenshot_cabinsketch.png) -## Orbitron +### Orbitron ![](screenshot_orbitron.png) +### The Grid + +Setting the grid on provides a graph paper style background to the App. +The grid is not supported on a Bangle 1 due to flicker issues. + +![](screenshot_grid.png) + diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index aa4f6abf8..db60a2738 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -1,10 +1,22 @@ var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); require("f_latosmall").add(Graphics); +const storage = require('Storage'); +const locale = require("locale"); const SETTINGS_FILE = "pastel.json"; const LOCATION_FILE = "mylocation.json"; let settings; let location; +// cloud, sun, partSun, snow, rain, storm, error +// create 1 bit, max contrast, brightness set to 85 +var cloudIcon = require("heatshrink").decompress(atob("kEggIfcj+AAYM/8ADBuFwAYPAmADCCAMBwEf8ADBhFwg4aBnEPAYMYjAVBhgDDDoQDHCYc4jwDB+EP///FYIDBMTgA==")); +var sunIcon = require("heatshrink").decompress(atob("kEggILIgOAAZkDAYPAgeBwPAgIFBBgPhw4TBp/yAYMcnADBnEcAYMwhgDBsEGgE/AYP8AYYLDCYgbDEYYrD8fHIwI7CIYZLDL54AHA==")); +var sunPartIcon = require("heatshrink").decompress(atob("kEggIHEmADJjEwsEAjkw8EAh0B4EAg35wEAgP+CYMDwv8AYMDBAP2g8HgH+g0DBYMMgPwAYX8gOMEwMG3kAg8OvgSBjg2BgcYGQIcBAY5CBg0Av//HAM///4MYgNBEIMOCoUMDoUAnBwGkEA")); +var snowIcon = require("heatshrink").decompress(atob("kEggITQj/AAYM98ADBsEwAYPAjADCj+AgOAj/gAYMIuEHwEAjEPAYQVChk4AYQhCAYcYBYQTDnEPgEB+EH///IAQACE4IAB8EICIPghwDB4EeBYNAjgDBg8EAYQYCg4bCgZuFA==")); +var rainIcon = require("heatshrink").decompress(atob("kEggIPMh+AAYM/8ADBuFwAYPgmADB4EbAYOAj/ggOAhnwg4aBnAeCjEcCIMMjADCDoQDHjAPCnAXCuEP///8EDAYJECAAXBwkAgPDhwDBwUMgEEhkggEOjFgFgMQLYQAOA==")); +var errIcon = require("heatshrink").decompress(atob("kEggILIgOAAYsD4ADBg/gAYMGsADBhkwAYsYjADCjgDBmEMAYNxxwDBsOGAYPBwYDEgOBwOAgYDB4EDHYPAgwDBsADDhgDBFIcwjAHBjE4AYMcmADBhhNCKIcG/4AGOw4A==")); + + function loadSettings() { settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; settings.grid = settings.grid||false; @@ -93,7 +105,49 @@ function prevInfo() { } } -var mm_prev = "xx"; + +/** +Choose weather icon to display based on condition. +Based on function from the Bangle weather app so it should handle all of the conditions +sent from gadget bridge. +*/ +function chooseIcon(condition) { + condition = condition.toLowerCase(); + if (condition.includes("thunderstorm")) return stormIcon; + if (condition.includes("freezing")||condition.includes("snow")|| + condition.includes("sleet")) { + return snowIcon; + } + if (condition.includes("drizzle")|| + condition.includes("shower")) { + return rainIcon; + } + if (condition.includes("rain")) return rainIcon; + if (condition.includes("clear")) return sunIcon; + if (condition.includes("few clouds")) return partSunIcon; + if (condition.includes("scattered clouds")) return cloudIcon; + if (condition.includes("clouds")) return cloudIcon; + if (condition.includes("mist") || + condition.includes("smoke") || + condition.includes("haze") || + condition.includes("sand") || + condition.includes("dust") || + condition.includes("fog") || + condition.includes("ash") || + condition.includes("squalls") || + condition.includes("tornado")) { + return cloudIcon; + } + return cloudIcon; +} + +/** +Get weather stored in json file by weather app. +*/ +function getWeather() { + let jsonWeather = storage.readJSON('weather.json'); + return jsonWeather; +} function draw() { var d = new Date(); @@ -114,20 +168,28 @@ function draw() { var h = g.getHeight(); var x = (g.getWidth()/2); var y = (g.getHeight()/3); - - g.reset(); - if (process.env.HWVERSION == 1) { - // avoid flicker on a bangle 1 by comparing with previous minute - if (mm_prev != mm) { - mm_prev = mm; - g.clearRect(0, 30, w, h - 24); - } + var weatherJson = getWeather(); + var w_temp; + var w_icon; + var w_wind; + + if (settings.weather && weatherJson && weatherJson.weather) { + var currentWeather = weatherJson.weather; + const temp = locale.temp(currentWeather.temp-273.15).match(/^(\D*\d*)(.*)$/); + w_temp = temp[1] + " " + temp[2]; + w_icon = chooseIcon(currentWeather.txt); + const wind = locale.speed(currentWeather.wind).match(/^(\D*\d*)(.*)$/); + w_wind = wind[1] + " " + wind[2] + " " + (currentWeather.wrose||'').toUpperCase(); } else { - // on a b2 safe to just clear anyway as there is no flicker - g.clearRect(0, 30, w, h - 24); + w_temp = "Err"; + w_wind = "???"; + w_icon = errIcon; } - + + g.reset(); + g.clearRect(0, 30, w, h - 24); + // draw a grid like graph paper if (settings.grid && process.env.HWVERSION !=1) { g.setColor("#0f0"); @@ -139,6 +201,18 @@ function draw() { g.setColor(g.theme.fg); + // draw weather line + if (settings.weather) { + g.drawImage(w_icon, (w/2) - 40, 24); + g.setFontLatoSmall(); + g.setFontAlign(-1,0); // left aligned + if (drawCount % 2 == 0) + g.drawString(w_temp, (w/2) + 6, 24 + ((y - 24)/2)); + else + g.drawString( (w_wind.split(' ').slice(0, 2).join(' ')), (w/2) + 6, 24 + ((y - 24)/2)); + // display first 2 words of the wind string eg '4 mph' + } + if (settings.font == "Architect") g.setFontArchitect(); else if (settings.font == "GochiHand") @@ -161,36 +235,39 @@ function draw() { // for the colon g.setFontAlign(0,-1); // centre aligned - - if (d.getSeconds()&1) { - g.drawString(":", x,y); - } else { - // on bangle 1, we are not using clearRect(), hide : by printing over it in reverse color - if (process.env.HWVERSION == 1) { - g.setColor(g.theme.bg); - g.drawString(":", x,y); - g.setColor(g.theme.fg); - } - } - + g.drawString(":", x,y); g.setFontLatoSmall(); g.setFontAlign(0, -1); g.drawString((infoData[infoMode].calc()), w/2, h - 24 - 24); - if (drawCount % 3600 == 0) + // recalc sunrise / sunset every hour + if (drawCount % 60 == 0) updateSunRiseSunSet(new Date(), location.lat, location.lon); drawCount++; + queueDraw(); } -// Only update when display turns on -if (process.env.BOARD!="SMAQ3") // hack for Q3 which is always-on -Bangle.on('lcdPower', function(on) { - if (secondInterval) - clearInterval(secondInterval); - secondInterval = undefined; - if (on) - secondInterval = setInterval(draw, 1000); - draw(); +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + nextInfo(); + draw(); + }, 60000 - (Date.now() % 60000)); +} + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } }); Bangle.setUI("clockupdown", btn=> { @@ -204,8 +281,6 @@ loadFonts(); loadLocation(); g.clear(); -var secondInterval = setInterval(draw, 1000); -draw(); - Bangle.loadWidgets(); Bangle.drawWidgets(); +draw(); diff --git a/apps/pastel/pastel.settings.js b/apps/pastel/pastel.settings.js index fad36964d..bf83fa7c2 100644 --- a/apps/pastel/pastel.settings.js +++ b/apps/pastel/pastel.settings.js @@ -4,6 +4,7 @@ // initialize with default settings... let s = { 'grid': false, + 'weather': false, 'font': "Lato" } @@ -39,8 +40,16 @@ value: s.grid, format: () => (s.grid ? 'Yes' : 'No'), onchange: () => { - s.grid = !s.grid - save() + s.grid = !s.grid; + save(); + }, + }, + 'Show Weather': { + value: s.weather, + format: () => (s.weather ? 'Yes' : 'No'), + onchange: () => { + s.weather = !s.weather; + save(); }, } }) diff --git a/apps/pastel/screenshot_grid.png b/apps/pastel/screenshot_grid.png new file mode 100644 index 000000000..7b993353b Binary files /dev/null and b/apps/pastel/screenshot_grid.png differ diff --git a/apps/pastel/screenshot_pastel.png b/apps/pastel/screenshot_pastel.png index d489f1914..c792ada8d 100644 Binary files a/apps/pastel/screenshot_pastel.png and b/apps/pastel/screenshot_pastel.png differ diff --git a/apps/pastel/weather_icons.png b/apps/pastel/weather_icons.png new file mode 100644 index 000000000..59d4f007f Binary files /dev/null and b/apps/pastel/weather_icons.png differ diff --git a/apps/pebble/ChangeLog b/apps/pebble/ChangeLog index d92be5e9c..0cba5a2b2 100644 --- a/apps/pebble/ChangeLog +++ b/apps/pebble/ChangeLog @@ -4,3 +4,4 @@ 0.04: Fix widget hiding code (fix #1046) 0.05: Fix typo in settings - Purple 0.06: Added dependancy on Pedometer Widget +0.07: Fixed icon and ong file to 48x48 diff --git a/apps/pebble/pebble.icon.js b/apps/pebble/pebble.icon.js index ecd7feb7f..1c1166156 100644 --- a/apps/pebble/pebble.icon.js +++ b/apps/pebble/pebble.icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("oFAwgNKiIAIFqofegIf/DAUzAAMyAwUQD60T/4ACD7Q/cPxIf/YCofcDhYiSXYYfuUZgf/D/4f/D6USkUgD/4fuogAID6vtDw/UD6vu6geF73kb6vuEAtN9wfYMIneD7JADDwIfaIAJdBD7YgBHwQfbAAgfkf6Qf/D/4feogAID6oAND/4f/iAdJD/4f/D/4fUDxYABD74iODiAftTZgfnYYczAAMyD7UT/4ACH/S+bD8DAKD9Y=")) +require("heatshrink").decompress(atob("mEw4UA///ssp4XthFCBwUBqoABqAaGBZcFBZdX1W1qgLHrwLKqv/6oLJAAILHioLJn5qBAAYLEBQoLeHQQABv4LjGAgLYq2qAAOlBbBHFBdPAKcQLdWcb7jAAoLcn4LKgEVHQVUBQsAgoLLq//6oLIr2q2oXJBZQvCqALGgILTA=")) diff --git a/apps/pebble/pebble.png b/apps/pebble/pebble.png index 10f5adb56..368e08750 100644 Binary files a/apps/pebble/pebble.png and b/apps/pebble/pebble.png differ diff --git a/apps/rebble/ChangeLog b/apps/rebble/ChangeLog index 16e65d4f9..b9c26b4e3 100644 --- a/apps/rebble/ChangeLog +++ b/apps/rebble/ChangeLog @@ -1,3 +1,4 @@ 0.01: First release 0.02: Fix typo to Purple 0.03: Added dependancy on Pedometer Widget +0.04: Fixed icon and png to 48x48 pixels diff --git a/apps/rebble/README.md b/apps/rebble/README.md index 712fa4e9b..0ecb51d7a 100644 --- a/apps/rebble/README.md +++ b/apps/rebble/README.md @@ -11,6 +11,7 @@ * Uses pedometer widget to get latest step count * Dependant apps are installed when Rebble installs * Uses the whole screen, widgets are made invisible but still run in the background +* The icon is James Dean - 'Rebel Without a Cause' ![](screenshot_rebble.png) ![](screenshot_rebble2.png) diff --git a/apps/rebble/rebble.icon.js b/apps/rebble/rebble.icon.js index 4c898974e..3fc45b820 100644 --- a/apps/rebble/rebble.icon.js +++ b/apps/rebble/rebble.icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("oFA4X/AAIHBw3Aiv3HmE/HQQAF/gPEnWqAAOpy2VqoFB3gPIBoIABtQPJ1PVqv1q3qB5OlrNVEIQPK2tlBwOptQPIyvdH4VtrQPI3tbqtdB4OaB5FVH4NV0pgBB5F13//MIIPJ1O2TgWV/o/I1fbB4WpqoPI1NvB4REBJ5APD/wPBD5JOBB4WVqwPH0oPE0oPJ/NX//6AoNVF5HZq3pq2qSYIPI6tX+pNBB5Ol6v6B4IABH5P7//b1oPBN5GlLwPr9IPK1IPC/SvK1QPCOAIPL6te//5B5lW/5ABL5APB/wPB3IPJ1Y/C/yuBF5APC9X+yo/K34LB3QPBtQPJ//23SPB1QPI3eVs2qJwIPJ1flqyeBtQPJtZPBLwIPKzf/1ROCB5OWAQJOBB5QsBAAQGBf5FlB5tVvoPMNQO9B4daB5O+B4aPIqtX35tBB5M1qtbB4i/HB4WvOAjvGB4IpBIQIADB46aBB4t8B49VB54AFB6zrB1Wm1RTBywPI0oPCeQOaB4+ltOlq2V02VqwPOrQPIF5w/PFQIvPB71pH4uqX8g")) +require("heatshrink").decompress(atob("mEw4X/AoOG4EV+/I+dVAAVUCgcFBIYABpIJBgcFoIKEqkQgEH6EH0ILEqAhCgkBqEVBYdAhUBBoU9GAlAlw5CgERgILDIocEgEGoALDlEHwEAlkUg8EBYfAFwVA+BgEqmQjWrBgMQhgvDqmA9Wq1WsNoMALweDBQIAB4E8BYdTpwLD/kA4AXDjwKC1f/IAILDnQLC1//4ALEHQQLCKgILDFwYLB6EATgVABYe///MNgdA3kQEoILGqCNBlfQh//4NAPAVQ+YLBQYM/ocABYfAiEqgE0g6DBF4eAlFrYQZHDoOu1Xo8lgBYtCKIOo9aOBAAJrCBYWv9X/+gXEqSZC/f//4LHz/6DQIjEBYOhgG6BY1a1WggDCB3ojErYTBoEOa4QLF1X9jWrXwILGKYOvBYtfKYX+17iBHYdX1WQgf/34LBUwQLB1cLWIJqCBYdV9W+1+//oLBWQVVqnuD4M/KQoAB/+kBYJGBCwYLCI4P/DQILFnwLCEQ1Vp+q/46CBYtDXgJ1FAAVwfI4ABqAUCBY8A9gLIqEA9ALEKYYLB9YLERwQ=")) diff --git a/apps/rebble/rebble.png b/apps/rebble/rebble.png index 69653015c..acfd37400 100644 Binary files a/apps/rebble/rebble.png and b/apps/rebble/rebble.png differ diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index a2245a02d..4d9881613 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -42,3 +42,4 @@ 0.37: Going into passkey menu now saves settings with passkey 0.38: Restructed menus as per forum discussion 0.39: Fix misbehaving debug info option +0.40: Moved off into Utils, put System after Apps diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 1208018ed..4bdf7f304 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -66,11 +66,10 @@ function showMainMenu() { '': { 'title': 'Settings' }, '< Back': ()=>load(), /*LANG*/'Apps': ()=>showAppSettingsMenu(), - /*LANG*/'Bluetooth': ()=>showBLEMenu(), /*LANG*/'System': ()=>showSystemMenu(), + /*LANG*/'Bluetooth': ()=>showBLEMenu(), /*LANG*/'Alerts': ()=>showAlertsMenu(), - /*LANG*/'Utils': ()=>showUtilMenu(), - /*LANG*/'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() } + /*LANG*/'Utils': ()=>showUtilMenu() }; return E.showMenu(mainmenu); @@ -537,7 +536,8 @@ function showUtilMenu() { setTimeout(showMainMenu, 50); } else showUtilMenu(); }); - } + }, + /*LANG*/'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() } }; if (Bangle.factoryReset) { menu['Factory Reset'] = ()=>{ diff --git a/apps/sonicclk/Changelog b/apps/sonicclk/Changelog new file mode 100644 index 000000000..7c83f6988 --- /dev/null +++ b/apps/sonicclk/Changelog @@ -0,0 +1,2 @@ +1.00 Added sonic clock app +1.01 Fixed text alignment issue; Increased acceleration required to activate twist; \ No newline at end of file diff --git a/apps/sonicclk/README.md b/apps/sonicclk/README.md new file mode 100644 index 000000000..a381e0a07 --- /dev/null +++ b/apps/sonicclk/README.md @@ -0,0 +1,13 @@ +# Sonic Clock + +A classic sonic clock featuring run, stop and wait animations. + +![Sonic Clock screenshot](screenshot.png) + +## Usage + +- Sonic will run when the screen is unlocked +- Sonic will stop when the screen is locked +- Sonic will wait when looking at your watch face (when `Bangle.on("twist", fn)` is fired). + +### Made with love by [Joseph](https://github.com/Johoseph) 🤗 diff --git a/apps/sonicclk/app-icon.js b/apps/sonicclk/app-icon.js new file mode 100644 index 000000000..33e22971b --- /dev/null +++ b/apps/sonicclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkBiIA/AE0ZzIACBIgFFC7oTCylEzOIDYeZogX6LwWd7oYCAAOJC82UpoXFAAKnMC6x2CpIVFC/gSCxOYAYP///4C4X/DBIXWIIwWBDAWPAYIXedQJwG/8AgEP//wgAX2CwIADRoQXmSIoXtJAeEoi+C+lEoAcBogX7zOUondolEpvdAAQXgYIgXCAAwXlAAIYC6ENLx4XtAYMZDAvd6gWJC7IKJABgX/C74A/ADY")) diff --git a/apps/sonicclk/app.js b/apps/sonicclk/app.js new file mode 100644 index 000000000..296677281 --- /dev/null +++ b/apps/sonicclk/app.js @@ -0,0 +1,284 @@ +const vw = g.getWidth(); +const bgWidth = 384; + +const sonic = { + bpp: 4, + transparent: 1, +}; + +const defaultSonic = { + width: 36, + height: 62, + buffer: require("heatshrink").decompress(atob("iIADzIACA4gNECKYOComZxAVDogRXBQfdCQQABCLITCogRFCQIRYLAgAFCQwRQBoQCC///AYYRWGAoMBAAgRYCYf/gEAB4ICBCKrDDCL4UECMA3DolEB4IDBAAQRb7oMB7vdHIIRWCAYRCAAoRWCoozDCBQRRBQI3FERIRPPIbGBPIQiICKAQCdQRZDAYIRXCQIJB/4RDEhQRQGYP//7FEGpARQCAYREgEzERQRNAAIRCLIKkBCLg4CYoIABB5IRWAQQRcBoIODCRYRQB4INECLkRgH/ZAMACRgRSmbGBCIIFBmYWCCLAAC7oCBdgNEAgIRWBQY2B7oTBAwMzCYoRUBIIQBCQQGBHIYRUABwRRA=")), +}; + +const startingBuffers = [ + { + width: 49, + height: 63, + buffer: require("heatshrink").decompress(atob("iIAFzIAEyMRjICBABkZDAogCCxgRBxAACzOUpIzDFxmYC4WJzNEGYgyQzOd6lIDoJiNGQYwB7tEDoQxQI4NN7BPDGJ54BondQAgvPGCjDDGCQVCCAVESAOIx/4GJYuCSIQwBFwX/GJbyEAAmDn///AwJjLyDAAsz/4xBGBAvJGAfwhIwHF5QwDgAwHXwYADGB6+DAAY2EGBYzHGA8P+EAgLyKGBIBBgD3ObwVPGAMEoAwIe4+ZolEGAVEGBRfDAYOUpvd6gaBpvQF5IuBFoOJDANE7vdC4IEBF5h8DGAQABGQNBF5YuCGAgyDYZwxE6lDmcxR5gAFGINEmczF6IxCob1B+YxIF5AxCeoQxJzLNBAAQFBhOUoEDGAIxJjOQC4cJAoIvBgAwCGJIwWiEAgcwglEpOZGwgvCGBANBmcAaIJ2BC4cPC4QwIga3BmEIeoIwBDoJhDF4j5DBgIABgGIGAROBMIYvEilBGAgvDAARIBGQL0FA4IwFF4YABJQIyBoczF4lEGA0JCwWUEoPd7qBCzIwDgAwGF4YXBpvd6iyBzORGAVAgLEEF4YwCF4IwEMYMzGAjECC4YwBF4IwEycxic0GAsN7vRiMZDIQvDBwItBS4IqBGAgQBAgIXCzovCLwItBS4MwgAwCF4XTn/xGIiODF4QwCF40z/4xERwYvCGAQvF7vf+CUEEobvCAAJgEF4Xd/6UEXgTvDAANAMAwwFCwgwESBAwFC4owCF43f/vUoaSCABAvCMAv/aQKSDAA8UF4JgG7/0oIWJGAhgG/9EC5YwCSA5eKFQiQIC5sQF4QwD6hfMGpAvBL5g1IhovVGAQvUGAMEF6owBF6owB")), + }, + { + width: 42, + height: 62, + buffer: require("heatshrink").decompress(atob("iIADzIAEA4QNEAAwUSBwOIAAWZogXECjgWFzPdogECCj5pDondIggUeSAYUjCISRDCQX/CpIUTXQgAEmf/CoIUaCZIUDgAUZRoYADCkIXHCg4BBgAUZX4y+BogUCogUccwgTB7vdCwIEBFA4UTHQYACCgQACogoJCiYAFCoYTHCigTHCoQnICioaJCRwUUHYbhBHpwUTXoQTCmczCpgUTcwYUB/8zAIIUfgDeBCoQUC/4UeRYQPBCoITCHxQUXCYMAgAVBmboKCjCUCAAQPFToIUaMgOIAAIUGBoIUYCoS9DNYIVECYwUWmYVBcIYVBokRBQIUbBoLhB7pjFOYQTGCixpCAwidCCY4UVCoYGFCYIUeBwLaGCZYUVDQ4TRChA")), + }, + { + width: 34, + height: 60, + buffer: require("heatshrink").decompress(atob("iIADzIADBAgOECCALBxAABAYIUDxAQVCQQNBogVFCDAAB7oRFCCwPCogQcNoQQCBoP/CI4QPS4gACmf/CIQQTBwwQEgGIfoQQPfIg1ECC6XFCA4ACCCYRFolECAVECCq5CbobeBAAPdCCgPECAYADoggHCCIRFB4YQQXYwSDOgwQPAAoQDBwMzmYRICB5SCGIYQZB4h0CEBoQKmf/oj/CCQQgHCB4PBAAJ1CxAWBEBAQRmYPBAgJCKCCIyCfoYQXCIKQBB4ndGoIQWiL4BgDaBS4IPBGw4QPBwQSCAgMzCoScECCg4FAAQQVB4IREABAQSAAQtGCCoRDOQYiMCBpxGCCoA==")), + }, + { + width: 44, + height: 62, + buffer: require("heatshrink").decompress(atob("iIAEzIADBAgPFCrIQBxAABAYIZDxAVfC4QSBogaFCsQAB7oWFCsAUCogVmSAQVCCQP/CxgVUYggACmf/CwQVdCYwVEgGIgAVbSoQADCsgYGCowACCroWFolECoVECr7wCAALyB7oXBAYJCJCqgUECoYADokzmYWFCrYWFNoIVceQwXDS4QUBCrYACRwQVDAoIXBCrznCMwRAFAAYpFCqf/mf/CgKZFohQGCrIACS4hFCCQ3dIgIVXIQIUDxAVIBAIVXCYJAFxGITIIVfCwMziOZAIIXEAAIVeAAQFBgBFEDITADCrUAAAoeDCwP/IAX/CrAPBog/CJQoWBHwJsFCrAWBCYpqCCgQNECqwUBCpJrCBooVUIBgABEQwVWokzChRPDCrA/NKIgxJCp4FEAB4VVA")), + }, + { + width: 38, + height: 62, + buffer: require("heatshrink").decompress(atob("iIADzIADBAgOECSgNBxAABAYIWDxASZCgQPBogXFCTgAB7oTFCTQRCogSgPYQSCB4P/CZISRVIgACmf/CYQSXCAwSEgGIgASVdgbvDCToVGCQwACCS4TFolECQVECTLPCe4b4BAAPdCTARECQYADogkJCSYTFCIoSSaIwUDEYwSTAATKCCQa4GCSwRC/8zog3CehASYaIkAmYTGCSYTBCIISEBIITGCSStEZ4YSceo7PBQpASSBAQVFaBISRgChBAIIRCxAkKCSAPBeQI5CK4ITG7oJBCSgRCPoYSFCIISVTwaiEzIREEAISTABH/AAIDBG4YSce4IAB/43MCSQRD7q1CEhwSQRwS1CCTqzDWwgSTA==")), + }, + { + width: 40, + height: 60, + buffer: require("heatshrink").decompress(atob("iIADzIAEA4QNEAAoTRBoOIAAWZogWECbQVFzPdogECCbpkDondIIgTcRIYThCASKDCIX/ChATSWIgAEmf/CgITYCRITDgATXQwYADCb4WHCY4BBgATXWwy1BogTCogTabYgSB7vdCoIDBEwwTSG4YACB4QACogmICaQAFCgYSGCaYSHCgQlHCagZIWIISNCaQ5DmYTB/8zCbq0DogThbYgTDExwTQWIszmazKCagWFbQNEgATcHwpoBomZgAULCaYUFgA8SCZlE7q5DHIITdBQPda4ITDRpQTXMQoUJCbLhCCgUzCgwTWCgIJFMxATVCgQABBYoACCa4IEBQwAICZwA=")), + }, + { + width: 36, + height: 60, + buffer: require("heatshrink").decompress(atob("iIANzIPOCI+ZABWICKwTDxGZogDBAAIRaHAfdCQoRXKQdECLhWCBIQRBAgP/CQwRQB4YAFmf/CQQRUK4YRIgGIgARUdIoReCQgRGAAQRVCQtEogRCogRXBoLqEdYIAB7oRWCAczmYRCAAdEEQ4RNOIR4CCQoQECKJxBogQCCIJcCPYPdCKo1CCIwIBG4QRUSYZ8BGgszUoQRSFIQSBdQbGDQQIVBCKIQCGILJCCAYRBRIKuCCKf/xARIiIRYK4ZYExBYDCKjDBxGICQdEAYQQCCKjcBBQKjFGgayDCKYAGT4gRSCQYRICAgRVCRYRYcIIRdCQSZCG44YFCKArCEiARQAAYTCChwRKA==")), + }, + { + width: 34, + height: 60, + buffer: require("heatshrink").decompress(atob("iIADzIADBAgOECCALBxAABAYIUDxAQVCQQNBogVFCDAAB7oRFCCwPCogQcNoQQCBoP/CI4QPS4gACmf/CIQQTBwwQEgGIfoQQPfIg1ECC6XFCA4ACCCYRFolECAVECCq5C7oADCQLABCCgPBBwkACgogECBwJFCAoPDCCAPFJgJ5BKYJjECCBqBBwf/mYQBAoMzmYRCCCIpCBAIPCA4gQSB4RrBboY6CoggGCCC+DB4YQBOogQRXwYQGU4YQUSYRTEdQgQTWYQzDB4gQVKooABBwYQSRAIQECYgQCHoQQWHQIQDXYLdDCB4RELYQPDCCyOGXYIPKCCAPGCAIgOCBpBLCCKyDBxYQMA==")), + }, + { + width: 36, + height: 60, + buffer: require("heatshrink").decompress(atob("iIANzIPOCI+ZABWICKwTDxGZogDBAAIRaHAfdCQoRXKQdECLhWCBIQRBAgP/CQwRQB4YAFmf/CQQRUK4YRIgGIgARUdIoReCQgRGAAQRVCQtEogRCogRXBoLqEdYIAB7oRWCAczmYRCAAdEEQ4RNOIR4CCQoQECKJxBogQCCIJcCPYwRRGoQRGBAITCCKoACPgI0FUYQSGCJpNDdQbGDQQQRTJooQECIIMBCMMRCKhwDPIMALAoZBCLQADxAABCAQRXogOBUYgUBCLTIDAAQSB/4RYB4oAB/4QDWQYROYowADgDHCCLASGCIYJDCKI2MDQwRQCQgcGQwwRRBAIAGB44RPA==")), + }, + { + width: 34, + height: 60, + buffer: require("heatshrink").decompress(atob("iIADzIADBAgOECCALBxAABAYIUDxAQVCQQNBogVFCDAAB7oRFCCwPCogQcNoQQCBoP/CI4QPS4gACmf/CIQQTBwwQEgGIfoQQPfIg1ECC6XFCA4ACCCYRFolECAVECCq5CbobeBAAPdCCgPDmczCAQADoggGCBkAA4RPBCIoPDCCAPDCAZXCOgoQQJYgQEBwJbBCIQQUBAIxFCCgPDboi7DEA4QPXwYPDCAJ1GCDQJCA4YQPB4hTEdQYQXgCXDB4YQVawQDBxAFBBwYQVAAYQBGAoABCCCqBBAcAAAIQGXYQQPCIcAYApACCCgPFAAX/XoQ5FCCwPBGQIPFCCIREmYOCB44QRTAabHCCAA==")), + }, +]; + +const stoppingBuffers = [ + { + width: 44, + height: 60, + buffer: require("heatshrink").decompress(atob("iIAEzIACA4gOFAAwVTCQVEzOIDIdECsAWC7oWCAAIVkogVFCwIVgNggAECsARCAQX//4VC/4WJCqg8FCgIWCAQIVdM43/gEACYICBCriVBAAQVmIg4VmIQdETAVEogZBogVhzNE7oTB7oACCoYbBCrCbECQgVFIoIVeCwcACg4VbN4xrGCrgCBBwMAC4QNBBwYVeM4IABIYYABBgIUGCqv/iK1CCQOINwdEAwIVDC4QVVmf/G4QNBCooFCCoRRBCqv//4WBCpJsECrb0DC4QVDYYgHBCq8zCohEDNAIGBCrgTBCob4DZAQGCCgYZBCqoICAAqUBdwQUCKYLaCCsACBCwP/IAP/IBQVVCgQECCgRrBmYVfAAhACe4QUOCqzwCCoKCLCrYWDCiIVWeQYVY")), + }, + { + width: 48, + height: 60, + buffer: require("heatshrink").decompress(atob("iIAEzIAFBIYQFAAwXXDAVEzOIxAaCogXlDAPdDAYZBC8wPBC4oYBC8h2FC84VBB4QCB///C4X/DBQXWIIwWBDAQCBC8AWGCQMAgAVBAQIXgOwR4DC9JLGC83/JIuIoimColEDYNEC7wYCHYIACondCoPdAAQXDDoIXYCoT7CC4gAFC4RLBC7IWHDAkACw4XZToY9CJI51EC7ZICAoIJBCAMADIQJCC8IADCAQXDbIJHBDgIWJC7AABCgOIPAdEAwIXDDJIXWCwQXFAoQXCcIIXnOwgXjDIQXDaogHBC8JKDOQIGBC8zaGAwQWDDYIXeDYkADAIPBUoX/IxAXeAQIYB/5GB/5GMC64WCAgQWCOoMzC8YAEIwVEapIXhDANEC4KONC7oYDCyYXYDAIABC7QA==")), + }, +]; + +const waitingBuffers = [ + { + width: 36, + height: 62, + offset: -1, + buffer: require("heatshrink").decompress(atob("iIADzIACA4gNECKYOComZxAVDogRXBQfdCQQABCLITCogRFCQIRYLAgAFCQwRQBoR7C/5aECKwwF/4ABBAQDBCK4TD/8AgH/HQYRUYYYRGzICCCKgUECIoDBCLA3DolEB4IDBAAQRb7oMB7vdHIIRWCAYRCAAoRWCoozDCBQRRBQI3FERIRPPIbGBPIQiICKAQCdQRZDAYIRXCQIJB/4RDEhQRQGYP//7FEGpARQCAYREgEzERQRNAAIRCLIKkBCLg4CYoIABB5IRWAQQRcBoIODCRYRQB4INECLkRgH/ZAMACRgRSmbGBCIIFBmYWCCLAAC7oCBdgNEAgIRWBQY2B7oTBAwMzCYoRUBIIQBCQQGBHIYRUABwRRA")), + }, + { + width: 38, + height: 62, + offset: 1, + buffer: require("heatshrink").decompress(atob("iIADzIACA4gNECSoPComZxAWDogSZBYfdCYQABCTYUCogSFCYISaLogAFCY4SRBwSCC/5fECTAzF/4ABBAQDBCTIUD/8AgH/HoYSWZwYSGzICCCSwVECQoDBCTQ6DolECAIDBAAQSd7oNB7vdHgISYCIYSCAAoSYCwo2DCJYSSBYI6FEhQSQQAbRBQAQkJCSIRCe4ReDAYISZCYIKB/4SDExYSRGwP//7QEHBISRCIYSEgEzEhYSOAAISCLwKtBCTw7CaAIABCBQSXAQQSeBwIPDCZgSRCAIOECT0RgH/aYMACYUzAgQSambRBBoIFBmdEgAAECSwAC7oCBfINECgIABCSwLDHIPdCgIRDEYgSWBYIRBCYSFBQwwSTABKpICR4A=")), + }, +]; + +const bg = { + width: 384, + height: 153, + bpp: 8, + transparent: 254, + buffer: require("heatshrink").decompress(atob("i4ASj0evF4pFIDKYA/AEp//AH5//AH5//AH5//AH5//AH5//AH5//AH5//AH5//AH5//AH5//AH5//AH5//AH5//AH5//AH5//AH5//AH5//AH5//AH5//AH5/bx+PB5vX64RFC54v3P/4v/P/4v/P/4v/P/4v/P/4v7CY5/VHIKxZACYvvP/4v/P/4v/P4/GAAR/BAAJP/P/5P/P/5P/F8sjkYnBO4oAHP64vHGKJfdF7p//P/5//P/4dTP4dIpB//P/5//P/5//F+p/TIIpDXMIohLF95//P/5//P/4APj0eP/5//P/5//P/4vxB4Z/XAH4AfP/4A/P/4A/P/4A/P/4A/P/4A/AB5/JTIaXJWZobFDpovvP/5//P/5//P/5//P/5//P/5//AAIPFCpp/JEpoABIIZHBL7IvvR45//P/5//P/5//P/5//P/5//P/IbTP6IAbJIpLXF+Z//F/5//F/5//F/5//F/5//F/5/tAH5//AH5//AH5//AH5//AH5//AH5//AH5//AH5//AH4AP4wACP4IABI/5//I/5//I/5//I/5//I/5//I/6B7PoNIpBF/P/5F/P/5F/P/5F/P/5F/P/5F/AHMejx//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//P/5//AHfGAAR/BAAJH/P/5H/P/5H/P/5H/P/5H/P/5H/QPZ9BpFIIv5//Iv4Aj6XSP/4A/P/4A/P/4A96XSP/4A/ACcejx//P/5//AEplB6XSAYJ//AH5//AH5//AHJlB6XSAYJ//AH5//AH5//AGPSAARhBA4oABA4ILBA4p//AH5//AH5//AE/SAARhBA4oABA4ILBA4p//AH5//AH5//AE5dB6QACAofX64HFAoZ//AH5//AH5//AFJdB6QACAofX64HFAoZ//AH5//AH5//AFJdB6XS6/XAYIFLCYJ//AH5//AH5//AFJdB6XS6/XAYIFLCYJ//AH5//AH5//AFZfB6XS6/XhgAEA4ILBB4J//AH5//AH5//AFpfB6XS6/XhgAEA4ILBB4J//AH5//AH5//AE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")), +}; + +const topSpeed = 15; +const timeout = 200; +let currentSpeed = 0; +let currentSonic = -1; + +let drawTimeout, drawInterval, waitTimeout; +let bgScroll = [0, null]; + +const start = () => { + if (drawTimeout) clearTimeout(drawTimeout); + if (waitTimeout) clearTimeout(waitTimeout); + if (drawInterval) clearInterval(drawInterval); + + drawInterval = setInterval(() => { + draw("start"); + bgScroll[0] += currentSpeed; + if (bgScroll[1]) bgScroll[1] += currentSpeed; + if (currentSpeed < topSpeed) currentSpeed++; + }, timeout); +}; + +const stop = () => { + if (drawTimeout) clearTimeout(drawTimeout); + if (drawInterval) clearInterval(drawInterval); + + drawInterval = setInterval(() => { + if (currentSpeed <= 0) { + clearInterval(drawInterval); + draw("reset"); + } else { + draw("stop"); + bgScroll[0] += currentSpeed; + if (bgScroll[1]) bgScroll[1] += currentSpeed; + currentSpeed--; + } + }, timeout); +}; + +const wait = () => { + currentSonic = -1; + currentSpeed = 0; + if (drawTimeout) clearTimeout(drawTimeout); + if (drawInterval) clearInterval(drawInterval); + Bangle.setLCDPower(1); + + drawInterval = setInterval(() => draw("wait"), timeout); + + waitTimeout = setTimeout(() => { + clearInterval(drawInterval); + currentSonic = -1; + draw("reset"); + }, 7500); +}; + +const queueDraw = () => { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function () { + drawTimeout = undefined; + draw("reset"); + }, 60000 - (Date.now() % 60000)); +}; + +const drawSonic = (action) => { + let target; + + if (action === "reset" || currentSonic === -1) { + target = defaultSonic; + } else if (action === "start") { + target = startingBuffers[currentSonic]; + } else if (action === "stop") { + if (currentSonic > 1) currentSonic = 0; + target = stoppingBuffers[currentSonic]; + } else { + target = waitingBuffers[currentSonic]; + } + + sonic.width = target.width; + sonic.height = target.height; + sonic.buffer = target.buffer; + sonic.offset = target.offset; + + g.drawImage( + sonic, + vw / 2 - 30 + (50 - sonic.width) + (sonic.offset || 0), + 86 + (65 - sonic.height) + ); + + if (action === "start") { + if (currentSonic === startingBuffers.length - 1) { + currentSonic = 6; + } else { + currentSonic++; + } + } else if (action === "stop") { + if (currentSpeed <= 2) { + currentSonic = -1; + } else if (currentSpeed <= 14) { + currentSonic = 1; + } else { + currentSonic = 0; + } + } else { + if (currentSonic === waitingBuffers.length - 1) { + currentSonic = 0; + } else { + currentSonic++; + } + } +}; + +const drawTime = () => { + const x = vw / 2; + const y = 24 + 25; + + const date = new Date(); + const timeStr = require("locale").time(date, 1).trim(); + const dateStr = require("locale").date(date).toUpperCase(); + + g.setColor("#000"); + g.setFontAlign(0, 0).setFont("6x8", 5); + g.drawString(timeStr, x + 3, y + 2); + + g.setFont("6x8", 1.5); + g.drawString(dateStr, x + 1, y + 29); + + g.setColor("#fff"); + g.setFontAlign(0, 0).setFont("6x8", 5); + g.drawString(timeStr, x, y); + + g.setFont("6x8", 1.5); + g.drawString(dateStr, x, y + 28); +}; + +const draw = (action) => { + if (bgWidth - bgScroll[0] < 0) { + bgScroll[0] = bgScroll[1]; + bgScroll[1] = null; + } + + g.drawImage(bg, -bgScroll[0], 24); + + if (bgWidth - bgScroll[0] < vw) { + bgScroll[1] = bgScroll[0] - bgWidth; + g.drawImage(bg, -bgScroll[1], 24); + } + + drawSonic(action); + drawTime(); + if (action === "reset") queueDraw(); +}; + +g.setTheme({ bg: "#0099ff", fg: "#fff", dark: true }).clear(); + +Bangle.on("lock", (locked) => { + if (locked) { + stop(); + } else { + start(); + } +}); + +Bangle.on("twist", () => wait()); + +Bangle.setOptions({ + lockTimeout: 10000, + backlightTimeout: 12000, + twistThreshold: 1600, +}); + +Bangle.setUI("clock"); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +draw("reset"); + +if (Bangle.isLocked()) { + stop(); +} else { + start(); +} diff --git a/apps/sonicclk/app.png b/apps/sonicclk/app.png new file mode 100644 index 000000000..db359d1e5 Binary files /dev/null and b/apps/sonicclk/app.png differ diff --git a/apps/sonicclk/screenshot.png b/apps/sonicclk/screenshot.png new file mode 100644 index 000000000..63173989a Binary files /dev/null and b/apps/sonicclk/screenshot.png differ diff --git a/apps/touchmenu/ChangeLog b/apps/touchmenu/ChangeLog new file mode 100644 index 000000000..c5277e465 --- /dev/null +++ b/apps/touchmenu/ChangeLog @@ -0,0 +1 @@ +0.01: App launched diff --git a/apps/touchmenu/README.md b/apps/touchmenu/README.md new file mode 100644 index 000000000..0e81f3755 --- /dev/null +++ b/apps/touchmenu/README.md @@ -0,0 +1,40 @@ +# TouchMenu + +A redesign of the built-in `E.showMenu()` to take advantage of the full touch screen on the Bangle.js 2. + +![screenshot](touchmenu.gif) + +## Features + +- All of the features of the built-in `E.showMenu()` +- Icon support for menu items: + ```javascript + menu.items[0].icon = Graphics.createImage(...); + ``` +- Custom accent colors: + ```javascript + E.showMenu({ + "": { + cAB: g.theme.bg2, // Accent background + cAF: g.theme.fg2 // Accent foreground + } + }) + ``` +- Automatic back button detection - name a button `< Back` and it will be given a special position and icon + +## Controls + +- Scroll through the options +- Tap on an option to select it +- Tap on a button again to use it +- Tap on a selected Boolean to toggle it +- Tap on a selected number to change - tap the right side of the screen to decrease, left side to increase +- If detected, tap on the back button in the upper left to go back + +## Requests + +Contact information is on my website: [kyleplo](https://kyleplo.com) + +## Creator + +[kyleplo](https://kyleplo.com) diff --git a/apps/touchmenu/touchmenu.boot.js b/apps/touchmenu/touchmenu.boot.js new file mode 100644 index 000000000..93a0ba1c8 --- /dev/null +++ b/apps/touchmenu/touchmenu.boot.js @@ -0,0 +1,197 @@ +E.showMenu = function(items) { + const gw = g.getWidth(); + const gh = g.getHeight(); + Bangle.removeAllListeners("drag"); + if(!items){ + delete m; + g.clearRect(0, 30, gw, gh - 30); + return false; + } + var loc = require("locale"); + var m = { + info: { + title: "Menu", + cB: g.theme.bg, + cF: g.theme.fg, + cHB: g.theme.bgH, + cHF: g.theme.fgH, + cAB: g.theme.bg2, + cAF: g.theme.fg2, + predraw : () => {}, + preflip : () => {} + }, + scroll: 0, + items: [], + selected: -1, + draw: () => { + g.reset().setFont('12x20'); + m.info.predraw(g); + g.setColor(m.info.cB).fillRect(0, 50, gw, gh - 30).setColor(m.info.cF); + m.items.forEach((e, i) => { + const s = (i * 48) - m.scroll + 50; + if(s < 30 || s > gh - 74){ + return false; + } + if(i == m.selected){ + g.setColor(m.info.cHB).fillRect(0, s, gw, Math.min(s + 48, gh - 30)).setColor(m.info.cHF); + }else{ + g.setColor(m.info.cF); + } + g.drawString(e.title, (e.icon ? 30 : 10), s + 5); + if(e.icon){ + g.drawImage(e.icon, 5, s + 5); + } + if(e.type && s < gh - 72){ + if(e.format){ + g.setFontAlign(1, -1, 0).drawString(e.format(e.value), gw - 10, s + 25).setFontAlign(-1, -1, 0); + }else{ + g.setFontAlign(1, -1, 0).drawString(e.value, gw - 10, s + 25).setFontAlign(-1, -1, 0); + } + } + }); + g.setColor(m.info.cAB).fillRect(0, 30, gw, 50); + g.setColor(m.info.cAF).drawString(m.info.title, (m.back ? 30 : 10), 32); + if(m.back){ + g.drawLine(5, 40, 20, 40); + g.drawLine(5, 40, 15, 33); + g.drawLine(5, 40, 15, 47); + } + m.info.preflip(g, m.scroll > 0, m.scroll < (m.items.length - 1) * 48); + }, + select: (x, y) => { + if(m.selected == -1 || m.selected !== Math.max(Math.min(Math.floor((y + m.scroll - 50) / 48), m.items.length - 1), 0)){ + if(y){ + if(y < 50 || y > gh - 30){ + return false; + }else{ + m.selected = Math.max(Math.min(Math.floor((y + m.scroll - 50) / 48), m.items.length - 1), 0); + } + }else{ + m.selected = Math.floor(m.scroll / 48); + } + m.draw(); + }else{ + if(m.items[m.selected].type && m.items[m.selected].type === "boolean"){ + m.items[m.selected].value = !m.items[m.selected].value; + m.items[m.selected].onchange(m.items[m.selected].value); + m.draw(); + }else if(m.items[m.selected].type && m.items[m.selected].type === "number"){ + if(x && x < (gw / 2)){ + m.items[m.selected].value = m.items[m.selected].value - (m.items[m.selected].step ? m.items[m.selected].step : 1); + }else{ + m.items[m.selected].value = m.items[m.selected].value + (m.items[m.selected].step ? m.items[m.selected].step : 1); + } + if(m.items[m.selected].value > (m.items[m.selected].max ? m.items[m.selected].max : Infinity)){ + m.items[m.selected].value = m.items[m.selected].min ? m.items[m.selected].min : 0; + } + if(m.items[m.selected].value < (m.items[m.selected].min ? m.items[m.selected].min : 0)){ + m.items[m.selected].value = m.items[m.selected].max ? m.items[m.selected].max : 10; + } + m.items[m.selected].onchange(m.items[m.selected].value); + m.draw(); + }else{ + if(m.items[m.selected]){ + m.items[m.selected](); + } + } + } + }, + move: d => { + m.scroll += (d * 48); + m.scroll = Math.min(Math.max(m.scroll, 0), (m.items.length - 1) * 48); + m.selected = Math.max(Math.min(Math.floor((m.scroll - 50) / 48), m.items.length - 1), 0); + m.draw(); + }, + }; + Object.keys(items).forEach(i => { + if(i == ""){ + m.info = Object.assign(m.info, items[i]); + }else if(i === "< Back" && items[i]){ + m.back = items[i]; + }else if(items[i]){ + m.items.push(items[i]); + m.items[m.items.length - 1].title = loc.translate(i); + if(items[i].hasOwnProperty("value")){ + if(typeof items[i].value === "boolean"){ + m.items[m.items.length - 1].type = "boolean"; + }else{ + m.items[m.items.length - 1].type = "number"; + } + } + } + }); + m.info.title = loc.translate(m.info.title); + m.draw(); + Bangle.on("drag", d => { + if(!d.b){ + return false; + } + if(d.dx == 0 && d.dy == 0){ + if(d.x < 30 && d.y < 50){ + m.back(); + return false; + } + m.select(d.x, d.y); + }else{ + m.selected = -1; + m.scroll -= d.dy; + m.scroll = Math.min(Math.max(m.scroll, 0), (m.items.length - 1) * 48); + m.draw(); + } + }); + return m; +}; + +E.showAlert = function (e, t){ + if(!e){ + E.showMenu(); + return false; + } + return new Promise(r => { + const menu = { + "": { + "title": (t ? t : "Alert") + }, + Ok: () => { + E.showMenu(); + r(); + } + }; + menu[e] = () => {}; + E.showMenu(menu); + }); +}; +E.showMessage = E.showAlert; + +E.showPrompt = function (e, t){ + if(!e){ + E.showMenu(); + return false; + } + return new Promise(r => { + const menu = { + "": { + "title": (t && t.title ? t.title : "Choose") + } + }; + menu[e] = () => {}; + if(t && t.buttons){ + Object.keys(t.buttons).forEach(b => { + menu[b] = () => { + E.showMenu(); + r(t.buttons[b]); + }; + }); + }else{ + menu.Yes = () => { + E.showMenu(); + r(true); + }; + menu.No = () => { + E.showMenu(); + r(false); + }; + } + E.showMenu(menu); + }); +}; diff --git a/apps/touchmenu/touchmenu.gif b/apps/touchmenu/touchmenu.gif new file mode 100644 index 000000000..3df4b3462 Binary files /dev/null and b/apps/touchmenu/touchmenu.gif differ diff --git a/apps/touchmenu/touchmenu.png b/apps/touchmenu/touchmenu.png new file mode 100644 index 000000000..58733cbc7 Binary files /dev/null and b/apps/touchmenu/touchmenu.png differ diff --git a/apps/widbatpc/ChangeLog b/apps/widbatpc/ChangeLog index 99822b5a9..273e611a4 100644 --- a/apps/widbatpc/ChangeLog +++ b/apps/widbatpc/ChangeLog @@ -11,3 +11,4 @@ 0.12: Fixed for Bangle 2 0.13: Fillbar setting added, see README 0.14: Fix drawing the bar when charging +0.15: Added option to always display the icon when charging (useful if 'hide if charge greater than' is enabled) diff --git a/apps/widbatpc/settings.js b/apps/widbatpc/settings.js index b7a5db9e6..b45fc6749 100644 --- a/apps/widbatpc/settings.js +++ b/apps/widbatpc/settings.js @@ -13,6 +13,7 @@ 'fillbar': false, 'charger': true, 'hideifmorethan': 100, + 'alwaysoncharge': false, } // ...and overwrite them with any saved values // This way saved values are preserved if a new version adds more settings @@ -68,6 +69,11 @@ format: x => x+"%", onchange: save('hideifmorethan'), }, + 'Show on charge': { // Not sure if this is readable enough in the 'big' menu + value: s.alwaysoncharge, + format: onOffFormat, + onchange: save('alwaysoncharge'), + }, } E.showMenu(menu) }) diff --git a/apps/widbatpc/widget.js b/apps/widbatpc/widget.js index 3e5ff47b4..5386ffe22 100644 --- a/apps/widbatpc/widget.js +++ b/apps/widbatpc/widget.js @@ -29,6 +29,7 @@ 'percentage': true, 'charger': true, 'hideifmorethan': 100, + 'alwaysoncharge': false, }; Object.keys(DEFAULTS).forEach(k=>{ if (settings[k]===undefined) settings[k]=DEFAULTS[k] @@ -67,8 +68,11 @@ var w = 40; if (Bangle.isCharging() && setting('charger')) w += 16; - if (E.getBattery() > setting('hideifmorethan')) + if (E.getBattery() > setting('hideifmorethan')) { w = 0; + if( Bangle.isCharging() && setting('alwaysoncharge') === true) + w = 56; + } var changed = WIDGETS["batpc"].width != w; WIDGETS["batpc"].width = w; return changed; diff --git a/core b/core index 5a5957714..ae9586977 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 5a5957714d4aa04413329f57c03e6de0cfb74caf +Subproject commit ae9586977948279d267f2749bf3a48d3aa753c11 diff --git a/loader.js b/loader.js index 768f5f38f..a0c280634 100644 --- a/loader.js +++ b/loader.js @@ -40,7 +40,7 @@ function onFoundDeviceInfo(deviceId, deviceVersion) { if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") { showToast(`You're using ${deviceId}, not a Bangle.js. Did you want espruino.com/apps instead?` ,"warning", 20000); } else if (versionLess(deviceVersion, RECOMMENDED_VERSION)) { - showToast(`You're using an old Bangle.js firmware (${deviceVersion}). You can update with the instructions here` ,"warning", 20000); + showToast(`You're using an old Bangle.js firmware (${deviceVersion}) and ${RECOMMENDED_VERSION} is available (see changes). You can update with the instructions here` ,"warning", 20000); }