Merge branch 'espruino:master' into master

pull/1070/head
stephenPspackman 2021-12-12 14:55:38 -08:00 committed by GitHub
commit 14e221fbb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
173 changed files with 4449 additions and 609 deletions

318
apps.json
View File

@ -1,8 +1,8 @@
[
{
"id": "fwupdate",
"name": "Firmware Update (BETA)",
"version": "0.01",
"name": "Firmware Update",
"version": "0.02",
"description": "Uploads new Espruino firmwares to Bangle.js 2",
"icon": "app.png",
"type": "RAM",
@ -16,7 +16,7 @@
{
"id": "boot",
"name": "Bootloader",
"version": "0.37",
"version": "0.38",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png",
"type": "bootloader",
@ -29,10 +29,35 @@
],
"sortorder": -10
},
{
"id": "hebrew_calendar",
"name": "Hebrew Calendar",
"shortName": "HebCal",
"version": "0.03",
"description": "lists the date according to the hebrew calendar",
"icon": "app.png",
"tags": "",
"supports": [
"BANGLEJS",
"BANGLEJS2"
],
"readme": "README.md",
"storage": [
{
"name": "hebrew_calendar.app.js",
"url": "app.js"
},
{
"name": "hebrew_calendar.img",
"url": "app-icon.js",
"evaluate": true
}
]
},
{
"id": "messages",
"name": "Messages",
"version": "0.07",
"version": "0.11",
"description": "App to display notifications from iOS and Gadgetbridge",
"icon": "app.png",
"type": "app",
@ -54,7 +79,7 @@
"name": "Android Integration",
"shortName": "Android",
"version": "0.04",
"description": "(BETA) App to display notifications from Gadgetbridge on Android. This will eventually replace the Gadgetbridge widget.",
"description": "Display notifications/music/etc from Gadgetbridge on Android. This replaces the old Gadgetbridge widget.",
"icon": "app.png",
"tags": "tool,system,messages,notifications",
"dependencies": {"messages":"app"},
@ -70,8 +95,8 @@
{
"id": "ios",
"name": "iOS Integration",
"version": "0.03",
"description": "(BETA) App to display notifications from iOS devices",
"version": "0.06",
"description": "Display notifications/music/etc from iOS devices",
"icon": "app.png",
"tags": "tool,system,ios,apple,messages,notifications",
"dependencies": {"messages":"app"},
@ -104,7 +129,7 @@
"id": "launch",
"name": "Launcher",
"shortName": "Launcher",
"version": "0.09",
"version": "0.10",
"description": "This is needed to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.",
"icon": "app.png",
"type": "launch",
@ -112,14 +137,16 @@
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"launch.app.js","url":"app-bangle1.js","supports":["BANGLEJS"]},
{"name":"launch.app.js","url":"app-bangle2.js","supports":["BANGLEJS2"]}
{"name":"launch.app.js","url":"app-bangle2.js","supports":["BANGLEJS2"]},
{"name":"launch.settings.js","url":"settings.js","supports":["BANGLEJS2"]}
],
"data": [{"name":"launch.json"}],
"sortorder": -10
},
{
"id": "setting",
"name": "Settings",
"version": "0.34",
"version": "0.36",
"description": "A menu for setting up Bangle.js",
"icon": "settings.png",
"tags": "tool,system",
@ -170,7 +197,7 @@
{
"id": "locale",
"name": "Languages",
"version": "0.10",
"version": "0.13",
"description": "Translations for different countries",
"icon": "locale.png",
"type": "locale",
@ -236,16 +263,17 @@
"id": "mywelcome",
"name": "Customised Welcome",
"shortName": "My Welcome",
"version": "0.12",
"version": "0.13",
"description": "Appears at first boot and explains how to use Bangle.js. Like 'Welcome', but can be customised with a greeting",
"icon": "app.png",
"tags": "start,welcome",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS","BANGLEJS2"],
"custom": "custom.html",
"screenshots": [{"url":"bangle1-customized-welcome-screenshot.png"}],
"storage": [
{"name":"mywelcome.boot.js","url":"boot.js"},
{"name":"mywelcome.app.js","url":"app.js"},
{"name":"mywelcome.app.js","url":"app-bangle1.js","supports": ["BANGLEJS"]},
{"name":"mywelcome.app.js","url":"app-bangle2.js","supports": ["BANGLEJS2"]},
{"name":"mywelcome.settings.js","url":"settings.js"},
{"name":"mywelcome.img","url":"app-icon.js","evaluate":true}
],
@ -255,7 +283,7 @@
"id": "gbridge",
"name": "Gadgetbridge",
"version": "0.24",
"description": "The default notification handler for Gadgetbridge notifications from Android. This will eventually be replaced by the 'Android' app.",
"description": "(NOT RECOMMENDED) Handles Gadgetbridge notifications from Android. This is now replaced by the 'Android' app.",
"icon": "app.png",
"type": "widget",
"tags": "tool,system,android,widget",
@ -463,22 +491,22 @@
]
},
{
"id": "mandlebrotclock",
"name": "Mandlebrot Clock",
"id": "mandelbrotclock",
"name": "Mandelbrot Clock",
"version": "0.01",
"description": "A mandlebrot set themed clock cool",
"icon": "mandlebrotclock.png",
"screenshots": [{ "url": "screenshot_mandlebrotclock.png" }],
"description": "A mandelbrot set themed clock cool",
"icon": "mandelbrotclock.png",
"screenshots": [{ "url": "screenshot_mandelbrotclock.png" }],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{ "name": "mandlebrotclock.app.js", "url": "mandlebrotclock.js" },
{ "name": "mandelbrotclock.app.js", "url": "mandelbrotclock.js" },
{
"name": "mandlebrotclock.img",
"url": "mandlebrotclock-icon.js",
"name": "mandelbrotclock.img",
"url": "mandelbrotclock-icon.js",
"evaluate": true
}
]
@ -699,10 +727,11 @@
{
"id": "gpsrec",
"name": "GPS Recorder",
"version": "0.26",
"version": "0.27",
"description": "Application that allows you to record a GPS track. Can run in background",
"icon": "app.png",
"tags": "tool,outdoors,gps,widget",
"screenshots": [{"url":"screenshot.png"}],
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"interface": "interface.html",
@ -795,7 +824,7 @@
{
"id": "weather",
"name": "Weather",
"version": "0.11",
"version": "0.12",
"description": "Show Gadgetbridge weather report",
"icon": "icon.png",
"screenshots": [{"url":"screenshot.png"}],
@ -1098,7 +1127,7 @@
{
"id": "qrcode",
"name": "Custom QR Code",
"version": "0.02",
"version": "0.03",
"description": "Use this to upload a customised QR code to Bangle.js",
"icon": "app.png",
"tags": "qrcode",
@ -1726,7 +1755,7 @@
"id": "cliock",
"name": "Commandline-Clock",
"shortName": "CLI-Clock",
"version": "0.14",
"version": "0.15",
"description": "Simple CLI-Styled Clock",
"icon": "app.png",
"screenshots": [{"url":"screenshot_cli.png"}],
@ -1908,6 +1937,19 @@
{"name":"widmp.wid.js","url":"widget.js"}
]
},
{
"id": "widmpsh",
"name": "Moon Phase Widget Southern Hemisphere",
"version": "0.01",
"description": "Display the current moon phase in blueish for the southern hemisphere in eight phases",
"icon": "widget.png",
"type": "widget",
"tags": "widget,tools",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"widmpsh.wid.js","url":"widget.js"}
]
},
{
"id": "minionclk",
"name": "Minion clock",
@ -1928,11 +1970,12 @@
"id": "openstmap",
"name": "OpenStreetMap",
"shortName": "OpenStMap",
"version": "0.10",
"description": "[BETA] Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are",
"version": "0.11",
"description": "Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are. Once installed this also adds map functionality to `GPS Recorder` and `Recorder` apps",
"icon": "app.png",
"tags": "outdoors,gps",
"tags": "outdoors,gps,osm",
"supports": ["BANGLEJS","BANGLEJS2"],
"screenshots": [{"url":"screenshot.png"}],
"custom": "custom.html",
"customConnect": true,
"storage": [
@ -1962,11 +2005,12 @@
"id": "chronowid",
"name": "Chrono Widget",
"shortName": "Chrono Widget",
"version": "0.03",
"version": "0.04",
"description": "Chronometer (timer) which runs as widget.",
"icon": "app.png",
"tags": "tool,widget",
"supports": ["BANGLEJS","BANGLEJS2"],
"screenshots": [{"url":"screenshot.png"}],
"readme": "README.md",
"storage": [
{"name":"chronowid.wid.js","url":"widget.js"},
@ -2055,12 +2099,12 @@
"id": "numerals",
"name": "Numerals Clock",
"shortName": "Numerals Clock",
"version": "0.09",
"version": "0.10",
"description": "A simple big numerals clock",
"icon": "numerals.png",
"type": "clock",
"tags": "numerals,clock",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true,
"screenshots": [{"url":"bangle1-numerals-screenshot.png"}],
"storage": [
@ -2100,6 +2144,19 @@
{"name":"snake.img","url":"snake-icon.js","evaluate":true}
]
},
{ "id": "snek",
"name": "The snek game",
"shortName":"Snek",
"version": "0.01",
"description": "A snek game where you control a snek to eat all the apples!",
"icon": "snek-icon.js",
"supports": ["BANGLEJS2"],
"tags": "game,fun",
"storage": [
{"name":"snek.app.js","url":"snek.js"},
{"name":"snek.img","url":"snek-icon.js","evaluate":true}
]
},
{
"id": "calculator",
"name": "Calculator",
@ -2349,7 +2406,7 @@
{
"id": "calendar",
"name": "Calendar",
"version": "0.02",
"version": "0.03",
"description": "Simple calendar",
"icon": "calendar.png",
"screenshots": [{"url":"screenshot_calendar.png"}],
@ -2359,8 +2416,10 @@
"allow_emulator": true,
"storage": [
{"name":"calendar.app.js","url":"calendar.js"},
{"name":"calendar.settings.js","url":"settings.js"},
{"name":"calendar.img","url":"calendar-icon.js","evaluate":true}
]
],
"data": [{"name":"calendar.json"}]
},
{
"id": "hidjoystick",
@ -2598,12 +2657,12 @@
"id": "widviz",
"name": "Widget Visibility Widget",
"shortName": "Viz Widget",
"version": "0.02",
"version": "0.03",
"description": "Swipe left to hide top bar widgets, swipe right to redisplay.",
"icon": "eye.png",
"type": "widget",
"tags": "widget",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"widviz.wid.js","url":"widget.js"}
]
@ -3736,7 +3795,7 @@
"id": "gbmusic",
"name": "Gadgetbridge Music Controls",
"shortName": "Music Controls",
"version": "0.07",
"version": "0.08",
"description": "Control the music on your Gadgetbridge-connected phone",
"icon": "icon.png",
"screenshots": [{"url":"screenshot_v1.png"},{"url":"screenshot_v2.png"}],
@ -4036,7 +4095,7 @@
{"name":"carcrazy.img","url":"app-icon.js","evaluate":true},
{"name":"carcrazy.settings.js","url":"settings.js"}
],
"data": [{"name":"app.json"}]
"data": [{"name":"CarCrazy.csv"}]
},
{
"id": "shortcuts",
@ -4296,7 +4355,7 @@
"version": "0.01",
"description": "A touch based GPS watch, shows OS map reference",
"icon": "gpstouch.png",
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}],
"screenshots": [{"url":"screenshot4.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot1.png"}],
"tags": "tools,app",
"supports": ["BANGLEJS2"],
"readme": "README.md",
@ -4343,7 +4402,7 @@
"id": "emojuino",
"name": "Emojuino",
"shortName": "Emojuino",
"version": "0.02",
"version": "0.03",
"description": "Emojis & Espruino: broadcast Unicode emojis via Bluetooth Low Energy.",
"icon": "emojuino.png",
"screenshots": [
@ -4365,7 +4424,7 @@
"id": "cliclockJS2Enhanced",
"name": "Commandline-Clock JS2 Enhanced",
"shortName": "CLI-Clock JS2",
"version": "0.02",
"version": "0.03",
"description": "Simple CLI-Styled Clock with enhancements. Modes that are hard to use and unneded are removed (BPM, battery info, memory ect) credit to hughbarney for the original code and design. Also added HID media controlls, just swipe on the clock face to controll the media! Gadgetbride support coming soon(hopefully) Thanks to t0m1o1 for media controls!",
"icon": "app.png",
"screenshots": [{"url":"screengrab.png"}],
@ -4449,7 +4508,7 @@
"shortName": "AuthWatch",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"version": "0.03",
"version": "0.04",
"description": "Google Authenticator compatible tool.",
"tags": "tool",
"interface": "interface.html",
@ -4478,7 +4537,7 @@
{"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true}
],
"data": [
{"name":"app.json"}
{"name":"calendarItems.csv"}
]
},
{ "id": "timecal",
@ -4531,7 +4590,7 @@
"shortName":"93 Dub",
"icon": "93dub.png",
"screenshots": [{"url":"screenshot.png"}],
"version":"0.03",
"version":"0.05",
"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",
@ -4549,9 +4608,10 @@
"version":"0.01",
"description": "Simple app to power off your Bangle.js",
"icon": "app.png",
"tags": "poweroff, shutdown",
"tags": "tool, poweroff, shutdown",
"supports" : ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"poweroff.app.js","url":"app.js"},
{"name":"poweroff.img","url":"app-icon.js","evaluate":true}
@ -4561,9 +4621,17 @@
"id": "sensible",
"name": "SensiBLE",
"shortName": "SensiBLE",
"version": "0.02",
"version": "0.03",
"description": "Collect, display and advertise real-time sensor data.",
"icon": "sensible.png",
"screenshots": [
{ "url": "screenshot-top.png" },
{ "url": "screenshot-acc.png" },
{ "url": "screenshot-bar.png" },
{ "url": "screenshot-gps.png" },
{ "url": "screenshot-hrm.png" },
{ "url": "screenshot-mag.png" }
],
"type": "app",
"tags": "tool,sensors",
"supports" : [ "BANGLEJS2" ],
@ -4591,9 +4659,9 @@
},
{
"id":"a_speech_timer",
"name":"A Speech Timer",
"name":"Speech Timer",
"icon": "app.png",
"version":"1.00",
"version":"1.01",
"description": "A timer designed to help keeping your speeches and presentations to time.",
"tags": "tool,timer",
"readme":"README.md",
@ -4603,23 +4671,6 @@
{"name":"a_speech_timer.img","url":"app-icon.js","evaluate":true}
]
},
{
"id": "sensible",
"name": "SensiBLE",
"shortName": "SensiBLE",
"version": "0.02",
"description": "Collect, display and advertise real-time sensor data.",
"icon": "sensible.png",
"type": "app",
"tags": "tool,sensors",
"supports" : [ "BANGLEJS2" ],
"allow_emulator": true,
"readme": "README.md",
"storage": [
{ "name": "sensible.app.js", "url": "sensible.js" },
{ "name": "sensible.img", "url": "sensible-icon.js", "evaluate": true }
]
},
{ "id": "mylocation",
"name": "My Location",
"shortName":"My Location",
@ -4643,7 +4694,7 @@
"id": "pebble",
"name": "Pebble Clock",
"shortName": "Pebble",
"version": "0.03",
"version": "0.04",
"description": "A pebble style clock to keep the rebellion going",
"readme": "README.md",
"icon": "pebble.png",
@ -4675,5 +4726,136 @@
"data": [
{"name":"pooqroman.json"}
]
}
},
{
"id": "widbata",
"name": "Battery Level Widget (Themed)",
"shortName":"Battery Theme",
"icon": "widbata.png",
"screenshots": [{"url":"screenshot_widbata_1.png"}],
"version":"0.01",
"type": "widget",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"description": "Shows the current battery level status in the top right using the clocks colour theme",
"tags": "widget,battery",
"storage": [
{"name":"widbata.wid.js","url":"widbata.wid.js"}
]
},
{
"id": "weatherClock",
"name": "Weather Clock",
"version": "0.03",
"description": "A clock which displays current weather conditions (requires Gadgetbridge and Weather apps).",
"icon": "app.png",
"screenshots": [{"url":"screens/screen1.png"}],
"type": "clock",
"tags": "clock, weather",
"supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true,
"readme": "README.md",
"storage": [
{"name":"weatherClock.app.js","url":"app.js"},
{"name":"weatherClock.img","url":"app-icon.js","evaluate":true}
]
},
{
"id": "menuwheel",
"name": "Wheel Menus",
"version": "0.01",
"description": "Replace Bangle.js 2's menus with a version that contains variable-size text and a back button",
"readme": "README.md",
"icon": "icon.png",
"screenshots": [
{"url":"screenshot_b1_dark.png"},{"url":"screenshot_b1_edit.png"},{"url":"screenshot_b1_light.png"},
{"url":"screenshot_b2_dark.png"},{"url":"screenshot_b2_edit.png"},{"url":"screenshot_b2_light.png"}
],
"type": "boot",
"tags": "system",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"menuwheel.boot.js","url":"boot.js"}
]
},
{ "id": "widChargingStatus",
"name": "Charging Status",
"shortName":"ChargingStatus",
"icon": "widget.png",
"version":"0.1",
"type": "widget",
"description": "A simple widget that shows a yellow lightning icon to indicate whenever the watch is charging. This way one can see the charging status at a glance, no matter which battery widget is being used.",
"tags": "widget",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"widChargingStatus.wid.js","url":"widget.js"}
]
},
{
"id": "flow",
"name": "FLOW",
"shortName": "FLOW",
"version": "0.01",
"description": "A game where you have to help a flow avoid white obstacles thing by tapping! This is a demake of an app which I forgot the name of. Press BTN(1) to restart. See if you can get to 2500 score!",
"icon": "app.png",
"tags": "game",
"supports" : ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name": "flow.app.js", "url": "app.js" },
{"name": "flow.img", "url": "app-icon.js","evaluate": true }
]
},
{ "id": "scribble",
"name": "Scribble",
"shortName":"Scribble",
"version":"0.01",
"type": "app",
"description": "A keyboard on your wrist! Swipe right for space, left for delete.",
"icon": "app.png",
"allow_emulator": true,
"tags": "tools, keyboard, text, scribble",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"scribble.app.js","url":"app.js"},
{"name":"scribble.img","url":"app-icon.js","evaluate":true}
],
"screenshots":[
{ "url":"screenshot.png" }
]
},
{
"id": "ptlaunch",
"name": "Pattern Launcher",
"shortName": "Pattern Launcher",
"version": "0.02",
"description": "Directly launch apps from the clock screen with custom patterns.",
"icon": "app.png",
"tags": "tools",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{ "name": "ptlaunch.app.js", "url": "app.js" },
{ "name": "ptlaunch.boot.js", "url": "boot.js" },
{ "name": "ptlaunch.img", "url": "app-icon.js", "evaluate": true }
],
"data": [{"name":"ptlaunch.patterns.json"}]
},
{ "id": "clicompleteclk",
"name": "CLI complete clock",
"shortName":"CLI cmplt clock",
"version":"0.02",
"description": "Command line styled clock with lots of information",
"icon": "app.png",
"allow_emulator": true,
"type": "clock",
"tags": "clock,cli,command,bash,shell,weather,hrt",
"supports" : ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"clicompleteclk.app.js","url":"app.js"},
{"name":"clicompleteclk.img","url":"app-icon.js","evaluate":true}
]
}
]

View File

@ -1,3 +1,5 @@
0.01: Initial version for upload
0.02: DiscoMinotaur's adjustments (removed battery and adjusted spacing)
0.03: Code style cleanup
0.04: Set 00:00 to 12:00 for 12 hour time
0.05: Display time, even on Thursday

View File

@ -5,7 +5,8 @@
Uses many portions from Espruino documentation, example watchfaces, and the waveclk app. It also sourced from Jon Barlow's 91 Dub v2.0 source code and resources and adapted for Bangle.js 2's screen. Time, date and the battery display works. It is not pixel perfect to the original.
Contributors:
Leer10
Orviwan (original watchface and assets)
Gordon Williams (Bangle.js, watchapps for reference code and documentation)
DiscoMinotaur (adjustments)
* Leer10
* Orviwan (original watchface and assets)
* Gordon Williams (Bangle.js, watchapps for reference code and documentation)
* DiscoMinotaur (adjustments)
* Ray Holder (minor 12 hour time rendering adjustment, fix Thursdays)

View File

@ -78,6 +78,9 @@ function draw(){
} else {
h = " " + h;
}
} else if (h === 0) {
// display 12:00 instead of 00:00 for 12 hr mode
h = "12";
}
//draw separator
@ -90,7 +93,7 @@ function draw(){
if (w == 1) {imgW = imgMon;}
if (w == 2) {imgW = imgTue;}
if (w == 3) {imgW = imgWed;}
if (w == 4) {imgW = imgThr;}
if (w == 4) {imgW = imgThu;}
if (w == 5) {imgW = imgFri;}
if (w == 6) {imgW = imgSat;}
g.drawImage(imgW, 85, 63);

View File

@ -1 +1,2 @@
1.00: Release (2021/12/01)
1.01: Grey font when timer is frozen (2021/12/04)

View File

@ -63,7 +63,7 @@ function countDown() {
Bangle.on('touch',(touchside, touchdata)=>{
if (!islocked && istimeron && touchdata.y > (100+10)) {
Bangle.buzz(40);
Bangle.buzz(40);
istimeron = false;
clearInterval(timerinterval);
} else if (touchdata.y > 24 && touchdata.y < (100-10)) {
@ -134,20 +134,20 @@ function draw() {
else if (current_value >= current_from) { g.setBgColor("#8F8"); }
g.clearRect(0,24,176,176);
g.reset();
g.setFontAlign(0, 0);
g.reset().setFontAlign(0, 0).setColor(istimeron ? "#000" : "#444");
g.setFont("Michroma36").drawString(timeToString(current_value), 88, 62);
g.reset().setFontAlign(0, 0);
g.setFont("HaxorNarrow7x17");
g.drawString(timeToString(current_from), 44, 62+26);
g.drawString(timeToString(current_mid), 88, 62+26);
g.drawString(timeToString(current_to), 132, 62+26);
if (current_value >= current_from) { g.drawRect(44-1,62+26+9,44+1,62+26+9+1); }
if (current_value >= current_mid) { g.drawRect(88-1,62+26+9,88+1,62+26+9+1); }
if (current_value >= current_to) { g.drawRect(132-1,62+26+9,132+1,62+26+9+1); }
if (showInstructions) {
g.setFont("6x8").drawString("Tapping timer locks buttons", 88, 100+5);
g.setFont("6x8").drawString("<= Swipe to change time =>", 88, 168);
@ -159,7 +159,7 @@ function draw() {
g.drawString(timeToString(newtimer_left_to), 44, 138+9);
g.drawString(timeToString(newtimer_right_from), 132, 138-9);
g.drawString(timeToString(newtimer_right_to), 132, 138+9);
g.drawRect(0+8,138-24, 88-9+1, 138+22+1);
g.drawRect(0+8,138-24, 88-9, 138+22);
g.drawRect(88+8,138-24, 176-10+1, 138+22+1);

View File

@ -1,3 +1,4 @@
0.04: Fix tapping at very bottom of list, exit on inactivity
0.03: Add "Calculating" placeholder, update JSON save format
0.02: Fix JSON save format
0.01: First release

View File

@ -10,6 +10,8 @@ const calculating = "Calculating";
const notokens = "No tokens";
const notsupported = "Not supported";
// sample settings:
// {tokens:[{"algorithm":"SHA1","digits":6,"period":30,"issuer":"","account":"","secret":"Bbb","label":"Aaa"}],misc:{}}
var settings = require("Storage").readJSON("authentiwatch.json", true) || {tokens:[],misc:{}};
if (settings.data ) tokens = settings.data ; /* v0.02 settings */
if (settings.tokens) tokens = settings.tokens; /* v0.03+ settings */
@ -146,14 +148,14 @@ function drawToken(id, r) {
// counter - draw triangle as swipe hint
let yc = (y1 + y2) / 2;
g.fillPoly([0, yc, 10, yc - 10, 10, yc + 10, 0, yc]);
adj = 5;
adj = 10;
}
// digits just below label
sz = 30;
do {
g.setFont("Vector", sz--);
} while (g.stringWidth(state.otp) > (r.w - adj));
g.drawString(state.otp, (x1 + x2) / 2 + adj, y1 + 16, false);
g.drawString(state.otp, (x1 + adj + x2) / 2, y1 + 16, false);
}
// shaded lines top and bottom
g.setColor(0.5, 0.5, 0.5);
@ -163,6 +165,8 @@ function drawToken(id, r) {
}
function draw() {
var timerfn = exitApp;
var timerdly = 10000;
var d = new Date();
if (state.curtoken != -1) {
var t = tokens[state.curtoken];
@ -203,17 +207,13 @@ function draw() {
y += tokenentryheight;
}
if (drewcur) {
// the current token has been drawn - draw it again in 1sec
if (state.drawtimer) {
clearTimeout(state.drawtimer);
}
var dly;
// the current token has been drawn - schedule a redraw
if (tokens[state.curtoken].period > 0) {
dly = (state.otp == calculating) ? 1 : 1000;
timerdly = (state.otp == calculating) ? 1 : 1000; // timed
} else {
dly = state.nexttime - d.getTime();
timerdly = state.nexttime - d.getTime(); // counter
}
state.drawtimer = setTimeout(draw, dly);
timerfn = draw;
if (tokens[state.curtoken].period <= 0) {
state.hide = 0;
}
@ -230,12 +230,16 @@ function draw() {
g.setFontAlign(0, 0, 0);
g.drawString(notokens, Bangle.appRect.x + Bangle.appRect.w / 2, Bangle.appRect.y + Bangle.appRect.h / 2, false);
}
if (state.drawtimer) {
clearTimeout(state.drawtimer);
}
state.drawtimer = setTimeout(timerfn, timerdly);
}
function onTouch(zone, e) {
if (e) {
var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / tokenentryheight);
if (id == state.curtoken || tokens.length == 0) {
if (id == state.curtoken || tokens.length == 0 || id >= tokens.length) {
id = -1;
}
if (state.curtoken != id) {
@ -254,26 +258,20 @@ function onTouch(zone, e) {
state.nextTime = 0;
state.curtoken = id;
state.hide = 2;
draw();
}
}
draw();
}
function onDrag(e) {
if (e.x > g.getWidth() || e.y > g.getHeight()) return;
if (e.dx == 0 && e.dy == 0) return;
var newy = Math.min(state.listy - e.dy, tokens.length * tokenentryheight - Bangle.appRect.h);
newy = Math.max(0, newy);
if (newy != state.listy) {
state.listy = newy;
draw();
}
state.listy = Math.max(0, newy);
draw();
}
function onSwipe(e) {
if (e == 1) {
Bangle.showLauncher();
}
if (e == -1 && state.curtoken != -1 && tokens[state.curtoken].period <= 0) {
tokens[state.curtoken].period--;
let newsettings={tokens:tokens,misc:settings.misc};
@ -281,8 +279,8 @@ function onSwipe(e) {
state.nextTime = 0;
state.otp = "";
state.hide = 2;
draw();
}
draw();
}
function bangle1Btn(e) {
@ -302,16 +300,22 @@ function bangle1Btn(e) {
state.curtoken = -1;
state.nextTime = 0;
onTouch(0, fakee);
} else {
draw(); // resets idle timer
}
}
function exitApp() {
Bangle.showLauncher();
}
Bangle.on('touch', onTouch);
Bangle.on('drag' , onDrag );
Bangle.on('swipe', onSwipe);
if (typeof BTN2 == 'number') {
setWatch(function(){bangle1Btn(-1); }, BTN1, {edge:"rising", debounce:50, repeat:true});
setWatch(function(){Bangle.showLauncher();}, BTN2, {edge:"rising", debounce:50, repeat:true});
setWatch(function(){bangle1Btn( 1); }, BTN3, {edge:"rising", debounce:50, repeat:true});
setWatch(function(){bangle1Btn(-1);}, BTN1, {edge:"rising", debounce:50, repeat:true});
setWatch(function(){exitApp(); }, BTN2, {edge:"rising", debounce:50, repeat:true});
setWatch(function(){bangle1Btn( 1);}, BTN3, {edge:"rising", debounce:50, repeat:true});
}
Bangle.loadWidgets();

View File

@ -41,3 +41,4 @@
Don't set beep vibration up on Bangle.js 2 (built in)
0.36: Add comments to .boot0 to make debugging a bit easier
0.37: Remove Quiet Mode settings: now handled by Quiet Mode Schedule app
0.38: Option to log to file if settings.log==2

View File

@ -23,8 +23,14 @@ if (s.ble!==false) {
boot += `bleServiceOptions.hid=Bangle.HID;\n`;
}
}
if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
if (s.log) boot += `Terminal.setConsole(true);\n`; // if showing debug, force REPL onto terminal
if (s.log==2) { // logging to file
boot += `_DBGLOG=require("Storage").open("log.txt","a");
`;
} if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
if (s.log==2) boot += `_DBGLOG=require("Storage").open("log.txt","a");
LoopbackB.on('data',function(d) {_DBGLOG.write(d);Terminal.write(d);});
LoopbackA.setConsole(true);\n`;
else if (s.log) boot += `Terminal.setConsole(true);\n`; // if showing debug, force REPL onto terminal
else boot += `E.setConsole(null,{force:true});\n`; // on new (2v05+) firmware we have E.setConsole which allows a 'null' console
/* If not programmable add our own handler for Bluetooth data
to allow Gadgetbridge commands to be received*/
@ -41,7 +47,10 @@ Bluetooth.on('line',function(l) {
try { global.GB(JSON.parse(l.slice(3,-1))); } catch(e) {}
});\n`;
} else {
if (s.log) boot += `if (!NRF.getSecurityStatus().connected) Terminal.setConsole();\n`; // if showing debug, put REPL on terminal (until connection)
if (s.log==2) boot += `_DBGLOG=require("Storage").open("log.txt","a");
LoopbackB.on('data',function(d) {_DBGLOG.write(d);Terminal.write(d);});
if (!NRF.getSecurityStatus().connected) LoopbackA.setConsole();\n`;
else if (s.log) boot += `if (!NRF.getSecurityStatus().connected) Terminal.setConsole();\n`; // if showing debug, put REPL on terminal (until connection)
else boot += `Bluetooth.setConsole(true);\n`; // else if no debug, force REPL to Bluetooth
}
// we just reset, so BLE should be on.

View File

@ -1,2 +1,3 @@
0.01: Basic calendar
0.02: Make Bangle 2 compatible
0.03: Add setting to start week on Sunday

View File

@ -6,3 +6,8 @@ Basic calendar
- Use `BTN4` (left screen tap) to go to the previous month
- Use `BTN5` (right screen tap) to go to the next month
## Settings
- Starts on Sunday: whether the calendar should start on Sunday (default is Monday).

View File

@ -18,6 +18,10 @@ const gray2 = "#888888";
const gray3 = "#bbbbbb";
const red = "#d41706";
let settings = require('Storage').readJSON("calendar.json", true) || {};
if (settings.startOnSun === undefined)
settings.startOnSun = false;
function drawCalendar(date) {
g.setBgColor(color4);
g.clearRect(0, 0, maxX, maxY);
@ -61,13 +65,18 @@ function drawCalendar(date) {
);
g.setFont("6x8", fontSize);
const dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
let dowLbls;
if (settings.startOnSun) {
dowLbls = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
} else {
dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
}
dowLbls.forEach((lbl, i) => {
g.drawString(lbl, i * colW + colW / 2, headerH + rowH / 2);
});
date.setDate(1);
const dow = date.getDay();
const dow = date.getDay() + (settings.startOnSun ? 1 : 0);
const dowNorm = dow === 0 ? 7 : dow;
const monthMaxDayMap = {

24
apps/calendar/settings.js Normal file
View File

@ -0,0 +1,24 @@
(function(back) {
var FILE = "calendar.json";
var settings = require('Storage').readJSON(FILE, true) || {};
if (settings.startOnSun === undefined)
settings.startOnSun = true;
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
E.showMenu({
"" : { "title" : "Calendar" },
"< Back" : () => back(),
'Start on Sunday': {
value: settings.startOnSun,
format: v => v?"Yes":"No",
onchange: v => {
settings.startOnSun = v;
writeSettings();
}
},
});
})

View File

@ -1,3 +1,5 @@
0.01: New widget and app!
0.02: Setting to reset values, timer buzzes at 00:00 and not later (see readme)
0.03: Display only minutes:seconds when less than 1 hour left
0.03: Display only minutes:seconds when less than 1 hour left
0.04: Change to 7 segment font, move to top widget bar
Better auto-update behaviour, less RAM used

View File

@ -5,14 +5,13 @@ The advantage is, that you can still see your normal watchface and other widgets
The widget is always active, but only shown when the timer is on.
Hours, minutes, seconds and timer status can be set with an app.
When there is less than one seconds left on the timer it buzzes.
When there is less than one second left on the timer it buzzes.
The widget has been tested on Bangle 1 and Bangle 2
## Screenshots
![](chrono_with_wave.jpg)
![](chrono_with_pastel.jpg)
![](screenshot.png)
## Features
@ -28,15 +27,15 @@ There are no settings section in the settings app, timer can be set using an app
* Hours: Set the hours for the timer
* Minutes: Set the minutes for the timer
* Seconds: Set the seconds for the timer
* Timer on: Starts the timer and displays the widget when set to 'On'. You have to leave the app to load the widget which starts the timer. The widget is always there, but only visible when timer is on.
* Timer on: Starts the timer and displays the widget when set to 'On'. You have to leave the app to load the widget which starts the timer. The widget is always there, but only visible when timer is on.
## Releases
* Offifical app loader: https://github.com/espruino/BangleApps/tree/master/apps/chronowid (https://banglejs.com/apps/)
* Official app loader: https://github.com/espruino/BangleApps/tree/master/apps/chronowid (https://banglejs.com/apps/)
* Forked app loader: https://github.com/Purple-Tentacle/BangleApps/tree/master/apps/chronowid (https://purple-tentacle.github.io/BangleApps/index.html#)
* Development: https://github.com/Purple-Tentacle/BangleAppsDev/tree/master/apps/chronowid
## Requests
If you have any feature requests, please write here: http://forum.espruino.com/conversations/345972/
If you have any feature requests, please write here: http://forum.espruino.com/conversations/345972/

View File

@ -3,7 +3,6 @@ Bangle.loadWidgets();
Bangle.drawWidgets();
const storage = require('Storage');
const boolFormat = v => v ? "On" : "Off";
let settingsChronowid;
function updateSettings() {
@ -12,6 +11,7 @@ function updateSettings() {
now.getHours() + settingsChronowid.hours, now.getMinutes() + settingsChronowid.minutes, now.getSeconds() + settingsChronowid.seconds);
settingsChronowid.goal = goal.getTime();
storage.writeJSON('chronowid.json', settingsChronowid);
if (WIDGETS["chronowid"]) WIDGETS["chronowid"].reload();
}
function resetSettings() {
@ -44,6 +44,7 @@ function showMenu() {
timerMenu.started.value = settingsChronowid.started;
}
},
'< Back' : ()=>{load();},
'Reset values': function() {
settingsChronowid.hours = 0;
settingsChronowid.minutes = 0;
@ -84,15 +85,15 @@ function showMenu() {
},
'Timer on': {
value: settingsChronowid.started,
format: boolFormat,
format: v => v ? "On" : "Off",
onchange: v => {
settingsChronowid.started = v;
updateSettings();
}
},
};
timerMenu['-Exit-'] = ()=>{load();};
return E.showMenu(timerMenu);
}
showMenu();
showMenu();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -1,93 +1,79 @@
(() => {
const storage = require('Storage');
settingsChronowid = storage.readJSON("chronowid.json",1)||{}; //read settingsChronowid from file
var height = 23;
var width = 58;
var settingsChronowid;
var interval = 0; //used for the 1 second interval timer
var now = new Date();
var diff;
var time = 0;
var diff = settingsChronowid.goal - now;
//Convert ms to time
function getTime(t) {
var milliseconds = parseInt((t % 1000) / 100),
seconds = Math.floor((t / 1000) % 60),
minutes = Math.floor((t / (1000 * 60)) % 60),
hours = Math.floor((t / (1000 * 60 * 60)) % 24);
hours = (hours < 10) ? "0" + hours : hours;
minutes = (minutes < 10) ? "0" + minutes : minutes;
seconds = (seconds < 10) ? "0" + seconds : seconds;
return hours + ":" + minutes + ":" + seconds;
return hours.toString().padStart(2,0) + ":" + minutes.toString().padStart(2,0) + ":" + seconds.toString().padStart(2,0);
}
function printDebug() {
print ("Nowtime: " + getTime(now));
print ("Now: " + now);
/*function printDebug() {
print ("Goaltime: " + getTime(settingsChronowid.goal));
print ("Goal: " + settingsChronowid.goal);
print("Difftime: " + getTime(diff));
print("Diff: " + diff);
print ("Started: " + settingsChronowid.started);
print ("----");
}
}*/
//counts down, calculates and displays
function countDown() {
now = new Date();
var now = new Date();
diff = settingsChronowid.goal - now; //calculate difference
WIDGETS["chronowid"].draw();
//time is up
// time is up
if (settingsChronowid.started && diff < 1000) {
Bangle.buzz(1500);
//write timer off to file
settingsChronowid.started = false;
storage.writeJSON('chronowid.json', settingsChronowid);
require('Storage').writeJSON('chronowid.json', settingsChronowid);
clearInterval(interval); //stop interval
interval = undefined;
}
//printDebug();
// calculates width and redraws accordingly
WIDGETS["chronowid"].redraw();
}
// draw your widget
function draw() {
if (!settingsChronowid.started) {
width = 0;
return; //do not draw anything if timer is not started
}
g.reset();
if (diff >= 0) {
if (diff < 3600000) { //less than 1 hour left
width = 58;
g.clearRect(this.x,this.y,this.x+width,this.y+height);
g.setFont("6x8", 2);
g.drawString(getTime(diff).substring(3), this.x+1, this.y+5); //remove hour part 00:00:00 -> 00:00
}
if (diff >= 3600000) { //one hour or more left
width = 48;
g.clearRect(this.x,this.y,this.x+width,this.y+height);
g.setFont("6x8", 1);
g.drawString(getTime(diff), this.x+1, this.y+((height/2)-4)); //display hour 00:00:00
}
}
// not needed anymoe, because we check if diff < 1000 now, so 00:00 is displayed.
// else {
// width = 58;
// g.clearRect(this.x,this.y,this.x+width,this.y+height);
// g.setFont("6x8", 2);
// g.drawString("END", this.x+15, this.y+5);
// }
}
if (settingsChronowid.started) interval = setInterval(countDown, 1000); //start countdown each second
// add the widget
WIDGETS["chronowid"]={area:"bl",width:width,draw:draw,reload:function() {
reload();
Bangle.drawWidgets(); // relayout all widgets
WIDGETS["chronowid"]={area:"tl",width:0,draw:function() {
if (!this.width) return;
g.reset().setFontAlign(0,0).clearRect(this.x,this.y,this.x+this.width,this.y+23);
//g.drawRect(this.x,this.y,this.x+this.width-1, this.y+23);
var scale;
var timeStr;
if (diff < 3600000) { //less than 1 hour left
width = 58;
scale = 2;
timeStr = getTime(diff).substring(3); // remove hour part 00:00:00 -> 00:00
} else { //one hour or more left
width = 48;
scale = 1;
timeStr = getTime(diff); //display hour 00:00:00 but small
}
// Font5x9Numeric7Seg - just build this in as it's tiny
g.setFontCustom(atob("AAAAAAAAAAIAAAQCAQAAAd0BgMBdwAAAAAAAdwAB0RiMRcAAAERiMRdwAcAQCAQdwAcERiMRBwAd0RiMRBwAAEAgEAdwAd0RiMRdwAcERiMRdwAFAAd0QiEQdwAdwRCIRBwAd0BgMBAAABwRCIRdwAd0RiMRAAAd0QiEQAAAAAAAAAA="), 32, atob("BgAAAAAAAAAAAAAAAAYCAAYGBgYGBgYGBgYCAAAAAAAABgYGBgYG"), 9 + (scale<<8));
g.drawString(timeStr, this.x+this.width/2, this.y+12);
}, redraw:function() {
var last = this.width;
if (!settingsChronowid.started) this.width = 0;
else this.width = (diff < 3600000) ? 58 : 48;
if (last != this.width) Bangle.drawWidgets();
else this.draw();
}, reload:function() {
settingsChronowid = require('Storage').readJSON("chronowid.json",1)||{};
if (interval) clearInterval(interval);
interval = undefined;
// start countdown each second
if (settingsChronowid.started) interval = setInterval(countDown, 1000);
// reset everything
countDown();
}};
//printDebug();
countDown();
})();
// set width correctly, start countdown each second
WIDGETS["chronowid"].reload();
})();

View File

@ -1,2 +1,3 @@
0.01: Submitted to App Loader
0.02: Removed unneded code, added HID controlls thanks to t0m1o1 for his code :p
0.03: Load widgets after Bangle.setUI to ensure widgets know if they're on a clock or not (fix #970)

View File

@ -50,7 +50,7 @@ if (next) {
setTimeout(drawApp, 1000);
Bangle.setLocked(true);
}, BTN1, { edge:"falling",repeat:true,debounce:50});
Bangle.on('drag', function(e) {
Bangle.on('drag', function(e) {
if(!e.b){
console.log(lasty);
console.log(lastx);
@ -91,7 +91,7 @@ if (next) {
lasty = lasty + e.dy;
}
});
}
@ -144,14 +144,15 @@ function writeLine(str,line){
}
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
drawAll();
Bangle.on('lcdPower',function(on) {
if (on) drawAll();
});
var click = setInterval(updateTime, 1000);
// Show launcher when button pressed
Bangle.setUI("clockupdown", btn=>{
drawAll();
drawAll(); // why do we redraw here??
});
Bangle.loadWidgets();
Bangle.drawWidgets();
drawAll();

View File

@ -0,0 +1,2 @@
0.01: New clock!
0.02: Load steps from Health Tracking app (if installed)

View File

@ -0,0 +1,22 @@
# Command line complete clock
Command line styled clock with lots of information:
It can show the following (depending on availability) information:
* Time
* Day of week
* Date
* Weather conditions and temperature (requires app [Weather](https://banglejs.com/apps/#weather))
* Steps (requires app [Health Tracking](https://banglejs.com/apps/#health%20tracking) or a step widget)
* Heart rate (when screen is on and unlocked)
## TODO
* Make time font bigger
* Show progress of steps (if any goal is set)
* Show trend of HRM out of history data
## Creator
Marco ([myxor](https://github.com/myxor))
## Icon
Icon taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgI/8/4ACAqYv/F/PwAqgA6A=="))

195
apps/clicompleteclk/app.js Normal file
View File

@ -0,0 +1,195 @@
const storage = require('Storage');
const locale = require("locale");
const font = "12x20";
const fontsize = 1;
const fontheight = 19;
const marginTop = 10;
const marginLeftTopic = 3; // margin of topics
const marginLeftData = 68; // margin of data values
const topicColor = g.theme.dark ? "#fff" : "#000";
const textColor = g.theme.dark ? "#0f0" : "#080";
let hrtValue;
let hrtValueIsOld = false;
let localTempValue;
let weatherTempString;
let lastHeartRateRowIndex;
// 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;
drawAll(false);
}, 60000 - (Date.now() % 60000));
}
function drawAll(drawInfoToo){
let now = new Date();
updateTime(now);
if (drawInfoToo) {
drawInfo(now);
}
queueDraw();
}
function updateTime(now){
if (!Bangle.isLCDOn()) return;
writeLineTopic("TIME", 1);
writeLine(locale.time(now,1),1);
if(now.getMinutes() == 0)
drawInfo(now);
}
function drawInfo(now) {
if (now == undefined)
now = new Date();
let i = 2;
writeLineTopic("DOWK", i);
writeLine(locale.dow(now),i);
i++;
writeLineTopic("DATE", i);
writeLine(locale.date(now,1),i);
i++;
/*
writeLineTopic("BAT", i);
const b = E.getBattery();
writeLine(b + "%", i); // TODO make bars
i++;
*/
// weather
const weatherJson = getWeather();
if(weatherJson && weatherJson.weather){
const currentWeather = weatherJson.weather;
const weatherTempValue = locale.temp(currentWeather.temp-273.15);
weatherTempString = weatherTempValue;
writeLineTopic("WTHR", i);
writeLine(currentWeather.txt,i);
i++;
writeLineTopic("TEMP", i);
writeLine(weatherTempValue,i);
i++;
}
// steps
const steps = getSteps();
if (steps != undefined) {
writeLineTopic("STEP", i);
writeLine(steps, i);
i++;
}
drawHeartRate(i);
}
function drawHeartRate(i) {
if (i == undefined)
i = lastHeartRateRowIndex;
writeLineTopic("HRTM", i);
if (hrtValue != undefined) {
if (!hrtValueIsOld)
writeLine(hrtValue,i);
else
writeLine(hrtValue,i, topicColor);
}
lastHeartRateRowIndex = i;
}
function writeLineTopic(str, line) {
var y = marginTop+line*fontheight;
g.setFont(font,fontsize);
g.setColor(topicColor).setFontAlign(-1,-1);
g.clearRect(0,y,g.getWidth(),y+fontheight-1);
g.drawString("[" + str + "]",marginLeftTopic,y);
}
function writeLine(str,line,pColor){
if (pColor == undefined)
pColor = textColor;
var y = marginTop+line*fontheight;
g.setFont(font,fontsize);
g.setColor(pColor).setFontAlign(-1,-1);
g.drawString(str,marginLeftData,y);
}
function getSteps() {
var steps = 0;
let health;
try {
health = require("health");
} catch (e) {
// Module health not found
}
if (health != undefined) {
health.readDay(new Date(), h=>steps+=h.steps);
} else if (WIDGETS.wpedom !== undefined) {
return WIDGETS.wpedom.getSteps();
} else if (WIDGETS.activepedom !== undefined) {
return WIDGETS.activepedom.getSteps();
}
return steps;
}
function getWeather() {
let jsonWeather = storage.readJSON('weather.json');
return jsonWeather;
}
// EVENTS:
// turn on HRM when the LCD is unlocked
Bangle.on('lock', function(isLocked) {
if (!isLocked) {
Bangle.setHRMPower(1,"clicompleteclk");
if (hrtValue == undefined)
hrtValue = "...";
else
hrtValueIsOld = true;
} else {
hrtValueIsOld = true;
Bangle.setHRMPower(0,"clicompleteclk");
}
drawHeartRate();
});
Bangle.on('lcdPower',function(on) {
if (on) {
drawAll(true);
} else {
hrtValueIsOld = true;
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}
});
Bangle.on('HRM', function(hrm) {
//if(hrm.confidence > 90){
hrtValueIsOld = false;
hrtValue = hrm.bpm;
if (Bangle.isLCDOn())
drawHeartRate();
//} else {
// hrtValue = undefined;
//}
});
g.clear();
Bangle.setUI("clock");
Bangle.loadWidgets();
Bangle.drawWidgets();
drawAll(true);

BIN
apps/clicompleteclk/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

View File

@ -7,3 +7,4 @@
0.13: Use setUI, work with smaller screens and themes
0.14: Fix BTN1 (fix #853)
Add light/dark theme support
0.15: Load widgets after Bangle.setUI to ensure widgets know if they're on a clock or not (fix #970)

View File

@ -183,9 +183,6 @@ Bangle.on('HRM', function(hrm) {
});
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
drawAll();
Bangle.on('lcdPower',function(on) {
if (on) drawAll();
});
@ -195,4 +192,7 @@ Bangle.setUI("clockupdown", btn=>{
if (btn<0) changeInfoMode();
if (btn>0) changeFunctionMode();
drawAll();
});
});
Bangle.loadWidgets();
Bangle.drawWidgets();
drawAll();

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Upgraded text to images, added welcome screen and subtitles.
0.03: Advertise app name as Espruino manufacturer data when idle.

View File

@ -32,6 +32,7 @@ const CYCLE_BUZZ_MILLISECONDS = 50;
const WELCOME_MESSAGE = 'Emojuino:\r\n\r\n< Swipe >\r\nto select\r\n\r\nTap\r\nto transmit';
// Non-user-configurable constants
const APP_ID = 'emojuino';
const IMAGE_INDEX = 0;
const CODE_POINT_INDEX = 1;
const EMOJI_PX = 96;
@ -40,12 +41,11 @@ const EMOJI_Y = (g.getHeight() - EMOJI_PX) / 2;
const TX_X = 68;
const TX_Y = 12;
const FONT_SIZE = 24;
const BTN_WATCH_OPTIONS = { repeat: true, debounce: 20, edge: "falling" };
const ESPRUINO_COMPANY_CODE = 0x0590;
const UNICODE_CODE_POINT_ELIDED_UUID = [ 0x49, 0x6f, 0x49, 0x44, 0x55,
0x54, 0x46, 0x2d, 0x33, 0x32 ];
// Global variables
let emojiIndex = 0;
let isToggleOn = false;
@ -100,9 +100,22 @@ function transmitEmoji(image, codePoint, duration) {
}
// Transmit the app name under the Espruino company code to facilitate discovery
function transmitAppName() {
let options = {
showName: false,
manufacturer: ESPRUINO_COMPANY_CODE,
manufacturerData: JSON.stringify({ name: APP_ID }),
interval: 2000
}
NRF.setAdvertising({}, options);
}
// Terminate the emoji transmission
function terminateEmoji(displayIntervalId) {
NRF.setAdvertising({ });
transmitAppName();
isTransmitting = false;
clearInterval(displayIntervalId);
drawImage(EMOJIS[emojiIndex][IMAGE_INDEX], false);
@ -169,3 +182,4 @@ g.setFontAlign(0, 0);
g.drawString(WELCOME_MESSAGE, g.getWidth() / 2, g.getHeight() / 2);
Bangle.on('touch', handleTouch);
Bangle.on('drag', handleDrag);
transmitAppName();

12
apps/flow/README.md Normal file
View File

@ -0,0 +1,12 @@
# FLOW
This is a game where you have to help a flow avoid white obstacles thing by tapping!
This is a demake of an app which I forgot the name of.
Press BTN(1) to restart.
See if you can get to 2500 score!
## Screenshots
![](screenshot1.png)
![](screenshot2.png)
![](screenshot3.png)

1
apps/flow/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4X/AwX48EHgEC1WgCQkVqoDBBfuqBQcBqoLagEqGAguBqALaGAOoAoQuEBbEAKgIMBBQNUBbgMCyoKHBbBVBBYIKGBbEBtNVrQLfOgNaT4gLagp0CPQOABbcBFwNAgEKBgILbitVqAFClWq0ALZFwTDFGAQLZFwYwDBfg"))

220
apps/flow/app.js Normal file
View File

@ -0,0 +1,220 @@
const isB2 = process.env.HWVERSION === 2;
// Bangle.js 1 runs just too fast in direct mode??? (also no getPixel)
if (!isB2) Bangle.setLCDMode("120x120");
const options = Bangle.getOptions();
options.lockTimeout = 0;
options.lcdPowerTimeout = 0;
Bangle.setOptions(options);
g.reset();
g.setBgColor(0, 0, 0);
g.setColor(255, 255, 255);
g.clear();
const h = g.getHeight();
function trigToCoord(ret) {
return ((ret + 1) * h) / 2;
}
function trigToLen(ret) {
return (ret * h) / 2;
}
let i = 0.2;
let speedCoef = 0.014;
let flowFile = require("Storage").readJSON("flow.json");
let highestI = (flowFile && flowFile.hiscore) || 0.1;
let colorA = [255, 255, 0];
let colorB = [0, 255, 255];
let x = 0;
let xt = 0;
let safeMode = false;
let lost = false;
function offsetRect(g, x, y, w) {
g.fillRect(x, y, x + w, y + w);
}
function getColor(num) {
return [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
[1, 1, 0],
[0, 1, 1],
[1, 0, 1],
[0.5, 0.5, 1],
[1, 0.5, 0],
[0, 1, 0.5],
[0.5, 0.5, 0.5],
][num];
}
function calculateColor(num) {
colorA = getColor(Math.floor((num % 1) * 10));
colorB = getColor(Math.floor((num % 10) - (num % 1)));
}
calculateColor(highestI);
Bangle.on("touch", () => (safeMode = !safeMode));
function resetGame() {
x = xt = 0;
safeMode = lost = false;
i = 0.2;
speedCoef = 0.014;
obstaclePeriod = 150;
obstacleMode = 1;
g.clear();
shownScore = false;
intervalId = setInterval(draw);
}
function checkCollision() {
lost = g.getPixel(trigToCoord(+x), (h * 2) / 3 - 4) !== 0;
if (lost) {
scoringI = i;
speedCoef = Math.min(speedCoef, 0.02);
g.setFont(isB2 ? "6x15" : "4x6", 3);
g.setColor(colorA[0], colorA[1], colorA[2])
.drawString(
"Game over",
trigToCoord(0) - g.stringWidth("Game over") / 2,
trigToCoord(0)
)
.setColor(1, 1, 1);
}
}
function drawPlayer() {
if (!safeMode) xt = Math.cos(i * Math.PI * 4) / 7.5;
else xt = -Math.cos(i * Math.PI * 2) / 20 + 0.35;
x = x * 0.8 + xt * 0.2;
if (highestI > 250) calculateColor(i);
g.setColor(colorA[0], colorA[1], colorA[2]);
offsetRect(g, trigToCoord(+x), (h * 2) / 3, 3);
g.setColor(colorB[0], colorB[1], colorB[2]);
offsetRect(g, trigToCoord(-x), (h * 2) / 3, 3);
}
let obstaclePeriod = 150;
let obstacleMode = 1;
function drawObstracle() {
g.setColor(1, 1, 1);
switch (obstacleMode) {
case 0:
offsetRect(g, trigToCoord(-0.15), 0, trigToLen(0.3));
break;
case 1:
offsetRect(g, trigToCoord(0.2), 0, trigToLen(0.2));
offsetRect(g, trigToCoord(-0.4), 0, trigToLen(0.2));
break;
case 2:
break;
}
obstaclePeriod--;
if (obstaclePeriod <= 0) {
// If we are off cooldown mode, pick a random actual mode
if (obstacleMode === 2) {
obstaclePeriod = Math.random() * 50 + 50;
obstacleMode = Math.round(Math.random());
} else if (Math.random() > 0.5) {
// Give it a chance to repeat with no cooldown
obstaclePeriod = 25 + 2.5 * speedCoef;
obstacleMode = 2;
}
}
}
let shownScore = false;
let scoringI = 0;
function draw() {
if (!lost) {
drawPlayer();
checkCollision();
speedCoef *= 1.0005;
drawObstracle();
} else {
speedCoef /= 1.05;
if (speedCoef <= 0.005) {
clearInterval(intervalId);
i -= speedCoef;
g.setFont(isB2 ? "6x15" : "4x6", 1);
const str = "Hiscore: " + Math.round(highestI * 10);
g.setColor(
scoringI > highestI ? 0 : 255,
0,
scoringI > highestI ? 255 : 0
)
.drawString(
str,
trigToCoord(0) - g.stringWidth(str) / 2,
trigToCoord(0)
)
.setColor(255, 255, 255);
if (scoringI > highestI) {
highestI = scoringI;
require("Storage").writeJSON("flow.json", {
hiscore: highestI,
});
calculateColor(highestI);
}
setTimeout(resetGame, 3000);
} else if (speedCoef <= 0.01 && !shownScore) {
shownScore = true;
g.setFont(isB2 ? "6x15" : "4x6", 2);
const str = "Score: " + Math.round(scoringI * 10);
g.setColor(colorB[0], colorB[1], colorB[2])
.drawString(
str,
trigToCoord(0) - g.stringWidth(str) / 2,
trigToCoord(0)
)
.setColor(1, 1, 1);
}
}
i += speedCoef;
g.scroll(0, speedCoef * h);
g.flip();
}
let intervalId;
if (BTN.read()) {
for (let i = 0; i < 10; i++) {
color = getColor(i);
g.setColor(color[0], color[1], color[2]);
g.fillRect((i / 10) * h, 0, ((i + 1) / 10) * h, h);
}
g.setColor(0);
g.setFont("Vector", 9);
let str = "Welcome to the debug screen!";
g.drawString(
str,
trigToCoord(0) - g.stringWidth(str) / 2,
trigToCoord(0) - 9
);
str = "Don't hold BTN while opening to play!";
g.drawString(str, trigToCoord(0) - g.stringWidth(str) / 2, trigToCoord(0));
g.flip();
setInterval(() => {
g.scroll(0, 0.014 * h);
i += 0.014;
calculateColor(i);
g.setColor(colorA[0], colorA[1], colorA[2]);
g.fillRect(0, 0, trigToCoord(0), 0.014 * h);
g.setColor(colorB[0], colorB[1], colorB[2]);
g.fillRect(trigToCoord(0), 0, trigToCoord(1), 0.014 * h);
}, 1000 / 30);
} else intervalId = setInterval(draw, 1000 / 30);

BIN
apps/flow/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

BIN
apps/flow/screenshot1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 992 B

BIN
apps/flow/screenshot2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
apps/flow/screenshot3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1 +1,4 @@
0.01: Initial version
0.02: Add support for ZIPs
Find and download ZIPs direct from the Espruino website
Take 'beta' tag off

View File

@ -3,29 +3,44 @@
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<p><b>THIS IS CURRENTLY BETA - PLEASE USE THE NORMAL FIRMWARE UPDATE
INSTRUCTIONS FOR <a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">BANGLE.JS</a> 1 AND <a href="https://www.espruino.com/Bangle.js2#firmware-updates" target="_blank">BANGLE.JS 2</a></b></p>
<div id="fw-unknown">
<p>Firmware updates using the App Loader are only possible on
<p><b>Firmware updates using the App Loader are only possible on
Bangle.js 2. For firmware updates on Bangle.js 1 please
<a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">see the Bangle.js 1 instructions</a></p>
<a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">see the Bangle.js 1 instructions</a></b></p>
</div>
<p>Your current firmware version is <span id="fw-version" style="font-weight:bold">unknown</span></p>
<div id="fw-ok" style="display:none">
<p>Please upload a hex file here. This file should be the <code>.app_hex</code>
<div id="latest-firmware" style="display:none">
<p>The currently available Espruino firmware releases are:</p>
<ul id="latest-firmware-list">
</ul>
<p>To update, click the link and then click the 'Upload' button that appears.</p>
</div>
<p>Or you can upload a hex or zip file here. This file should be an <code>.app_hex</code>
file, *not* the normal <code>.hex</code> (as that contains the bootloader as well).</p>
<input class="form-input" type="file" id="fileLoader" accept=".hex,.app_hex"/><br>
<p><button id="upload" class="btn btn-primary">Upload</button></p>
<input class="form-input" type="file" id="fileLoader" accept=".hex,.app_hex,.zip"/><br>
<p><button id="upload" class="btn btn-primary" style="display:none">Upload</button></p>
</div>
<p>Firmware updates via this tool work differently to the NRF Connect method mentioned on
<a href="https://www.espruino.com/Bangle.js2#firmware-updates">the Bangle.js page</a>. Firmware
is uploaded to a file on the Bangle. Once complete the Bangle reboots and the bootloader copies
the new firmware into internal Storage.</p>
<pre id="log"></pre>
<script src="../../core/lib/customize.js"></script>
<script src="../../core/lib/espruinotools.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.js"></script>
<script>
var hex;
var hexJS; // JS to upload hex
var HEADER_LEN = 16; // size of app flash header
var APP_START = 0x26000;
var APP_MAX_LENGTH = 0xda000; // from linker file - the max size the app can be, for sanity check!
var MAX_ADDRESS = 0x1000000; // discount anything in hex file above this
var VERSION = 0x12345678; // VERSION! Use this to test firmware in JS land
var DEBUG = false;
@ -37,6 +52,8 @@ function log(t) {
function onInit(device) {
console.log(device);
if (device && device.version)
document.getElementById("fw-version").innerText = device.version;
if (device && device.id=="BANGLEJS2") {
document.getElementById("fw-unknown").style = "display:none";
document.getElementById("fw-ok").style = "";
@ -44,7 +61,7 @@ function onInit(device) {
}
function checkForFileOnServer() {
/*function getURL(url, callback) {
function getURL(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = callback;
baseURL = url;
@ -55,6 +72,7 @@ function checkForFileOnServer() {
function getFilesFromURL(url, regex, callback) {
getURL(url, function() {
console.log(this.responseXML)
var files = [];
var elements = this.responseXML.getElementsByTagName("a");
for (var i=0;i<elements.length;i++) {
@ -67,35 +85,78 @@ function checkForFileOnServer() {
});
}
var regex = new RegExp("_bangle2");
var regex = new RegExp("_banglejs2.*zip$");
var domFirmwareList = document.getElementById("latest-firmware-list");
var domFirmware = document.getElementById("latest-firmware");
console.log("Checking server...");
var domFirmware = document.getElementById("latest-firmware");
getFilesFromURL("https://www.espruino.com/binaries/", regex, function(releaseFiles) {
releaseFiles.sort().reverse().forEach(function(f) {
releaseFiles.sort().reverse().forEach(function(f) {
var name = f.substr(f.substr(0,f.length-1).lastIndexOf('/')+1);
domFirmware.innerHTML += 'Release: <a href="'+f+'">'+name+'</a><br/>';
console.log("Found "+name);
domFirmwareList.innerHTML += '<li>Release: <a href="'+f+'" class="fw-link">'+name+'</a></li>';
domFirmware.style = "";
});
getFilesFromURL("https://www.espruino.com/binaries/travis/master/",regex, function(travisFiles) {
travisFiles.forEach(function(f) {
var name = f.substr(f.lastIndexOf('/')+1);
domFirmware.innerHTML += 'Cutting Edge build: <a href="'+f+'">'+name+'</a><br/>';
console.log("Found "+name);
domFirmwareList.innerHTML += '<li>Cutting Edge build: <a href="'+f+'" class="fw-link">'+name+'</a></li>';
domFirmware.style = "";
});
document.getElementById("checking-server").style = "display:none";
document.getElementById("main-ui").style = "";
console.log("Finished check for firmware files...");
var fwlinks = document.querySelectorAll(".fw-link");
for (var i=0;i<fwlinks.length;i++)
fwlinks[i].addEventListener("click", e => {
e.preventDefault();
var url = e.target.href;
downloadZipFile(url).then(info=>{
document.getElementById("upload").style = ""; // show upload
});
});
});
});*/
});
}
function downloadFile() {
/*response = await fetch(APP_HEX_PATH+"readlink.php?link="+APP_HEX_FILE, {
method: 'GET',
cache: 'no-cache',
});
if (response.ok) {
blob = await response.blob();
data = await blob.text();
document.getElementById("latest-firmware").innerHTML="(<b>"+data.toString()+"</b>)";
}*/
function downloadZipFile(url) {
return new Promise((resolve,reject) => {
Espruino.Core.Utils.getBinaryURL(url, (err, binary) => {
if (err) return reject("Unable to download "+url);
resolve(binary);
});
}).then(convertZipFile);
}
function convertZipFile(binary) {
var info = {};
Promise.resolve(binary).then(binary => {
info.binary = binary;
return JSZip.loadAsync(binary)
}).then(function(zipFile) {
info.zipFile = zipFile;
return info.zipFile.file("manifest.json").async("string");
}).then(function(content) {
info.manifest = JSON.parse(content).manifest;
}).then(function(content) {
console.log(info.manifest);
return info.zipFile.file(info.manifest.application.dat_file).async("arraybuffer");
}).then(function(content) {
info.dat_file = content;
}).then(function(content) {
console.log(info.manifest);
return info.zipFile.file(info.manifest.application.bin_file).async("arraybuffer");
}).then(function(content) {
info.bin_file = content;
if (info.bin_file.byteLength > APP_MAX_LENGTH) throw new Error("Firmware file is too big!");
info.storageContents = new Uint8Array(info.bin_file.byteLength + HEADER_LEN)
info.storageContents.set(new Uint8Array(info.bin_file), HEADER_LEN);
createJS_app(info.storageContents, APP_START, APP_START+info.bin_file.byteLength);
log("Download complete");
console.log("Download complete",info);
document.getElementById("upload").style = ""; // show upload
return info;
}).catch(err => log("ERROR:" + err));
}
function handleFileSelect(event) {
@ -103,13 +164,24 @@ function handleFileSelect(event) {
log("More than one file selected!");
return;
}
var file = event.target.files[0];
var reader = new FileReader();
reader.onload = function(event) {
hex = event.target.result.split("\n");
document.getElementById("upload").style = ""; // show upload
fileLoaded();
};
reader.readAsText(event.target.files[0]);
if (file.name.endsWith(".hex") || file.name.endsWith(".app_hex")) {
reader.onload = function(event) {
hex = event.target.result.split("\n");
document.getElementById("upload").style = ""; // show upload
fileLoaded();
};
reader.readAsText(event.target.files[0]);
} else if (file.name.endsWith(".zip")) {
reader.onload = function(event) {
convertZipFile(event.target.result);
};
reader.readAsArrayBuffer(event.target.files[0]);
} else {
log("Unknown file extension for "+file.name);
}
};
@ -174,14 +246,16 @@ function btoa(input) {
return out;
}
// To upload the app, we write to external flash
function createJS_app(binary, bin32, startAddress, endAddress, HEADER_LEN) {
/* To upload the app, we write to external flash,
binary = Uint8Array of data to flash. Should include HEADER_LEN header, then bytes to flash */
function createJS_app(binary, startAddress, endAddress) {
/* typedef struct {
uint32_t address;
uint32_t size;
uint32_t CRC;
uint32_t version;
} FlashHeader; */
var bin32 = new Uint32Array(binary.buffer);
bin32[0] = startAddress;
bin32[1] = endAddress - startAddress;
bin32[2] = CRC32(new Uint8Array(binary.buffer, HEADER_LEN));
@ -189,7 +263,8 @@ function createJS_app(binary, bin32, startAddress, endAddress, HEADER_LEN) {
console.log("CRC 0x"+bin32[2].toString(16));
hexJS = "";//`\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${bin32[2]}) { print("FIRMWARE UP TO DATE!"); load();}\n`;
hexJS += '\x10var s = require("Storage");\n';
var CHUNKSIZE = 1024;
hexJS += '\x10s.erase(".firmware");\n';
var CHUNKSIZE = 2048;
for (var i=0;i<binary.length;i+=CHUNKSIZE) {
var l = binary.length-i;
if (l>CHUNKSIZE) l=CHUNKSIZE;
@ -243,10 +318,8 @@ function fileLoaded() {
});
console.log(`// Data from 0x${startAddress.toString(16)} to 0x${endAddress.toString(16)} (${endAddress-startAddress} bytes)`);
// Work out data
var HEADER_LEN = 16;
var binary = new Uint8Array(HEADER_LEN + endAddress-startAddress);
binary.fill(0); // actually seems to assume a block is filled with 0 if not complete
var bin32 = new Uint32Array(binary.buffer);
parseLines(function(addr, data) {
if (addr>MAX_ADDRESS) return; // ignore data out of range
var binAddr = HEADER_LEN + addr - startAddress;
@ -260,7 +333,7 @@ function fileLoaded() {
createJS_bootloader(new Uint8Array(binary.buffer, HEADER_LEN), startAddress, endAddress);
} else {
console.log("App - Writing to external flash");
createJS_app(binary, bin32, startAddress, endAddress);
createJS_app(binary, startAddress, endAddress);
}
}
@ -279,7 +352,7 @@ function handleUpload() {
document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false);
document.getElementById("upload").addEventListener("click", handleUpload);
checkForFileOnServer();
setTimeout(checkForFileOnServer, 10);
</script>
</body>

View File

@ -5,3 +5,4 @@
0.05: Setting to disable double/triple press control, remove touch controls setting, reduce fadeout flicker
0.06: Bangle.js 2 support
0.07: Fix "previous" button image
0.08: Fix scrolling title background color

View File

@ -91,7 +91,7 @@ function rScroller(l) {
y = l.y+l.h/2;
l.offset = l.offset%w;
g.setClipRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1)
.setColor(l.col)
.setColor(l.col).setBgColor(l.bgCol) // need to set colors: iScroll calls this function outside Layout
.setFontAlign(-1, 0) // left center
.clearRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1)
.drawString(l.label, l.x-l.offset+40, y)

View File

@ -28,3 +28,4 @@
0.24: Better support for Bangle.js 2, avoid widget area for Graphs, smooth graphs more
0.25: Fix issue where if Bangle.js 2 got a GPS fix but no reported time, errors could be caused by the widget (fix #935)
0.26: Multiple bugfixes
0.27: Map drawing with light theme (fix #1023)

View File

@ -8,3 +8,6 @@ This app allows you to record a GPS track. It can run in background. The data ca
When you turn on recording, a widget badge that looks like a satellite will appear immediately at the top of the screen. However, the recording does not begin immediately. It usually takes several minutes for the watch to get a [GPS fix](https://en.wikipedia.org/wiki/Time_to_first_fix). You will notice a blinking question mark at the lower left of the badge indicating currently getting a fix. The badge will change when a GPS fix is achieved and that is when the app actually starts writing data to the log file. You can [upload assistant files](https://banglejs.com/apps/#assisted%20gps%20update) to speed up the time spent on getting a GPS fix.
## Viewing a track
![](screenshot.png)

View File

@ -197,15 +197,14 @@ function plotTrack(info) {
g.setColor(1,0.5,0.5);
g.setFont("Vector",16);
g.drawString("Track"+info.fn.toString()+" - Loading",10,220);
g.setColor(0,0,0);
g.setColor(g.theme.bg);
g.fillRect(0,220,239,239);
if (!info.qOSTM) {
g.setColor(1, 0, 0);
g.fillRect(9,80,11,120);
g.fillPoly([9,60,19,80,0,80]);
g.setColor(1,1,1);
g.setColor(g.theme.fg);
g.drawString("N",2,40);
g.setColor(1,1,1);
} else {
osm.lat = info.lat;
osm.lon = info.lon;
@ -228,7 +227,7 @@ function plotTrack(info) {
g.setColor(0,1,0);
g.fillCircle(mp.x,mp.y,5);
if (info.qOSTM) g.setColor(1,0,0.55);
else g.setColor(1,1,1);
else g.setColor(g.theme.fg);
l = f.readLine(f);
while(l!==undefined) {
c = l.split(",");
@ -248,11 +247,11 @@ function plotTrack(info) {
g.setColor(1,0,0);
g.fillCircle(ox,oy,5);
if (info.qOSTM) g.setColor(0, 0, 0);
else g.setColor(1,1,1);
else g.setColor(g.theme.fg);
g.drawString(require("locale").distance(dist),g.getWidth() / 2, g.getHeight() - 20);
g.setFont("6x8",2);
g.setFontAlign(0,0,3);
g.drawString("Back",g.getWidth() - 10, g.getHeight() - 40);
g.drawString("Back",g.getWidth() - 10, g.getHeight()/2);
setWatch(function() {
viewTrack(info.fn, info);
}, global.BTN3||BTN1);

BIN
apps/gpsrec/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -14,3 +14,5 @@
![](screenshot2.png)
![](screenshot3.png)
![](screenshot4.png)
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/)

View File

@ -0,0 +1,3 @@
0.01: New App!
0.02: using TS and rollup to bundle
0.03: bug fixes and support bangle 1

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016-20 Ionică Bizău <bizauionica@gmail.com> (https://ionicabizau.net)
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.

View File

@ -0,0 +1,26 @@
# Hebrew Calendar
Displays the current hebrew calendar date
Add screen shots (if possible) to the app folder and link then into this file with ![](<name>.png)
## Usage
Open the app, and it shows a menu with the date components
## Features
Shows the hebrew date, month, and year; alongside the gregorian date
## Controls
Name the buttons and what they are used for
## Requests
Michael Salaverry (github.com/barakplasma)
## Creator
Michael Salaverry
with help from https://github.com/IonicaBizau/hebrew-date (MIT license)
<div>Icons made by <a href="https://www.flaticon.com/authors/smashicons" title="Smashicons">Smashicons</a> from <a href="https://www.flaticon.com/" title="Flaticon">[www.flaticon.com](https://www.flaticon.com/premium-icon/calendar_3130060?term=jewish&page=1&position=10&page=1&position=10&related_id=3130060&origin=tag)</a></div>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,17 @@
!function(){"use strict";
/*!
* This script was taked from this page and ported to Node.js by Ionic Bizu
* http://www.shamash.org/help/javadate.shtml
*
* This script was adapted from C sources written by
* Scott E. Lee, which contain the following copyright notice:
*
* Copyright 1993-1995, Scott E. Lee, all rights reserved.
* Permission granted to use, copy, modify, distribute and sell so long as
* the above copyright and this permission statement are retained in all
* copies. THERE IS NO WARRANTY - USE AT YOUR OWN RISK.
*
* Bill Hastings
* RBI Software Systems
* bhastings@rbi.com
*/var t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};var i=new function(t,i,e,o,r,n,h,a,s,f,u,l,v,c){this[0]=t,this[1]=i,this[2]=e,this[3]=o,this[4]=r,this[5]=n,this[6]=h,this[7]=a,this[8]=s,this[9]=f,this[10]=u,this[11]=l,this[12]=v,this[13]=c}("Tishri","Heshvan","Kislev","Tevet","Shevat","AdarI","AdarII","Nisan","Iyyar","Sivan","Tammuz","Av","Elul"),e=new function(t,i,e,o,r,n,h,a,s,f,u,l,v,c,y,d,m,M,b){this[0]=t,this[1]=i,this[2]=e,this[3]=o,this[4]=r,this[5]=n,this[6]=h,this[7]=a,this[8]=s,this[9]=f,this[10]=u,this[11]=l,this[12]=v,this[13]=c,this[14]=y,this[15]=d,this[16]=m,this[17]=M,this[18]=b}(12,12,13,12,12,13,12,13,12,12,13,12,12,13,12,12,13,12,13);g.clear();let o=new Date,r=function(o){var r,n,h=0,a=0,s=0,f=0,u=0,l=0,v=0;function c(t){var i,o,r,n;for(f=Math.floor((t+310)/6940),r=void 0,n=void 0,r=31524,n=(r+=45971*f)>>16,n+=2744*f,o=Math.floor(n/25920),r=(n-=25920*o)<<16|65535&r,i=Math.floor(r/25920),l=o<<16|i,v=r-=25920*i;l<t-6940+310;)f++,v+=179876755,l+=Math.floor(v/25920),v%=25920;for(u=0;u<18&&!(l>t-74);u++)v+=765433*e[u],l+=Math.floor(v/25920),v%=25920}function y(t,i,e){var o=i,r=o%7;return(e>=19440||!(2==t||5==t||7==t||10==t||13==t||16==t||18==t)&&2==r&&e>=9924||(3==t||6==t||8==t||11==t||14==t||17==t||0==t)&&1==r&&e>=16789)&&(o++,7==++r&&(r=0)),3!=r&&5!=r&&0!=r||o++,o}var d=o;return"object"===(void 0===d?"undefined":t(d))&&(r=o.getMonth()+1,n=o.getDate(),d=o.getFullYear()),function(t){var i,o=0,r=0,n=t-347997;if(c(n),n>=(o=y(u,l,v))){if(s=19*f+u+1,n<o+59)return void(n<o+30?(h=1,a=n-o+1):(h=2,a=n-o-29));v+=765433*e[u],l+=Math.floor(v/25920),r=y((u+1)%19,l,v%=25920)}else{if(s=19*f+u,n>=o-177)return void(n>o-30?(h=13,a=n-o+30):n>o-60?(h=12,a=n-o+60):n>o-89?(h=11,a=n-o+89):n>o-119?(h=10,a=n-o+119):n>o-148?(h=9,a=n-o+148):(h=8,a=n-o+178));if(13==e[(s-1)%19]){if(h=7,(a=n-o+207)>0)return;if(h--,(a+=30)>0)return;h--,a+=30}else{if(h=6,(a=n-o+207)>0)return;h--,a+=30}if(a>0)return;if(h--,(a+=29)>0)return;r=o,c(l-365),o=y(u,l,v)}if(l=n-o-29,355==(i=r-o)||385==i){if(l<=30)return h=2,void(a=l);l-=30}else{if(l<=29)return h=2,void(a=l);l-=29}h=3,a=l}(function(t,i,e){var o=0,r=0,n=void 0;return o=t<0?t+4801:t+4800,i>2?r=i-3:(r=i+9,o--),n=Math.floor(146097*Math.floor(o/100)/4),n+=Math.floor(o%100*1461/4),n+=Math.floor((153*r+2)/5),n+=e-32045}(d,r,n)),{year:s,month:h,date:a,month_name:i[h-1]}}(o);var n={"":{title:"Hebrew Date"},cal:{value:require("locale").date(o,1),onchange:()=>{}},date:{value:r.date,onchange:()=>{}},month:{value:r.month_name,onchange:()=>{}},year:{value:r.year,onchange:()=>{}}};E.showMenu(n)}();

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,23 @@
{
"name": "hebrew_calendar",
"version": "0.0.3",
"description": "Bangle.js app for seeing hebrew calendar",
"main": "app.js",
"types": "app.d.ts",
"scripts": {
"build": "rollup -c"
},
"author": {
"name": "Michael Salaverry",
"url": "https://github.com/barakplasma"
},
"license": "MIT",
"devDependencies": {
"@rollup/plugin-typescript": "^4.1.1",
"rollup": "^2.10.2",
"rollup-plugin-terser": "^5.3.0",
"terser": "^4.7.0",
"tslib": "^2.0.0",
"typescript": "^3.9.2"
}
}

View File

@ -0,0 +1,15 @@
import typescript from '@rollup/plugin-typescript';
import { terser } from 'rollup-plugin-terser';
export default {
input: './src/app.ts',
output: {
dir: '.',
format: 'iife',
name: 'hebrew_calendar'
},
plugins: [
typescript(),
terser(),
]
};

View File

@ -0,0 +1,34 @@
declare var Bangle: any;
declare var g: any;
declare var E: any;
declare var require: any;
g.clear();
let now = new Date();
import { hebrewDate } from "./hebrewDate";
let today = hebrewDate(now);
var mainmenu = {
"" : {
"title" : "Hebrew Date"
},
cal: {
value: require('locale').date(now,1),
onchange : () => {}
},
date: {
value : today.date,
onchange : () => {}
},
month: {
value : today.month_name,
onchange : () => {}
},
year: {
value : today.year,
onchange : () => {}
}
};
E.showMenu(mainmenu);

View File

@ -0,0 +1,358 @@
/*!
* This script was taked from this page and ported to Node.js by Ionic Bizu
* http://www.shamash.org/help/javadate.shtml
*
* This script was adapted from C sources written by
* Scott E. Lee, which contain the following copyright notice:
*
* Copyright 1993-1995, Scott E. Lee, all rights reserved.
* Permission granted to use, copy, modify, distribute and sell so long as
* the above copyright and this permission statement are retained in all
* copies. THERE IS NO WARRANTY - USE AT YOUR OWN RISK.
*
* Bill Hastings
* RBI Software Systems
* bhastings@rbi.com
*/
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var GREG_SDN_OFFSET = 32045,
DAYS_PER_5_MONTHS = 153,
DAYS_PER_4_YEARS = 1461,
DAYS_PER_400_YEARS = 146097;
var HALAKIM_PER_HOUR = 1080,
HALAKIM_PER_DAY = 25920,
HALAKIM_PER_LUNAR_CYCLE = 29 * HALAKIM_PER_DAY + 13753,
HALAKIM_PER_METONIC_CYCLE = HALAKIM_PER_LUNAR_CYCLE * (12 * 19 + 7);
var HEB_SDN_OFFSET = 347997,
NEW_MOON_OF_CREATION = 31524,
NOON = 18 * HALAKIM_PER_HOUR,
AM3_11_20 = 9 * HALAKIM_PER_HOUR + 204,
AM9_32_43 = 15 * HALAKIM_PER_HOUR + 589;
var SUN = 0,
MON = 1,
TUES = 2,
WED = 3,
THUR = 4,
FRI = 5,
SAT = 6;
function weekdayarr(d0, d1, d2, d3, d4, d5, d6) {
this[0] = d0;
this[1] = d1;
this[2] = d2;
this[3] = d3;
this[4] = d4;
this[5] = d5;
this[6] = d6;
}
function gregmontharr(m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11) {
this[0] = m0;
this[1] = m1;
this[2] = m2;
this[3] = m3;
this[4] = m4;
this[5] = m5;
this[6] = m6;
this[7] = m7;
this[8] = m8;
this[9] = m9;
this[10] = m10;
this[11] = m11;
}
function hebrewmontharr(m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13?: any) {
this[0] = m0;
this[1] = m1;
this[2] = m2;
this[3] = m3;
this[4] = m4;
this[5] = m5;
this[6] = m6;
this[7] = m7;
this[8] = m8;
this[9] = m9;
this[10] = m10;
this[11] = m11;
this[12] = m12;
this[13] = m13;
}
function monthsperyeararr(m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18) {
this[0] = m0;
this[1] = m1;
this[2] = m2;
this[3] = m3;
this[4] = m4;
this[5] = m5;
this[6] = m6;
this[7] = m7;
this[8] = m8;
this[9] = m9;
this[10] = m10;
this[11] = m11;
this[12] = m12;
this[13] = m13;
this[14] = m14;
this[15] = m15;
this[16] = m16;
this[17] = m17;
this[18] = m18;
}
var gWeekday = new weekdayarr("Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur"),
gMonth = new gregmontharr("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"),
hMonth = new hebrewmontharr("Tishri", "Heshvan", "Kislev", "Tevet", "Shevat", "AdarI", "AdarII", "Nisan", "Iyyar", "Sivan", "Tammuz", "Av", "Elul"),
mpy = new monthsperyeararr(12, 12, 13, 12, 12, 13, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 13);
/**
* hebrewDate
* Convert the Gregorian dates into Hebrew calendar dates.
*
* @name hebrewDate
* @function
* @param {Date|Number} inputDate The date object (representing the Gregorian date) or the year.
* @return {Object} An object containing:
*
* - `year`: The Hebrew year.
* - `month`: The Hebrew month.
* - `month_name`: The Hebrew month name.
* - `date`: The Hebrew date.
*/
export const hebrewDate = function (inputDateOrYear: Date) {
var inputMonth, inputDate;
var hebrewMonth = 0,
hebrewDate = 0,
hebrewYear = 0,
metonicCycle = 0,
metonicYear = 0,
moladDay = 0,
moladHalakim = 0;
function GregorianToSdn(inputYear, inputMonth, inputDay) {
var year = 0,
month = 0,
sdn = void 0;
// Make year a positive number
if (inputYear < 0) {
year = inputYear + 4801;
} else {
year = inputYear + 4800;
}
// Adjust the start of the year
if (inputMonth > 2) {
month = inputMonth - 3;
} else {
month = inputMonth + 9;
year--;
}
sdn = Math.floor(Math.floor(year / 100) * DAYS_PER_400_YEARS / 4);
sdn += Math.floor(year % 100 * DAYS_PER_4_YEARS / 4);
sdn += Math.floor((month * DAYS_PER_5_MONTHS + 2) / 5);
sdn += inputDay - GREG_SDN_OFFSET;
return sdn;
}
function SdnToHebrew(sdn) {
var tishri1 = 0,
tishri1After = 0,
yearLength = 0,
inputDay = sdn - HEB_SDN_OFFSET;
FindTishriMolad(inputDay);
tishri1 = Tishri1(metonicYear, moladDay, moladHalakim);
if (inputDay >= tishri1) {
// It found Tishri 1 at the start of the year.
hebrewYear = metonicCycle * 19 + metonicYear + 1;
if (inputDay < tishri1 + 59) {
if (inputDay < tishri1 + 30) {
hebrewMonth = 1;
hebrewDate = inputDay - tishri1 + 1;
} else {
hebrewMonth = 2;
hebrewDate = inputDay - tishri1 - 29;
}
return;
}
// We need the length of the year to figure this out,so find Tishri 1 of the next year.
moladHalakim += HALAKIM_PER_LUNAR_CYCLE * mpy[metonicYear];
moladDay += Math.floor(moladHalakim / HALAKIM_PER_DAY);
moladHalakim = moladHalakim % HALAKIM_PER_DAY;
tishri1After = Tishri1((metonicYear + 1) % 19, moladDay, moladHalakim);
} else {
// It found Tishri 1 at the end of the year.
hebrewYear = metonicCycle * 19 + metonicYear;
if (inputDay >= tishri1 - 177) {
// It is one of the last 6 months of the year.
if (inputDay > tishri1 - 30) {
hebrewMonth = 13;
hebrewDate = inputDay - tishri1 + 30;
} else if (inputDay > tishri1 - 60) {
hebrewMonth = 12;
hebrewDate = inputDay - tishri1 + 60;
} else if (inputDay > tishri1 - 89) {
hebrewMonth = 11;
hebrewDate = inputDay - tishri1 + 89;
} else if (inputDay > tishri1 - 119) {
hebrewMonth = 10;
hebrewDate = inputDay - tishri1 + 119;
} else if (inputDay > tishri1 - 148) {
hebrewMonth = 9;
hebrewDate = inputDay - tishri1 + 148;
} else {
hebrewMonth = 8;
hebrewDate = inputDay - tishri1 + 178;
}
return;
} else {
if (mpy[(hebrewYear - 1) % 19] == 13) {
hebrewMonth = 7;
hebrewDate = inputDay - tishri1 + 207;
if (hebrewDate > 0) return;
hebrewMonth--;
hebrewDate += 30;
if (hebrewDate > 0) return;
hebrewMonth--;
hebrewDate += 30;
} else {
hebrewMonth = 6;
hebrewDate = inputDay - tishri1 + 207;
if (hebrewDate > 0) return;
hebrewMonth--;
hebrewDate += 30;
}
if (hebrewDate > 0) return;
hebrewMonth--;
hebrewDate += 29;
if (hebrewDate > 0) return;
// We need the length of the year to figure this out,so find Tishri 1 of this year.
tishri1After = tishri1;
FindTishriMolad(moladDay - 365);
tishri1 = Tishri1(metonicYear, moladDay, moladHalakim);
}
}
yearLength = tishri1After - tishri1;
moladDay = inputDay - tishri1 - 29;
if (yearLength == 355 || yearLength == 385) {
// Heshvan has 30 days
if (moladDay <= 30) {
hebrewMonth = 2;
hebrewDate = moladDay;
return;
}
moladDay -= 30;
} else {
// Heshvan has 29 days
if (moladDay <= 29) {
hebrewMonth = 2;
hebrewDate = moladDay;
return;
}
moladDay -= 29;
}
// It has to be Kislev.
hebrewMonth = 3;
hebrewDate = moladDay;
}
function FindTishriMolad(inputDay) {
// Estimate the metonic cycle number. Note that this may be an under
// estimate because there are 6939.6896 days in a metonic cycle not
// 6940,but it will never be an over estimate. The loop below will
// correct for any error in this estimate.
metonicCycle = Math.floor((inputDay + 310) / 6940);
// Calculate the time of the starting molad for this metonic cycle.
MoladOfMetonicCycle();
// If the above was an under estimate,increment the cycle number until
// the correct one is found. For modern dates this loop is about 98.6%
// likely to not execute,even once,because the above estimate is
// really quite close.
while (moladDay < inputDay - 6940 + 310) {
metonicCycle++;
moladHalakim += HALAKIM_PER_METONIC_CYCLE;
moladDay += Math.floor(moladHalakim / HALAKIM_PER_DAY);
moladHalakim = moladHalakim % HALAKIM_PER_DAY;
}
// Find the molad of Tishri closest to this date.
for (metonicYear = 0; metonicYear < 18; metonicYear++) {
if (moladDay > inputDay - 74) break;
moladHalakim += HALAKIM_PER_LUNAR_CYCLE * mpy[metonicYear];
moladDay += Math.floor(moladHalakim / HALAKIM_PER_DAY);
moladHalakim = moladHalakim % HALAKIM_PER_DAY;
}
}
function MoladOfMetonicCycle() {
var r1 = void 0,
r2 = void 0,
d1 = void 0,
d2 = void 0;
// Start with the time of the first molad after creation.
r1 = NEW_MOON_OF_CREATION;
// Calculate gMetonicCycle * HALAKIM_PER_METONIC_CYCLE. The upper 32
// bits of the result will be in r2 and the lower 16 bits will be in r1.
r1 += metonicCycle * (HALAKIM_PER_METONIC_CYCLE & 0xFFFF);
r2 = r1 >> 16;
r2 += metonicCycle * (HALAKIM_PER_METONIC_CYCLE >> 16 & 0xFFFF);
// Calculate r2r1 / HALAKIM_PER_DAY. The remainder will be in r1,the
// upper 16 bits of the quotient will be in d2 and the lower 16 bits
// will be in d1.
d2 = Math.floor(r2 / HALAKIM_PER_DAY);
r2 -= d2 * HALAKIM_PER_DAY;
r1 = r2 << 16 | r1 & 0xFFFF;
d1 = Math.floor(r1 / HALAKIM_PER_DAY);
r1 -= d1 * HALAKIM_PER_DAY;
moladDay = d2 << 16 | d1;
moladHalakim = r1;
}
function Tishri1(metonicYear, moladDay, moladHalakim) {
var tishri1 = moladDay,
dow = tishri1 % 7,
leapYear = metonicYear == 2 || metonicYear == 5 || metonicYear == 7 || metonicYear == 10 || metonicYear == 13 || metonicYear == 16 || metonicYear == 18,
lastWasLeapYear = metonicYear == 3 || metonicYear == 6 || metonicYear == 8 || metonicYear == 11 || metonicYear == 14 || metonicYear == 17 || metonicYear == 0;
// Apply rules 2,3 and 4
if (moladHalakim >= NOON || !leapYear && dow == TUES && moladHalakim >= AM3_11_20 || lastWasLeapYear && dow == MON && moladHalakim >= AM9_32_43) {
tishri1++;
dow++;
if (dow == 7) dow = 0;
}
// Apply rule 1 after the others because it can cause an additional delay of one day.
if (dow == WED || dow == FRI || dow == SUN) {
tishri1++;
}
return tishri1;
}
var inputYear: Date | number = inputDateOrYear;
if ((typeof inputYear === "undefined" ? "undefined" : _typeof(inputYear)) === "object") {
inputMonth = inputDateOrYear.getMonth() + 1;
inputDate = inputDateOrYear.getDate();
inputYear = inputDateOrYear.getFullYear();
}
SdnToHebrew(GregorianToSdn(inputYear, inputMonth, inputDate));
return {
year: hebrewYear,
month: hebrewMonth,
date: hebrewDate,
month_name: hMonth[hebrewMonth - 1]
};
};

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "es2015",
"noImplicitAny": false,
"target": "es2015"
},
"include": [
"src"
]
}

View File

@ -1,3 +1,7 @@
0.01: New App!
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.06: Fix (not) popupping up old messages

View File

@ -26,6 +26,13 @@ E.on('ANCS',msg=>{
function ancsHandler() {
var msg = Bangle.ancsMessageQueue[0];
NRF.ancsGetNotificationInfo( msg.uid ).then( info => {
if(msg.preExisting === true){
info.new = false;
} else {
info.new = true;
}
E.emit("notify", Object.assign(msg, info));
Bangle.ancsMessageQueue.shift();
if (Bangle.ancsMessageQueue.length)
@ -49,16 +56,48 @@ E.on('notify',msg=>{
"message" : string,
"messageSize" : string,
"date" : string,
"new" : boolean,
"posAction" : string,
"negAction" : string,
"name" : string,
*/
var appNames = {
"com.netflix.Netflix" : "Netflix",
"com.google.ios.youtube" : "YouTube",
"com.apple.facetime": "FaceTime",
"com.apple.mobilecal": "Calendar",
"com.apple.mobilemail": "Mail",
"com.apple.MobileSMS": "SMS Message",
"com.apple.Passbook": "iOS Wallet",
"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.google.Chromecast" : "Google Home",
"com.google.Gmail" : "GMail",
"com.google.hangouts" : "Hangouts",
"com.google.ios.youtube" : "YouTube",
"com.hammerandchisel.discord" : "Discord",
"com.ifttt.ifttt" : "IFTTT",
"com.jumbo.app" : "Jumbo",
"com.linkedin.LinkedIn" : "LinkedIn",
"com.nestlabs.jasper.release" : "Nest",
"com.netflix.Netflix" : "Netflix",
"com.reddit.Reddit" : "Reddit",
"com.skype.skype": "Skype",
"com.skype.SkypeForiPad": "Skype",
"com.atebits.Tweetie2": "Twitter"
"com.spotify.client": "Spotify",
"com.tinyspeck.chatlyio": "Slack",
"com.toyopagroup.picaboo": "Snapchat",
"com.ubercab.UberClient": "Uber",
"com.ubercab.UberEats": "UberEats",
"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",
// could also use NRF.ancsGetAppInfo(msg.appId) here
};
var unicodeRemap = {
@ -70,6 +109,7 @@ E.on('notify',msg=>{
t : msg.event,
id : msg.uid,
src : appNames[msg.appId] || msg.appId,
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)

View File

@ -8,3 +8,4 @@
0.08: Merge Bangle.js 1 and 2 launchers
0.09: Bangle.js 2 - pressing the button goes back to clock (fix #971)
After 10s of being locked, the launcher goes back to the clock screen
0.10: added in selectable font in settings including scalable vector font

View File

@ -1,4 +1,22 @@
var s = require("Storage");
let fonts = g.getFonts();
var scaleval = 1;
var vectorval = 20;
var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
let settings = require('Storage').readJSON("launch.json", true) || {};
if ("vectorsize" in settings) {
vectorval = parseInt(settings.vectorsize);
}
if ("font" in settings){
if(settings.font == "Vector"){
scaleval = vectorval/20;
font = "Vector"+(vectorval).toString();
}
else{
font = settings.font;
scaleval = (font.split('x')[1])/20;
}
}
var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type));
apps.sort((a,b)=>{
var n=(0|a.sortorder)-(0|b.sortorder);
@ -11,8 +29,6 @@ apps.forEach(app=>{
if (app.icon)
app.icon = s.read(app.icon); // should just be a link to a memory area
});
// FIXME: not needed after 2v11
var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
// FIXME: check not needed after 2v11
if (g.wrapString) {
g.setFont(font);
@ -22,9 +38,9 @@ if (g.wrapString) {
function drawApp(i, r) {
var app = apps[i];
if (!app) return;
g.clearRect(r.x,r.y,r.x+r.w-1, r.y+r.h-1);
g.setFont(font).setFontAlign(-1,0).drawString(app.name,64,r.y+32);
if (app.icon) try {g.drawImage(app.icon,8,r.y+8);} catch(e){}
g.clearRect((r.x),(r.y),(r.x+r.w-1), (r.y+r.h-1));
g.setFont(font).setFontAlign(-1,0).drawString(app.name,64*scaleval,r.y+(32*scaleval));
if (app.icon) try {g.drawImage(app.icon,8*scaleval, r.y+(8*scaleval), {scale: scaleval});} catch(e){}
}
g.clear();
@ -32,7 +48,7 @@ Bangle.loadWidgets();
Bangle.drawWidgets();
E.showScroller({
h : 64, c : apps.length,
h : 64*scaleval, c : apps.length,
draw : drawApp,
select : i => {
var app = apps[i];

25
apps/launch/settings.js Normal file
View File

@ -0,0 +1,25 @@
// make sure to enclose the function in parentheses
(function(back) {
let settings = require('Storage').readJSON('launch.json',1)||{};
let fonts = g.getFonts();
function save(key, value) {
settings[key] = value;
require('Storage').write('launch.json',settings);
}
const appMenu = {
'': {'title': 'Launcher Settings'},
'< Back': back,
'Font': {
value: fonts.includes(settings.font)? fonts.indexOf(settings.font) : fonts.indexOf("12x20"),
min:0, max:fonts.length-1, step:1,wrap:true,
onchange: (m) => {save('font', fonts[m])},
format: v => fonts[v]
},
'Vector font size': {
value: settings.vectorsize || 10,
min:10, max: 20,step:1,wrap:true,
onchange: (m) => {save('vectorsize', m)}
}
};
E.showMenu(appMenu);
});

View File

@ -10,3 +10,6 @@
0.08: Added Mavigation units and en_NAV
0.09: Added New Zealand en_NZ
0.10: Apply 12hour setting to time
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

View File

@ -130,7 +130,7 @@ var locales = {
temperature: "°C",
ampm: { 0: "", 1: "" },
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
datePattern: { 0: "%A, %d. %B %Y", "1": "%d.%m.%Y" }, // Sonntag, 1. März 2020 // 01.01.20
datePattern: { 0: "%d. %b %Y", "1": "%d.%m.%Y" }, // 1. Mär 2020 // 01.03.20
abmonth: "Jan,Feb,Mär,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez",
month: "Januar,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember",
abday: "So,Mo,Di,Mi,Do,Fr,Sa",
@ -184,12 +184,29 @@ var locales = {
temperature: "°C",
ampm: { 0: "", 1: "" },
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
datePattern: { 0: "%A %B %d %Y", 1: "%d.%m.%y" }, // zondag 1 maart 2020 // 01.01.20
datePattern: { 0: "%d %b %Y", 1: "%d-%m-%Y" }, // 28 feb 2020 // 28-02-2020
abday: "zo,ma,di,wo,do,vr,za",
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",
// No translation for english...
trans: { yes: "ja", Yes: "Ja", no: "nee", No: "Nee", ok: "ok", on: "aan", off: "uit", "< Back": "< Terug" }
},
"en_NL": { // English date units with Dutch number, currency and navigation units.
lang: "en_NL",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "€",
int_curr_symbol: "EUR",
speed: "km/h",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: { 0: "am", 1: "pm" },
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%b %d %Y", 1: "%d/%m/%Y" }, // Feb 28 2020" // "01/03/2020"(short)
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
month: "January,February,March,April,May,June,July,August,September,October,November,December",
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
},
"en_CA": {
lang: "en_CA",

View File

@ -1,6 +1,6 @@
# Mandlebrot Clock
# Mandelbrot Clock
A simple clock themed on the mandlebrot set.
A simple clock themed on the mandelbrot set.
Written by [James Milner](https://www.github.com/jameslmilner)

View File

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

View File

@ -1,6 +1,6 @@
// MIT License - James Milner 2021
const mandlebrotBmp = {
const mandelbrotBmp = {
width: 176,
height: 176,
bpp: 8,
@ -13,7 +13,7 @@ const mandlebrotBmp = {
};
function draw() {
g.drawImage(mandlebrotBmp);
g.drawImage(mandelbrotBmp);
// work out how to display the current time
const d = new Date();
const h = d.getHours(),

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

1
apps/menuwheel/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New menu!

25
apps/menuwheel/README.md Normal file
View File

@ -0,0 +1,25 @@
# Wheel Menu
Replace Bangle.js 2's menus with a version that contains variable-size text and a back button.
Bangle.js 1:
![Dark Mode Screenshot](screenshot_b1_dark.png)
![Light Mode Screenshot](screenshot_b1_light.png)
Bangle.js 2:
![Dark Mode Screenshot](screenshot_b2_dark.png)
![Editing Screenshot](screenshot_b2_edit.png)
![Light Mode Screenshot](screenshot_b2_light.png)
## Features
If the menu contains "Back" or "Exit", it is shown as a button instead.
The menu wraps around, with a divider between the last and first items.
## Controls
Bangle.js 1: Use BTN1/BTN3 to scroll through items, BTN2 to open/edit the selected item.
Bangle.js 2: Swipe up/down to scroll through items, tap/BTN to open/edit the selected item.
Press the back button (if present) to go back.

213
apps/menuwheel/boot.js Normal file
View File

@ -0,0 +1,213 @@
E.showMenu = function(items) {
g.clearRect(Bangle.appRect); // clear screen if no menu supplied
// clean up back button listener
if (Bangle.backHandler) Bangle.removeListener('touch', Bangle.backHandler)
delete Bangle.backHandler;
if (!items) {
Bangle.setUI();
return;
}
var B2 = process.env.HWVERSION===2,
loc = require("locale"),
menuItems = Object.keys(items),
options = items[""];
if (options) menuItems.splice(menuItems.indexOf(""),1);
if (!(options instanceof Object)) options = {};
// show "< Back" item (or similar) as button instead (i.e. remove from the menu)
var back,backLbl;
for (var b of ['Back', 'Exit', 'Cancel']) {
if (!items[b] && items['< '+b]) b = '< '+b;
back = items[b];
if (typeof back === "function") {
backLbl = loc.translate(b);
menuItems.splice(menuItems.indexOf(b),1);
break;
}
else back = undefined;
}
// font sizes
var small = B2?15:22,
large = B2?30:45;
if (options.selected === undefined) options.selected = 0;
var ar = Bangle.appRect,
x = ar.x,
x2 = ar.x2,
w = ar.w,
y = ar.y,
y2 = ar.y2;
if (options.title) y += 22;
var wrap = menuItems.length>3; // don't wrap if all items are always in view anyway
var vc=Math.round((y+y2)/2), // vertical center
hc = Math.round((x+x2)/2), // horizontal center
ih = large+small*2; // active item height
var getItem = idx => {
// we wrap out-of-range indexes
while (idx<0) idx+=menuItems.length;
idx = idx%menuItems.length;
var name = menuItems[idx];
var item = items[name];
var v;
if ("object"== typeof item) {
v = item.value;
if (item.format) v = item.format(v);
v = loc.translate(""+v);
}
return {lbl: loc.translate(name), v: v};
};
var l = {
lastIdx : null, // we want a complete redraw on first run
draw : function() {
var idx = options.selected,
edit = l.selectEdit;
g.reset();
// don't highlight whole item when editing
g.setColor(edit?g.theme.fg:g.theme.fgH)
.setBgColor(edit?g.theme.bg:g.theme.bgH)
.setFont('Vector', large);
var item = getItem(idx),
lw = g.stringWidth(item.lbl)+2;
if (lw+2 >= w) { // label width doesn't fit at large size: scale it down
g.setFont('Vector', Math.floor(large*ar.w/lw));
}
g.clearRect(x,vc-ih/2,x2,vc+ih/2)
.setFontAlign(0,0,0).drawString(item.lbl,hc,vc);
if (item.v !== undefined) {
g.setColor(g.theme.fgH).setBgColor(g.theme.bgH) // always highlighted: either as part of item, or while editing
.setFontAlign(0,1,0)
.setFont('Vector', small)
.clearRect(x,vc+ih/2-small-2,x2,vc+ih/2)
.drawString(item.v,hc,vc+ih/2-1);
if (edit) {
g.drawImage("\x0c\x05\x81\x00 \x07\x00\xF9\xF0\x0E\x00@",x2-23,vc+ih/2-small+(B2?1:5),{scale:2});
}
}
if (l.lastIdx !== idx) {
// we scrolled: redraw all
l.lastIdx=idx;
g.reset();
if (options.title) {
if (B2) g.setFont('12x20');
else g.setFont('6x8',2);
g.drawLine(x, y-2, x2, y-2)
.setFontAlign(0,1,0)
.drawString(options.title, (x+x2)/2, y-2);
}
// clear prev/next items area
g.clearRect(x,y,x2,vc-ih/2-1)
.clearRect(x,vc+ih/2+1,x2,y2);
// get display label by index
var lbl = idx => {
var item = getItem(idx);
if (item.v !== undefined) item.lbl+=': '+item.v;
return item.lbl;
}
// previous two items
g.setFontAlign(0, 1)
if (wrap||idx>0) g.setFont('Vector', small).drawString(lbl(idx-1), hc, vc-ih/2-5);
if (wrap||idx>1) g.setFont('Vector', small/2).drawString(lbl(idx-2), hc, vc-ih/2-small-10);
// next two items
g.setFontAlign(0, -1);
if (wrap||idx<menuItems.length-1) g.setFont('Vector', small).drawString(lbl(idx+1), hc, vc+ih/2+5);
if (wrap||idx<menuItems.length-2) g.setFont('Vector', small/2).drawString(lbl(idx+2), hc, vc+ih/2+small+10);
if (wrap) {
// draw divider between first/last items
var div = y => g.drawLine(x, y, x2, y);
if (idx===0) div(vc-ih/2-1);
if (idx===1) div(vc-ih/2-small-8);
// if (s === 2) div(vc-ih/2-small*1.5-13);
if (idx===menuItems.length-1) div(vc+ih/2+1);
if (idx===menuItems.length-2) div(vc+ih/2+small+6);
// if (s === 2) div(vc+ih/2+small*1.5+13);
}
if (back) {
g.setBgColor(g.theme.bg2)
.setFont('Vector', small);
var bw=g.stringWidth(backLbl);
g.clearRect(x,y, x+bw+2, y+small+2);
var bx1=x, by1=y, bx2=x+bw+2, by2=y+small+2;
// g.drawRect(x,y, x+bw+2, y+small+2);
var poly = [ // button outline
bx1+2,by1,
bx2-2,by1,
bx2, by1+2,
bx2, by2-2,
bx2-2,by2,
bx1+2,by2,
bx1, by2-2,
bx1, by1+2,
]
g.setColor(g.theme.bg2).fillPoly(poly, true)
.setColor(g.theme.fg2).drawPoly(poly, true)
.setFontAlign(-1,-1,0).drawString(backLbl, x+2,y+2);
}
}
g.flip();
},
select : function() { // same as default menu
var item = items[menuItems[options.selected]];
if ("function" == typeof item) {l.lastIdx=null; item(l);} // force a redraw after callback
else if ("object" == typeof item) {
// if a number, go into 'edit mode'
if ("number" == typeof item.value)
l.selectEdit = l.selectEdit?undefined:item;
else { // else just toggle bools
if ("boolean" == typeof item.value) item.value=!item.value;
if (item.onchange) {l.lastIdx=null; item.onchange(item.value);} // force a redraw after callback
}
l.draw();
}
},
move : function(dir) {
if (l.selectEdit) { // same as default menu
var item = l.selectEdit;
item.value -= (dir||1)*(item.step||1);
if (item.min!==undefined && item.value<item.min) item.value = item.wrap ? item.max : item.min;
if (item.max!==undefined && item.value>item.max) item.value = item.wrap ? item.min : item.max;
if (item.onchange) {l.lastIdx=null; item.onchange(item.value);} // force a redraw after callback
} else {
if (B2) dir=-dir; // swipe vs button scrolling
if (!wrap && (options.selected+dir<0 || options.selected+dir>=menuItems.length)) {
return;
}
options.selected = (options.selected+dir+menuItems.length)%menuItems.length;
}
l.draw();
}
};
l.draw();
Bangle.setUI("updown",dir => {
if (dir) l.move(dir);
else l.select();
});
if (back) {
// we have a back button: check touches before passing them to setUI's touchHandler
if (B2) {
Bangle.removeListener('touch', Bangle.touchHandler);
Bangle.backHandler = (b, xy) => {
// anywhere top-left (but above the active item) = back button
if (xy.x<hc && xy.y<vc-ih/2) back();
else Bangle.touchHandler(b, xy);
}
} else {
// Bangle.js 1 has no touchHandler
Bangle.backHandler = (b) => {
// left side = back button
if (b===1) back();
}
}
// note: backHandler is cleaned up at the top of this file
Bangle.on('touch', Bangle.backHandler);
}
return l;
};

BIN
apps/menuwheel/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -8,3 +8,9 @@
Back now marks a message as read
Clicking top-left opens a menu which allows you to delete a message or mark unread
0.07: Added settings menu with option to choose vibrate pattern and frequency (fix #909)
0.08: Fix rendering of long messages (fix #969)
buzz on new message (fix #999)
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)

View File

@ -8,9 +8,17 @@ and responded to.
It is a replacement for the old `notify`/`gadgetbridge` apps.
## Usage
## Settings
You can change settings by going to the global `Settings` app, then `App Settings`
and `Messages`:
* `Vibrate` - This is the pattern of buzzes that should be made when a new message is received
* `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.
...
## Requests

View File

@ -21,6 +21,7 @@
*/
var Layout = require("Layout");
var fontSmall = "6x8";
var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2";
var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2";
var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4";
@ -41,7 +42,12 @@ try {
};
}
/** this is a timeout if the app has started and is showing a single message
but the user hasn't seen it (eg no user input) - in which case
we should start a timeout for settings.unreadTimeout to return
to the clock. */
var unreadTimeout;
/// List of all our messages
var MESSAGES = require("Storage").readJSON("messages.json",1)||[];
if (!Array.isArray(MESSAGES)) MESSAGES=[];
var onMessagesModified = function(msg) {
@ -68,7 +74,7 @@ function getNegImage() {
function getMessageImage(msg) {
if (msg.img) return atob(msg.img);
var s = (msg.src||"").toLowerCase();
if (s=="Phone") return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA=");
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==");
@ -170,27 +176,36 @@ function showMessageSettings(msg) {
function showMessage(msgid) {
var msg = MESSAGES.find(m=>m.id==msgid);
if (!msg) return checkMessages(); // go home if no message found
if (msg.src=="Maps") return showMapMessage(msg);
if (msg.id=="music") return showMusicMessage(msg);
if (msg.src=="Maps") {
cancelReloadTimeout(); // don't auto-reload to clock now
return showMapMessage(msg);
}
if (msg.id=="music") {
cancelReloadTimeout(); // don't auto-reload to clock now
return showMusicMessage(msg);
}
// Normal text message display
var title=msg.title, titleFont = fontLarge;
var title=msg.title, titleFont = fontLarge, lines;
if (title) {
var w = g.getWidth()-40;
var w = g.getWidth()-48;
if (g.setFont(titleFont).stringWidth(title) > w)
titleFont = fontMedium;
if (g.setFont(titleFont).stringWidth(title) > w)
title = g.wrapString(title, w).join("\n");
if (g.setFont(titleFont).stringWidth(title) > w) {
lines = g.wrapString(title, w);
title = (lines.length>2) ? lines.slice(0,2).join("\n")+"..." : lines.join("\n");
}
}
var buttons = [
{type:"btn", src:getBackImage(), cb:()=>{
msg.new = false; // read mail
saveMessages();
msg.new = false; saveMessages(); // read mail
cancelReloadTimeout(); // don't auto-reload to clock now
checkMessages({clockIfNoMsg:1,clockIfAllRead:0,showMsgIfUnread:1});
}} // back
];
if (msg.positive) {
buttons.push({type:"btn", src:getPosImage(), cb:()=>{
msg.new = false; saveMessages();
cancelReloadTimeout(); // don't auto-reload to clock now
Bangle.messageResponse(msg,true);
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1});
}});
@ -199,19 +214,25 @@ function showMessage(msgid) {
buttons.push({type:"btn", src:getNegImage(), cb:()=>{
console.log("Response");
msg.new = false; saveMessages();
cancelReloadTimeout(); // don't auto-reload to clock now
Bangle.messageResponse(msg,false);
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1});
}});
}
lines = g.wrapString(msg.body, g.getWidth()-10);
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:()=>showMessageSettings(msg) },
{ type:"btn", src:getMessageImage(msg), cb:()=>{
cancelReloadTimeout(); // don't auto-reload to clock now
showMessageSettings(msg);
}},
{ type:"v", fillx:1, c: [
{type:"txt", font:fontMedium, label:msg.src||"Message", bgCol:colBg, fillx:1, pad:2 },
{type:"txt", font:fontSmall, label:msg.src||"Message", bgCol:colBg, fillx:1, pad:2, halign:1 },
title?{type:"txt", font:titleFont, label:title, bgCol:colBg, fillx:1, pad:2 }:{},
]},
]},
{type:"txt", font:fontMedium, label:msg.body||"", wrap:true, fillx:1, filly:1, pad:2 },
{type:"txt", font:fontMedium, label:body, fillx:1, filly:1, pad:2 },
{type:"h",fillx:1, c: buttons}
]});
g.clearRect(Bangle.appRect);
@ -244,7 +265,8 @@ function checkMessages(options) {
// no new messages - go to clock?
if (options.clockIfAllRead && newMessages.length==0)
return load();
// we don't have to time out of this screen...
cancelReloadTimeout();
// Otherwise show a menu
E.showScroller({
h : 48,
@ -286,9 +308,23 @@ function checkMessages(options) {
});
}
function cancelReloadTimeout() {
if (!unreadTimeout) return;
clearTimeout(unreadTimeout);
unreadTimeout = undefined;
}
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
setTimeout(() => {
var unreadTimeoutSecs = (require('Storage').readJSON("messages.settings.json", true) || {}).unreadTimeout;
if (unreadTimeoutSecs===undefined) unreadTimeoutSecs=60;
if (unreadTimeoutSecs)
unreadTimeout = setTimeout(function() {
print("Message not seen - reloading");
load();
}, unreadTimeoutSecs*1000);
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:1});
},10); // if checkMessages wants to 'load', do that

View File

@ -1,10 +1,10 @@
/* Push a new message onto messages queue, event is:
{t:"add",id:int, src,title,subject,body,sender,tel, important:bool, new:bool}
{t:"add",id:int, id:"music", state, artist, track, etc} // add new
{t:"remove-",id:int} // remove
{t:"modify",id:int, title:string} // modified
*/
exports.pushMessage = function(event) {
/* event is:
{t:"add",id:int, src,title,subject,body,sender,tel, important:bool} // add new
{t:"add",id:int, id:"music", state, artist, track, etc} // add new
{t:"remove-",id:int} // remove
{t:"modify",id:int, title:string} // modified
*/
var messages, inApp = "undefined"!=typeof MESSAGES;
if (inApp)
messages = MESSAGES; // we're in an app that has already loaded messages
@ -16,7 +16,11 @@ exports.pushMessage = function(event) {
if (mIdx>=0) messages.splice(mIdx, 1); // remove item
mIdx=-1;
} else { // add/modify
if (event.t=="add") event.new=true; // new message
if (event.t=="add"){
if(event.new === undefined ) { // If 'new' has not been set yet, set it
event.new=true; // Assume it should be new
}
}
if (mIdx<0) {
mIdx=0;
messages.unshift(event); // add new messages to the beginning
@ -27,17 +31,27 @@ exports.pushMessage = function(event) {
// if in app, process immediately
if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]);
// ok, saved now - we only care if it's new
if (event.t!="add") return;
// otherwise load after a delay, to ensure we have all the messages
if (event.t!="add") {
return;
} else if(event.new == false) {
return;
}
// otherwise load messages/show widget
var loadMessages = Bangle.CLOCK || event.important;
// first, buzz
if (loadMessages && global.WIDGETS && WIDGETS.messages)
WIDGETS.messages.buzz();
// after a delay load the app, to ensure we have all the messages
if (exports.messageTimeout) clearTimeout(exports.messageTimeout);
exports.messageTimeout = setTimeout(function() {
exports.messageTimeout = undefined;
// if we're in a clock or it's important, go straight to messages app
if (Bangle.CLOCK || event.important) return load("messages.app.js");
if (loadMessages) return load("messages.app.js");
if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know
WIDGETS.messages.show();
}, 500);
}
/// Remove all messages
exports.clearAll = function(event) {
var messages, inApp = "undefined"!=typeof MESSAGES;
if (inApp) {

View File

@ -3,6 +3,7 @@
let settings = require('Storage').readJSON("messages.settings.json", true) || {};
if (settings.vibrate===undefined) settings.vibrate=".";
if (settings.repeat===undefined) settings.repeat=4;
if (settings.unreadTimeout===undefined) settings.unreadTimeout=60;
return settings;
}
function updateSetting(setting, value) {
@ -30,6 +31,12 @@
format: v => v+"s",
onchange: v => updateSetting("repeat", v)
},
'Unread timer': {
value: settings().unreadTimeout,
min: 0, max: 240, step : 10,
format: v => v?v+"s":"Off",
onchange: v => updateSetting("unreadTimeout", v)
},
};
E.showMenu(mainmenu);
})

View File

@ -1,5 +1,5 @@
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");
@ -13,9 +13,11 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() {
WIDGETS["messages"].buzz(); // buzz every 4 seconds
}
setTimeout(()=>WIDGETS["messages"].draw(), 1000);
},show:function() {
if (process.env.HWVERSION>1) Bangle.on('touch', this.touch);
},show:function(quiet) {
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;
Bangle.drawWidgets();
Bangle.setLCDPower(1);// turns screen on
@ -33,4 +35,15 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() {
if (c=="-") Bangle.buzz(500).then(()=>setTimeout(b,100));
}
b();
},touch:function(b,c) {
var w=WIDGETS["messages"];
if (!w||!w.width||c.x<w.x||c.x>w.x+w.width||c.y<w.y||c.y>w.y+23) return;
load("messages.app.js");
}};
/* We might have returned here if we were in the Messages app for a
message but then the watch was never viewed. In that case we don't
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);
})();

View File

@ -14,3 +14,4 @@
0.10: Add birthday style
0.11: Skip double buffering, use 240x240 size
0.12: Fix swipe direction (#800)
0.13: Bangle.js 2 support

View File

@ -0,0 +1,254 @@
// exec each function from seq one after the other
function animate(seq,period) {
var c = g.getColor();
var i = setInterval(function() {
if (seq.length) {
var f = seq.shift();
g.setColor(c);
if (f) f();
} else clearInterval(i);
},period);
}
// Fade in to FG color with angled lines
function fade(col, callback) {
var n = 0;
function f() {"ram"
g.setColor(col);
for (var i=n;i<240;i+=10) g.drawLine(i,0,0,i).drawLine(i,240,240,i);
g.flip();
n++;
if (n<10) setTimeout(f,0);
else callback();
}
f();
}
var SCENE_COUNT=11;
function getScene(n) {
if (n==0) return function() {
console.log("Start app");
g.clear(1);
eval(require("Storage").read("mywelcome.custom.js"));
}
if (n==1) return function() {
g.reset().setBgColor(0).clearRect(0,0,176,176);
g.setFont("6x15");
var n=0;
var l = Bangle.getLogo();
var im = g.imageMetrics(l);
var i = setInterval(function() {
n+=0.1;
g.setColor(n,n,n);
g.drawImage(l,(176-im.width)/2,(176-im.height)/2);
if (n>=1) {
clearInterval(i);
setTimeout(()=>g.drawString("Open",44,104), 500);
setTimeout(()=>g.drawString("Hackable",44,116), 1000);
setTimeout(()=>g.drawString("Smart Watch",44,128), 1500);
}
},50);
};
if (n==2) return function() {
var img = require("heatshrink").decompress(atob("ptR4n/j/4gH+8H5wl+jOukVVoHZ8dt/n//n37OtgH9sHhwHp4H5xmkGiH72MRje/LL/7iIAEE7sPEgoAC+AlagIlIiMQErPxDwUYxAABwIHCj8N7nOl3uEqa6BEggnFjfM5nCkUil3gEq5KDAAQmC6QmBE4JxSEhIABiQmB8QmSXoQlCYRMdEwIlCAAIlNhYlOiO85nNEyMPEoZwIAAcsYIYmPXoYlMiKaFExX/u9VEqLBBOYrCH+czmtVqJyDEpiaCOYsgSYszmc3qtTEqMR7hzG8AlGmd1OQglOOY6aEgYlCmmZoJMCTBrnD6SaIEoU/zOUuolSjbnBJgqaCEoU5zOXX4RyQYBBzCS4X5zNDqqZCJiERJg5zBEoVJEoM1JgYlQjhMHc4JLEmZMEEp6ZIJgPzS4WTmZMVTILmFYAK+BmglCmd1JgUYJiPNEorABEIOZygDBm5MCiJMQlhMH8ByBXwIlBJgUxJiMd5nOTIzlBTAK+BAANVq4jPAAS/HJgJyCTATAEACC/B4S/IJgIlCYAgAPiS/Kn5yEYANTEyPc5niOQxMB/LlCOapyJJgbpBYAZzROQK/Gl0ATIWfEoZzBc6IlB6SYGgBJBJgpzSlhyH8EAh5MBTIjnCuIlOjjlHTAJzC/LmDTSSYIEoTABOYIlETSKYHXwIABOYM0yYmETSCYHEobnDOYqaBExu8TAwlEc4U5EoiaCmK+NTAolFEwX0TQzBMXwXiEpTBCAAomNEoS+EEo4mIYIImKEoS+EEpDoBEyUbEo3gEo4mJdAImIJY4lJEycdEoPOOBYmPuIlE+HcJYhKKTZ1fhYkB2EAhnNcYMuEhomMr8A3YABEoJyB5gjOAAYmHm9VgELEoJMBEoXAEyXzE45YBJgXwEqx1I+ByDOYJyVJw5yCgEB3cQGgJMWJwQnCu6/CgFBigDB13S/glVAAf1qomCglEoADB1QDBADEPEoNVqEAolEgEKolKErJMDYAJMD0lE0AmaEoNaAgJMCFIYAahV/IgIiDOTgABNYJMEOToiCIoJMCOTzfCN4RMBOTxsDJIRyfIwZMBKQZzfJgRyfOYZMBOUBzCJgNKOT5zDJgLoCADxKBOAIABOT6aCAARyfOYRyjOYRyjOYlKEsBzEEsBzEOUJzDOUIABOUiaDOURzCOUZzCEscKCiY"));
var im = g.imageMetrics(img);
g.reset();
g.setBgColor("#ff00ff");
var y = 176, speed = 5;
function balloon(callback) {
y-=speed;
var x = (176-im.width)/2;
g.drawImage(img,x,y);
g.clearRect(x,y+81,x+77,y+81+speed);
if (y>30) setTimeout(balloon,0,callback);
else callback();
}
fade("#ff00ff", function() {
balloon(function() {
g.setColor(-1).setFont("6x15:2").setFontAlign(0,0);
g.drawString("Welcome.",88,130);
});
});
setTimeout(function() {
var n=0;
var i = setInterval(function() {
n+=4;
g.scroll(0,-4);
if (n>150)
clearInterval(i);
},20);
},3500);
};
if (n==3) return function() {
g.reset();
g.setBgColor("#ffff00").setColor(0).clear();
g.setFont("12x20").setFontAlign(0,0);
var x = 70, y = 25, h=25;
animate([
()=>g.drawString("Your",x,y+=h),
()=>g.drawString("Bangle.js",x,y+=h),
()=>g.drawString("has one",x,y+=h),
()=>g.drawString("button",x,y+=h),
()=>{g.setFont("12x20:2").setFontAlign(0,0,1).drawString("HERE!",150,88);}
],200);
};
if (n==4) return function() {
g.reset();
g.setBgColor("#00ffff").setColor(0).clear();
g.setFontAlign(0,0).setFont("6x15:2");
g.drawString("Press",88,40).setFontAlign(0,-1);
g.setFont("12x20");
g.drawString("To wake the\nscreen up, or to\nselect", 88,60);
};
if (n==5) return function() {
g.reset();
g.setBgColor("#00ffff").setColor(0).clear();
g.setFontAlign(0,0).setFont("6x15:2");
g.drawString("Long Press",88,40).setFontAlign(0,-1);
g.setFont("12x20");
g.drawString("To go back to\nthe clock", 88,60);
};
if (n==6) return function() {
g.reset();
g.setBgColor("#ff0000").setColor(0).clear();
g.setFontAlign(0,0).setFont("12x20");
g.drawString("If Bangle.js ever\nstops, hold the\nbutton for\nten seconds.\n\nBangle.js will\nthen reboot.", 88,78);
};
if (n==7) return function() {
g.reset();
g.setBgColor("#0000ff").setColor(-1).clear();
g.setFont("12x20").setFontAlign(0,0);
var x = 88, y = -20, h=60;
animate([
()=>{g.drawString("Bangle.js has a\nfull touchscreen",x,y+=h);},
0,0,
()=>{g.drawString("Drag up and down\nto scroll and\ntap to select",x,y+=h);},
],300);
};
if (n==8) return function() {
g.reset();
g.setBgColor("#00ff00").setColor(0).clear();
g.setFont("12x20").setFontAlign(0,0);
var x = 88, y = -35, h=80;
animate([
()=>{g.drawString("Bangle.js comes\nwith a few\napps installed",x,y+=h);},
0,0,
()=>{g.drawString("To add more, visit\nbanglejs.com/apps",x,y+=h);},
],400);
};
if (n==9) return function() {
g.reset();
g.setBgColor("#ff0000").setColor(0).clear();
g.setFont("12x20").setFontAlign(0,0);
var x = 88;
g.drawString("You can also make\nyour own apps!",x,30);
g.drawString("Check out\nbanglejs.com",x,130);
var rx = 0, ry = 0;
// draw a cube
function draw() {
// rotate
rx += 0.1;
ry += 0.11;
var rcx=Math.cos(rx),
rsx=Math.sin(rx),
rcy=Math.cos(ry),
rsy=Math.sin(ry);
// Project 3D coordinates into 2D
function p(x,y,z) {
var t;
t = x*rcy + z*rsy;
z = z*rcy - x*rsy;
x=t;
t = y*rcx + z*rsx;
z = z*rcx - y*rsx;
y=t;
z += 4;
return [88 + 60*x/z, 78+ 60*y/z];
}
var a;
// draw a series of lines to make up our cube
var s = 30;
g.clearRect(88-s,78-s,88+s,78+s);
a = p(-1,-1,-1); g.moveTo(a[0],a[1]);
a = p(1,-1,-1); g.lineTo(a[0],a[1]);
a = p(1,1,-1); g.lineTo(a[0],a[1]);
a = p(-1,1,-1); g.lineTo(a[0],a[1]);
a = p(-1,-1,-1); g.lineTo(a[0],a[1]);
a = p(-1,-1,1); g.moveTo(a[0],a[1]);
a = p(1,-1,1); g.lineTo(a[0],a[1]);
a = p(1,1,1); g.lineTo(a[0],a[1]);
a = p(-1,1,1); g.lineTo(a[0],a[1]);
a = p(-1,-1,1); g.lineTo(a[0],a[1]);
a = p(-1,-1,-1); g.moveTo(a[0],a[1]);
a = p(-1,-1,1); g.lineTo(a[0],a[1]);
a = p(1,-1,-1); g.moveTo(a[0],a[1]);
a = p(1,-1,1); g.lineTo(a[0],a[1]);
a = p(1,1,-1); g.moveTo(a[0],a[1]);
a = p(1,1,1); g.lineTo(a[0],a[1]);
a = p(-1,1,-1); g.moveTo(a[0],a[1]);
a = p(-1,1,1); g.lineTo(a[0],a[1]);
}
setInterval(draw,50);
};
if (n==10) return function() {
g.reset();
g.setBgColor("#ffffff");g.clear();
g.setFontAlign(0,0);
g.setFont("12x20");
var x = 88, y = 10, h=21;
animate([
()=>g.drawString("That's it!",x,y+=h),
()=>{g.drawString("Press",x,y+=h*2);
g.drawString("the button",x,y+=h);
g.drawString("to start",x,y+=h);
g.drawString("Bangle.js",x,y+=h);}
],400);
}
}
var sceneNumber = 0;
function move(dir) {
if (dir>0 && sceneNumber+1 == SCENE_COUNT) return; // at the end
sceneNumber = (sceneNumber+dir)%SCENE_COUNT;
if (sceneNumber<0) sceneNumber=0;
clearInterval();
getScene(sceneNumber)();
if (sceneNumber>1) {
var l = SCENE_COUNT;
for (var i=0;i<l-2;i++) {
var x = 88+(i-(l-2)/2)*12;
if (i<sceneNumber-1) {
g.setColor(-1).fillCircle(x,166,4);
} else {
g.setColor(0).fillCircle(x,166,4);
g.setColor(-1).drawCircle(x,166,4);
}
}
}
if (sceneNumber < SCENE_COUNT-1)
setTimeout(function() {
move(1);
}, (sceneNumber==0) ? 20000 : 5000);
}
Bangle.on('swipe', dir => move(dir));
setWatch(()=>{
if (sceneNumber == SCENE_COUNT-1)
load();
else
move(1);
}, BTN1, {repeat:true});
Bangle.setLCDTimeout(0);
Bangle.setLocked(0);
Bangle.setLCDPower(1);
move(0);

View File

@ -28,13 +28,15 @@ function getApp() {
var line3 = document.getElementById("line3").value;
var line4 = document.getElementById("line4").value;
var style = document.getElementById("style").value;
// build the app's text using a templated String
if (style=="Birthday") return `(function() {
var ib = require("heatshrink").decompress(atob("jk0ggGDhOZAAWQCYwMEBxAMFAAIaHyc/+c5DgwMC/84Dg4aCBgwcDBoOf+Y4GBoQEBn4zCI44DBDQ4NEyf4BpgoIBoefxINMBhApEBrQAKBrrrGWpANZHBT7FBpYqIFAYcJBggNOFQwoFDgwMHBwoMIBwYMKBrkykANLmcwBu0zBrMDBv4AFN5gA/ADY"));
var ir = require("heatshrink").decompress(atob("jk0ggGDhvdAAXQCYwMEBxAMFAAIaH6c/+c9DgwMC/8zDg4aC/4YCHIwNB7/zHAwNCAgM/DQwqDAYIaHBonT/oNMFBAND74NNBhApEBrQAKBrrrGWpANZHBT7FBpYqIFAYcJBgkA5oMF7gNFFQwoFDgwMHHIoMIAAPM5gMKBrk0oANLmcwBu0zBrMDBv4AFN5gA/ADYA="));
var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/+c3DgwMC/8yDg4aC/4YCHIwNBv/zHAwNCAgM/DQwqDAYIaHBolz+4NMFBANDv8nBpgMIFIgNaABQNddYy1IBrI4KfYoNLFRAoDDhIMEgHnBgt+BooqGFAoqGBg4OFBhAODBhQNcmUgBpczmAN2mYNZgYN/AApvMAH4Ab"));
var igift = require("heatshrink").decompress(atob("q1QxH+ADOi0QbZ5nMHDQAbKgIACKa4ACKnJWVKghW0KgxWTKgxWyKhBWRKhBWwKhRWPKhRWuKhhWNKhhWtKpxWKKhys8KxBU8Ky5U+KypU/KyhU/KyhU/KynGKn5WTKn5WUKmHCADpJJE7uYABZUfKuuYKv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/AAv+Kv5VT/wADyIAaKpIlbABZSEKv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/ADNtKv6rdKzZVwKhAABy5V/Khw"));
var W=240,H=240;
var W=g.getWidth(),H=g.getHeight();
var titleFont = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
var blns = [];
function updateFlake(f) {
f.im = [ir,ig,ib][Math.round(Math.random()*100)%3];
@ -60,7 +62,7 @@ var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/
});
var x = W/2, y = H/2;
g.drawImage(igift,x-43,y-80);
g.setFont("6x8",2).setFontAlign(0,0);
g.setFont(titleFont).setFontAlign(0,0);
g.drawString(${JSON.stringify(line1)},x,y+=20);
g.drawString(${JSON.stringify(line2)},x,y+=20);
g.setFont("6x8");
@ -68,7 +70,7 @@ var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/
g.drawString(${JSON.stringify(line4)},x,y+=10);
g.flip();
}
g.clear();
g.clear(1).setBgColor(0).setColor(-1).clearRect(0,0,W,H);
setInterval(draw,50);
})()`;
// if (style=="Christmas")
@ -76,6 +78,7 @@ var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/
var isnow = require("heatshrink").decompress(atob("jEagQWTgfAAocf+gFDh4FDiARBggVB3AFBl3Agf8jfkn/AgX/v/9/+Agfv/2//YrBgfwh4wCgfghYFJCIYdFFIw1EIIpNFL44FFOIoAP"));
var itree = require("heatshrink").decompress(atob("mtWxH+ADHHDTI0aGuXH5vNGmhqvTYIzBGtoxF6fTG4g4oGgQyBAAZssGoI0Ga1g1FGdo01ZgIAEGmHHNoLSuAAN/rdb0YFBGlgCBGYIABA4YArGYY1CGn4znAAM6GeVd5PQ5Iyurc/vQ0oGZFAn+d4XC3d5GddiGYIEBy+7zoEBGlFhoEcsQ9GT08+oFk1mkGdaVBMgNArnJ6/KzswGs/J6GlrlbqtbvPC5PCy8wGohniMIPJvIpCqmX3e7vI0BqhqlMIY0DqhtBqoEBa0xgBMIIoEqoABGQwzfsIhBv4qHABM50vQGjg1CGaN66DoBGt1ioGd5LoBGjo1PGYNhvLoCa7wnBqgvGA4YzCAgN5GUAsCqoDBmAHCAYU/wPQ0oSDGcBiDqkwAYcxoFd5PX6GdGjrIIqtUAAc3jk5vPC4fCy5pef5I2BTQMcnAHBy+7y95T0oADnFk1ekBpI2aGRUin7NGAA9hsIzVsIgHTAKZBZoPJ5LNDGhBpXGolcwOsrtcA4TNB3bNDGb/+sVin9AoGe6HX5InEvN/TkP+5XQwM/sRsBzqWB4QuKGjvC6HQ4QdDvKWBZYMwmAuHmFUCYNbqibX3fD5O7qolEZQQ0FBwgKDqgJBGiphEDwNUEgJbBFIQqCAgYOCB4IzCnE6GyhYFGoQnDABYzGAAQ1UAAo2NBoQSBnOB0t/Gjo2EABIPCoGe6HX4QzTGRIAEqtVF4QEBBQc4oE4y/J5PCvIxeABk/oADBvO73eXTyAyZMwM/Awd5vIOFGslAr2Av4PLNcU/jmA6HX5I1KasFcn8dTIOd5PJ4SZGGiNhAAIyNn0ckU+ZYe7AAJpJEYJnNGZk+n9kw9cBAcwGoN5aZg1JJJQABm8/oEjoDKC5ALCrUwqh/NrvQ6HDGp04n9doEdoE/sQJBZQZhCqgABGZk6zw0K/1dnVAoNAFwOlCYL1FubJBy4GCGh1AnOX4XC3YzHFYOeCgdV5PQ5OdD4rKBqqYNGYlbv+X3edGY3CGgKMDAAO7JAJgDAClcr2BEYgADaIZ0DL4uXGbDuB6HX5I1GsP+sNhOgWXIhBmWd4Od5PK4TwFGIJoBAYI2BAD0/jlcQoO7AAJaEGQQADGr0/sjNEvOdAoZmDGgw2ZsVAkeAZpQACGZI2VsU/kVGn1bZoPJZogpGGhA4GfRYwBoGC1mlBQbNFFoo0JNxAGCEod/wM6oFAn9iv/J6/Kzo1Ey9/MZQAKCg4GCFgTDEvPCSwI0BC5I0RN4ocEYYPQ5OdHgeXSwTFKGaJyKFYPC3f+MIdbpzFLAD4zB/1OqtbqtOGgYArGAIADGl9UAAI0wGQN5GoQ0vvIABGoI0uGYQABqo0zNOg0uaQY0/GllOGn40//w="));
var W=g.getWidth(),H=g.getHeight();
var titleFont = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
var flakes = [];
for (var i=0;i<10;i++) {
var f = {
@ -97,7 +100,7 @@ var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/
});
var x = W/2, y = H/2;
g.drawImage(itree,x-27,y-80);
g.setFont("6x8",2).setFontAlign(0,0);
g.setFont(titleFont).setFontAlign(0,0);
g.drawString(${JSON.stringify(line1)},x,y+=20);
g.drawString(${JSON.stringify(line2)},x,y+=20);
g.setFont("6x8");
@ -105,7 +108,7 @@ var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/
g.drawString(${JSON.stringify(line4)},x,y+=10);
g.flip();
}
g.clear();
g.clear(1).setBgColor(0).setColor(-1).clearRect(0,0,W,H);
setInterval(draw,50);
})();
`;

View File

@ -7,3 +7,4 @@
0.07: Add date on touch and some improvements (see settings and readme)
0.08: Add new draw styles, tidy up draw functionality
0.09: Tweak for faster rendering
0.10: Enhance for use with Bangle2, insert new draw mode 'thickfill'

View File

@ -7,14 +7,20 @@ Settings can be accessed through the app/widget settings menu of the Bangle.js
### Color:
* rnd - shows numerals in different color combinations every time the watches wakes
* r/g - red/green
* y/w - yellow/white
* o/c - orange/cyan
* b/y - blue/yellow'ish
* r/g - red/green (Bangle1/Bangle2)
* y/w - yellow/white (Bangle1 only)
* o/c - orange/cyan (Bangle1 only)
* b/y - blue/yellow'ish (Bangle1 only)
* r/g - red/green (Bangle2 only)
* g/b - green/blue (Bangle2 only)
* r/c - red/cyan (Bangle2 only)
* m/g - magenta/green (Bangle2 only)
### Draw mode
* fill - fill numerals
* frame - only shows outline of numerals
* framefill - frame with lighter color fill
* thickfill - thick frame in theme foreground color
### Menu button
* choose button to start launcher menu with

View File

@ -6,7 +6,7 @@
* + see README.md for details
*/
var numerals = {
var numerals = {
0:[[9,1,82,1,90,9,90,92,82,100,9,100,1,92,1,9],[30,25,61,25,69,33,69,67,61,75,30,75,22,67,22,33]],
1:[[50,1,82,1,90,9,90,92,82,100,73,100,65,92,65,27,50,27,42,19,42,9]],
2:[[9,1,82,1,90,9,90,53,82,61,21,61,21,74,82,74,90,82,90,92,82,100,9,100,1,92,1,48,9,40,70,40,70,27,9,27,1,19,1,9]],
@ -19,8 +19,8 @@ var numerals = {
9:[[9,1,82,1,90,9,90,92,82,100,9,100,1,92,1,82,9,74,69,74,69,61,9,61,1,53,1,9],[22,27,69,27,69,41,22,41]],
};
var _12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false;
var _hCol = ["#ff5555","#ffff00","#FF9901","#2F00FF"];
var _mCol = ["#55ff55","#ffffff","#00EFEF","#FFBF00"];
var _hCol = [];
var _mCol = [];
var _rCol = 0;
var scale = g.getWidth()/240;
var interval = 0;
@ -42,15 +42,23 @@ var drawFuncs = {
},
thickframe : function(poly,isHole){
g.drawPoly(poly,true);
g.drawPoly(translate(1,0,poly),true);
g.drawPoly(translate(1,1,poly),true);
g.drawPoly(translate(0,1,poly),true);
g.drawPoly(translate(1,0,poly,1),true);
g.drawPoly(translate(1,1,poly,1),true);
g.drawPoly(translate(0,1,poly,1),true);
},
thickfill : function(poly,isHole){
if (isHole) g.setColor(g.theme.bg);
g.fillPoly(poly,true);
g.setColor(g.theme.fg);
g.drawPoly(translate(1,0,poly,1),true);
g.drawPoly(translate(1,1,poly,1),true);
g.drawPoly(translate(0,1,poly,1),true);
}
};
function translate(tx, ty, p){
function translate(tx, ty, p, ascale){
//return p.map((x, i)=> x+((i&1)?ty:tx));
return g.transformVertices(p, {x:tx,y:ty,scale:scale});
return g.transformVertices(p, {x:tx,y:ty,scale:ascale==undefined?scale:ascale});
}
@ -99,6 +107,18 @@ function setUpdateInt(set){
if (set) interval=setInterval(draw, REFRESH_RATE);
}
function setUp(){
if (process.env.HWVERSION==1){
_hCol = ["#ff5555","#ffff00","#FF9901","#2F00FF"];
_mCol = ["#55ff55","#ffffff","#00EFEF","#FFBF00"];
} else {
_hCol = ["#ff0000","#00ff00","#ff0000","#ff00ff"];
_mCol = ["#00ff00","#0000ff","#00ffff","#00ff00"];
}
if (settings.color==0) _rCol = Math.floor(Math.random()*_hCol.length);
}
setUp();
g.clear(1);
// Show launcher when button pressed
Bangle.setUI("clock");
@ -111,11 +131,12 @@ if (settings.showDate) {
}
Bangle.on('lcdPower', function(on){
if (on){
if (settings.color==0) _rCol = Math.floor(Math.random()*_hCol.length);
setUp();
draw();
setUpdateInt(1);
} else setUpdateInt(0);
});
Bangle.on('lock', () => setUp());
Bangle.loadWidgets();
Bangle.drawWidgets();
Bangle.drawWidgets();

View File

@ -12,8 +12,8 @@
}
let numeralsSettings = storage.readJSON('numerals.json',1);
if (!numeralsSettings) resetSettings();
let dm = ["fill","frame","framefill","thickframe"];
let col = ["rnd","r/g","y/w","o/c","b/y"];
let dm = ["fill","frame","framefill","thickframe","thickfill"];
let col = process.env.HWVERSION==1?["rnd","r/g","y/w","o/c","b/y"]:["rnd","r/g","g/b","r/c","m/g"];
let btn = [[24,"BTN1"],[22,"BTN2"],[23,"BTN3"],[11,"BTN4"],[16,"BTN5"]];
var menu={
"" : { "title":"Numerals"},

View File

@ -8,3 +8,5 @@
0.08: Update for drag event refactor
0.09: Use current theme cols when drawing GPS info
0.10: Improve scale factor calculation to fix scaling issues (#984)
0.11: Add slight offset to OSM data to align it properly (fix #984)
Fix alignment of satellite info text

View File

@ -25,11 +25,11 @@ function drawMarker() {
var fix;
Bangle.on('GPS',function(f) {
fix=f;
g.reset().clearRect(0,y1,240,y1+8).setFont("6x8").setFontAlign(0,0);
g.reset().clearRect(0,y1,g.getWidth()-1,y1+8).setFont("6x8").setFontAlign(0,0);
var txt = fix.satellites+" satellites";
if (!fix.fix)
txt += " - NO FIX";
g.drawString(txt,120,y1 + 4);
g.drawString(txt,g.getWidth()/2,y1 + 4);
drawMarker();
});
Bangle.setGPSPower(1, "app");

View File

@ -132,8 +132,10 @@ TODO:
var zoom = map.getZoom();
var centerlatlon = map.getBounds().getCenter();
var center = map.project(centerlatlon, zoom).divideBy(OSMTILESIZE);
var ox = Math.round((center.x - Math.floor(center.x)) * OSMTILESIZE);
var oy = Math.round((center.y - Math.floor(center.y)) * OSMTILESIZE);
// Reason for 16px adjustment below not 100% known, but it seems to
// align everything perfectly: https://github.com/espruino/BangleApps/issues/984
var ox = Math.round((center.x - Math.floor(center.x)) * OSMTILESIZE) + 16;
var oy = Math.round((center.y - Math.floor(center.y)) * OSMTILESIZE) + 16;
center = center.floor(); // make sure we're in the middle of a tile
// JS version of Bangle.js's projection
function bproject(lat, lon) {
@ -155,8 +157,15 @@ TODO:
var bd = bproject(pd.lat, pd.lng)
var scale = bc.distanceTo(bd);
var tileGetters = [];
// test
/*var p = bproject(centerlatlon.lat, centerlatlon.lng);
var q = bproject(mylat, mylon);
var testPt = {
x : (q.x-p.x)/scale + (MAPSIZE/2),
y : (MAPSIZE/2) - (q.y-p.y)/scale
};*/
var tileGetters = [];
// Render everything to a canvas...
var canvas = document.getElementById("maptiles");
canvas.style.display="";
@ -173,6 +182,11 @@ TODO:
tileGetters.push(new Promise(function(resolve,reject) {
img.onload = function(){
ctx.drawImage(img,i*OSMTILESIZE - ox, j*OSMTILESIZE - oy);
/*if (testPt) {
ctx.fillStyle="green";
ctx.fillRect(testPt.x-1, testPt.y-5, 3,10);
ctx.fillRect(testPt.x-5, testPt.y-1, 10,3);
}*/
resolve();
};
}));

Some files were not shown because too many files have changed in this diff Show More