diff --git a/apps.json b/apps.json
index 70e4f14f0..0826bbf81 100644
--- a/apps.json
+++ b/apps.json
@@ -1,9 +1,9 @@
[
{
"id": "fwupdate",
- "name": "Firmware Update (BETA)",
- "version": "0.01",
- "description": "Uploads new Espruino firmwares to Bangle.js 2",
+ "name": "Firmware Update",
+ "version": "0.02",
+ "description": "[BETA] Uploads new Espruino firmwares to Bangle.js 2. For now, please use the instructions under https://www.espruino.com/Bangle.js2#firmware-updates",
"icon": "app.png",
"type": "RAM",
"tags": "tools,system",
@@ -11,12 +11,12 @@
"custom": "custom.html",
"customConnect": true,
"storage": [],
- "sortorder": -20
+ "sortorder": 20
},
{
"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,55 @@
],
"sortorder": -10
},
+ {
+ "id": "hebrew_calendar",
+ "name": "Hebrew Calendar",
+ "shortName": "HebCal",
+ "version": "0.04",
+ "description": "lists the date according to the hebrew calendar",
+ "icon": "app.png",
+ "allow_emulator": false,
+ "tags": "tool,locale",
+ "supports": [
+ "BANGLEJS",
+ "BANGLEJS2"
+ ],
+ "readme": "README.md",
+ "storage": [
+ {
+ "name": "hebrew_calendar.app.js",
+ "url": "app.js"
+ },
+ {
+ "name": "hebrewDate",
+ "url": "hebrewDate.js"
+ },
+ {
+ "name": "hebrew_calendar.img",
+ "url": "app-icon.js",
+ "evaluate": true
+ }
+ ]
+ },
+ { "id": "golfscore",
+ "name": "Golf Score",
+ "shortName":"golfscore",
+ "version":"0.02",
+ "description": "keeps track of strokes during a golf game",
+ "icon": "app.png",
+ "tags": "outdoors",
+ "allow_emulator": true,
+ "supports" : ["BANGLEJS","BANGLEJS2"],
+ "readme": "README.md",
+ "storage": [
+ {"name":"golfscore.app.js","url":"app.js"},
+ {"name":"golfscore.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 +99,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 +115,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 +149,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 +157,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 +217,7 @@
{
"id": "locale",
"name": "Languages",
- "version": "0.10",
+ "version": "0.13",
"description": "Translations for different countries",
"icon": "locale.png",
"type": "locale",
@@ -236,16 +283,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}
],
@@ -254,8 +302,8 @@
{
"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.",
+ "version": "0.25",
+ "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 +511,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
}
]
@@ -510,7 +558,7 @@
"icon": "clock-impword.png",
"type": "clock",
"tags": "clock",
- "supports": ["BANGLEJS"],
+ "supports": ["BANGLEJS","BANGLEJS2"],
"screenshots": [{"url":"bangle1-impercise-word-clock-screenshot.png"}],
"allow_emulator": true,
"storage": [
@@ -699,10 +747,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 +844,7 @@
{
"id": "weather",
"name": "Weather",
- "version": "0.11",
+ "version": "0.13",
"description": "Show Gadgetbridge weather report",
"icon": "icon.png",
"screenshots": [{"url":"screenshot.png"}],
@@ -1098,7 +1147,7 @@
{
"id": "qrcode",
"name": "Custom QR Code",
- "version": "0.02",
+ "version": "0.04",
"description": "Use this to upload a customised QR code to Bangle.js",
"icon": "app.png",
"tags": "qrcode",
@@ -1726,7 +1775,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 +1957,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 +1990,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 +2025,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 +2119,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 +2164,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 +2426,7 @@
{
"id": "calendar",
"name": "Calendar",
- "version": "0.02",
+ "version": "0.03",
"description": "Simple calendar",
"icon": "calendar.png",
"screenshots": [{"url":"screenshot_calendar.png"}],
@@ -2359,8 +2436,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 +2677,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 +3815,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"}],
@@ -3811,7 +3890,7 @@
"id": "qmsched",
"name": "Quiet Mode Schedule and Widget",
"shortName": "Quiet Mode",
- "version": "0.04",
+ "version": "0.05",
"description": "Automatically turn Quiet Mode on or off at set times, and change LCD options while Quiet Mode is active.",
"icon": "app.png",
"screenshots": [{"url":"screenshot_b1_main.png"},{"url":"screenshot_b1_edit.png"},{"url":"screenshot_b1_lcd.png"},
@@ -4036,7 +4115,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",
@@ -4058,14 +4137,17 @@
{
"id": "vectorclock",
"name": "Vector Clock",
- "version": "0.02",
+ "version": "0.03",
"description": "A digital clock that uses the built-in vector font.",
"icon": "app.png",
"type": "clock",
"tags": "clock",
- "supports": ["BANGLEJS"],
+ "supports": ["BANGLEJS", "BANGLEJS2"],
"allow_emulator": true,
- "screenshots": [{"url":"bangle1-vector-clock-screenshot.png"}],
+ "screenshots": [
+ {"url":"bangle2-vector-clock-screenshot.png"},
+ {"url":"bangle1-vector-clock-screenshot.png"}
+ ],
"storage": [
{"name":"vectorclock.app.js","url":"app.js"},
{"name":"vectorclock.img","url":"app-icon.js","evaluate":true}
@@ -4296,7 +4378,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 +4425,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 +4447,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"}],
@@ -4383,9 +4465,9 @@
"name": "A Battery Widget (with percentage)",
"shortName":"A Battery Widget",
"icon": "widget.png",
- "version":"1.01",
+ "version":"1.02",
"type": "widget",
- "supports": ["BANGLEJS2"],
+ "supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"description": "Simple and slim battery widget with charge status and percentage",
"tags": "widget,battery",
@@ -4478,7 +4560,7 @@
{"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true}
],
"data": [
- {"name":"app.json"}
+ {"name":"calendarItems.csv"}
]
},
{ "id": "timecal",
@@ -4531,7 +4613,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 +4631,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 +4644,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 +4682,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 +4694,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 +4717,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",
@@ -4660,7 +4734,7 @@
{ "id": "pooqroman",
"name": "pooq Roman watch face",
"shortName":"pooq Roman",
- "version":"0.0.0",
+ "version":"0.02",
"description": "A classic watch face with a certain dynamicity. Most amusing in 24h mode. Slide up to show more hands, down for less(!). By design does not support standard widgets, sorry!",
"icon": "app.png",
"type": "clock",
@@ -4675,5 +4749,187 @@
"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.04",
+ "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.10",
+ "description": "Directly launch apps from the clock screen with custom patterns.",
+ "icon": "app.png",
+ "screenshots": [{"url":"main_menu_add.png"}, {"url":"add_pattern.png"}, {"url":"select_app.png"}, {"url":"main_menu_manage.png"}, {"url":"manage_patterns.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": "rebble",
+ "name": "Rebble Clock",
+ "shortName": "Rebble",
+ "version": "0.01",
+ "description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion",
+ "readme": "README.md",
+ "icon": "rebble.png",
+ "dependencies": {"mylocation":"app"},
+ "screenshots": [{"url":"screenshot_rebble.png"}],
+ "type": "clock",
+ "tags": "clock",
+ "supports": ["BANGLEJS2"],
+ "storage": [
+ {"name":"rebble.app.js","url":"rebble.app.js"},
+ {"name":"rebble.settings.js","url":"rebble.settings.js"},
+ {"name":"rebble.img","url":"rebble.icon.js","evaluate":true}
+ ]
+ },
+ { "id": "snaky",
+ "name": "Snaky",
+ "shortName":"Snaky",
+ "version":"0.01",
+ "description": "The classic snake game. Eat apples and don't bite your tail. Control the snake with the touch screen.",
+ "tags": "game,fun",
+ "icon": "snaky.png",
+ "supports" : ["BANGLEJS2"],
+ "readme": "README.md",
+ "storage": [
+ {"name":"snaky.app.js","url":"snaky.js"},
+ {"name":"snaky.img","url":"snaky-icon.js","evaluate":true}
+ ]
+ },
+ {
+ "id": "clicompleteclk",
+ "name": "CLI complete clock",
+ "shortName":"CLI cmplt clock",
+ "version":"0.03",
+ "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.img","url":"app-icon.js","evaluate":true},
+ {"name":"clicompleteclk.settings.js","url":"settings.js"}
+ ],
+ "data": [{"name":"clicompleteclk.json"}]
+ },
+ {
+ "id":"awairmonitor",
+ "name":"Awair Monitor",
+ "icon": "app.png",
+ "allow_emulator": true,
+ "version":"0.01",
+ "description": "Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awair device.",
+ "tags": "tool,health",
+ "readme":"README.md",
+ "supports":["BANGLEJS2"],
+ "storage": [
+ {"name":"awairmonitor.app.js","url":"app.js"},
+ {"name":"awairmonitor.img","url":"app-icon.js","evaluate":true}
+ ]
}
]
diff --git a/apps/93dub/ChangeLog b/apps/93dub/ChangeLog
index 5fbfe4fa3..c1b2588bb 100644
--- a/apps/93dub/ChangeLog
+++ b/apps/93dub/ChangeLog
@@ -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
diff --git a/apps/93dub/README.md b/apps/93dub/README.md
index fd24d54d8..4d1ade582 100644
--- a/apps/93dub/README.md
+++ b/apps/93dub/README.md
@@ -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)
diff --git a/apps/93dub/app.js b/apps/93dub/app.js
index 92544304c..1b0f69a94 100644
--- a/apps/93dub/app.js
+++ b/apps/93dub/app.js
@@ -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);
diff --git a/apps/a_speech_timer/ChangeLog b/apps/a_speech_timer/ChangeLog
index 4a8e3fbe7..b3aa9e0dd 100644
--- a/apps/a_speech_timer/ChangeLog
+++ b/apps/a_speech_timer/ChangeLog
@@ -1 +1,2 @@
1.00: Release (2021/12/01)
+1.01: Grey font when timer is frozen (2021/12/04)
diff --git a/apps/a_speech_timer/app.js b/apps/a_speech_timer/app.js
index dae2545b2..440cd92c6 100644
--- a/apps/a_speech_timer/app.js
+++ b/apps/a_speech_timer/app.js
@@ -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);
diff --git a/apps/awairmonitor/ChangeLog b/apps/awairmonitor/ChangeLog
new file mode 100644
index 000000000..0cc9a42b0
--- /dev/null
+++ b/apps/awairmonitor/ChangeLog
@@ -0,0 +1 @@
+0.01: Beta version for Bangle 2 paired with Chrome (2021/12/11)
diff --git a/apps/awairmonitor/README.md b/apps/awairmonitor/README.md
new file mode 100644
index 000000000..8d6e25633
--- /dev/null
+++ b/apps/awairmonitor/README.md
@@ -0,0 +1,20 @@
+# Awair Monitor
+
+Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awair device.
+
+* What you need:
+ * A BangleJS 2
+ * An Awair device [with local API enabled](https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature)
+ * The web app [awair_to_bangle.html](awair_to_bangle.html) that will retrive the data from your Awair device and sent it to your BangleJS 2 through Chrome's Bluetooth LE connection
+* How to get started
+ * Open awair_to_bangle.html with a text/code editor and input the IP address of your Awair on top (const awair_ip_1 = "192.168.xx.xx")
+ * Launch the Awair Monitor app on your BangleJS
+ * Open awair_to_bangle.html on Chrome and click "Connect BangleJS" - it connects to your watch the same way as the Bangle app store
+ * Once connected to the watch with the app running, the watch app is updated once per second
+
+
+
+## Creator
+[@alainsaas](https://github.com/alainsaas)
+
+Contributions are welcome, send me your Pull Requests!
diff --git a/apps/awairmonitor/app-icon.js b/apps/awairmonitor/app-icon.js
new file mode 100644
index 000000000..9d4dcf4a3
--- /dev/null
+++ b/apps/awairmonitor/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwgP/AD38g4FD8EAAoeAgE/AoUD/EfAgP+AYMPDgQPBw4FB/F///DAoPwAQPjAQPBAQPxDgJVCAoP4gYaCCwIcBAoM/8P8h0HjEP8f4h0Gp0H4/44lj5+H4/54lzj/jx/5/lyDgIFDh/xAoQRBAoXsuY8Bx4jCAoeEkYFB447CAoRxBOAPxM4RmC8IFD4ZZD/8H/DHDh/+AoaSBUAIABCoYATVwS2Ct4FE84REXQQLCk4RJAo0XGxY="))
diff --git a/apps/awairmonitor/app.js b/apps/awairmonitor/app.js
new file mode 100644
index 000000000..a5a1d1a72
--- /dev/null
+++ b/apps/awairmonitor/app.js
@@ -0,0 +1,98 @@
+Graphics.prototype.setFontMichroma36 = function() {
+g.setFontCustom(atob("AAAAAAAAAAAAAAAAeAAAAAeAAAAAeAAAAAeAAAAAAAAAAAAAAAAAAAAAAAGAAAAA+AAAAD+AAAAP+AAAA/8AAAD/wAAAf/AAAB/4AAAH/gAAAf+AAAB/4AAAH/gAAAf+AAAAfwAAAAfAAAAAcAAAAAAAAAAAAAAAAAAAAAAAA///AAD///wAH///4AP///8APwAD+APAAAeAeAAAeAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAeAPAAAeAPwAD+AP///8AH///4AD///wAA///AAAAAAAAAAAAAAAAAAAAAEAAAAAOAAAAAfAAAAA+AAAAB8AAAAD8AAAAH4AAAAPwAAAAPgAAAAfAAAAAf///+Af///+Af///+Af///+AAAAAAAAAAAAAAAAAAAAAAAAAA/Af+AD/A/+AH/B/+AP/D/+APwD4eAPADweAfADweAeADweAeADweAeADweAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAPgeAeAPAeAeAPAeAeAPAeAeAPAeAfAPAeAPw/AeAP/+AeAH/+AeAD/8AeAB/wAOAAAAAAAAAAAAAAAAAAAAAAAAAB8APgAD8AP4AH8AP8AP8AP8APgAB+AfAAAeAeAAAeAeAAAPAeAAAPAeAAAPAeAAAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAeAfAeAeAPx/h+AP///+AH///8AD///4AB/h/gAAAAAAAAAAAAAAAAAAAAAAeAAAAA/AAAAA/AAAAB/AAAAD/AAAAH/AAAAPvAAAAPPAAAAfPAAAA+PAAAB8PAAAD4PAAADwPAAAHwPAAAPgPAAAfAPAAA+APAAA8APAAB8APAAD4APAAHwAPAAPgAPAAPAAPAAfAAPAAf///+Af///+Af///+Af///+AAAAPAAAAAPAAAAAPAAAAAPAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAf/8PgAf/8P4Af/8P8Af/8P8AeB4A+AeB4AeAeDwAeAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAfAeDwAeAeD4A+AeD+D+AeB//8AeB//4AeA//4AAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAA///AAD///wAH///4AH///8AP4fB+APAeAeAfA8AeAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAfA8APAPA+AeAPgeAeAP8fh+AH8f/8AD8P/8AA8H/4AAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAeAAAAAeAAAAAeAAAAAeAAAAAeAAACAeAAAGAeAAAOAeAAAeAeAAA+AeAAD+AeAAH8AeAAP4AeAAfwAeAA/gAeAB/AAeAD+AAeAP4AAeAfwAAeA/gAAeB/AAAeD+AAAeH8AAAefwAAAe/gAAAf/AAAAf+AAAAf8AAAAf4AAAAfgAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAMAAB+B/wAD/j/4AH/3/8AP///+AP//A+AfB+AeAeA+AeAeA+APAeA+APAeA+APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA+APAeA+APAeA+APAeA+AOAeA+AeAPh/A+AP///+AP/3/8AH/3/8AB/D/wAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAD/4HAAH/8HwAP/+H4AP5/H8AfAfA8AeAPAeAeAPAeAeAPAeAeAHgfAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHAPAeAPAOAeAPAeAPAPAeAPwfB+AP///8AH///4AD///wAA///AAAAAAAAAAAAAAAAAAAAAAAAAAAB8DwAAB8HwAAB8HwAAB8DwAAAAAAAAAAAAA"), 46, atob("CBIkESMjJCMjIyMjCA=="), 36+(1<<8)+(1<<16));
+};
+
+var drawTimeout;
+
+function queueNextDraw() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ }, 1000 - (Date.now() % 1000));
+}
+
+var locale = require("locale");
+
+var bt_current_co2 = 0;
+var bt_current_voc = 0;
+var bt_current_pm25 = 0;
+var bt_current_humi = 0;
+var bt_current_temp = 0;
+var bt_last_update = 0;
+
+var last_update = 0;
+var bt_co2_history = new Array(10).fill(0);
+var bt_voc_history = new Array(10).fill(0);
+var bt_pm25_history = new Array(10).fill(0);
+var bt_humi_history = new Array(10).fill(0);
+var bt_temp_history = new Array(10).fill(0);
+
+var internal_last_update = -1;
+
+function draw() {
+ g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
+
+ var date = new Date();
+ g.setFontAlign(0,0);
+ g.setFont("Michroma36").drawString(locale.time(date,1), g.getWidth()/2, 56);
+
+ g.setFont("6x8");
+ g.drawString(locale.date(new Date(),1), g.getWidth()/2, 80);
+
+ g.setFont("6x8");
+ g.drawString("CO2", 20, 100);
+ g.drawString("VOC", 55, 100);
+ g.drawString("PM25", 90, 100);
+ g.drawString("Humi", 125, 100);
+ g.drawString("Temp", 160, 100);
+
+ g.setFont("HaxorNarrow7x17");
+ g.drawString(""+bt_current_co2, 18, 110);
+ g.drawString(""+bt_current_voc, 53, 110);
+ g.drawString(""+bt_current_pm25, 88, 110);
+ g.drawString(""+bt_current_humi, 123, 110);
+ g.drawString(""+bt_current_temp, 158, 110);
+
+ if (last_update != bt_last_update) {
+ last_update = bt_last_update;
+ internal_last_update = last_update;
+ if (last_update % 10 == 0) {
+ bt_co2_history.shift(); bt_co2_history.push(bt_current_co2);
+ bt_voc_history.shift(); bt_voc_history.push(bt_current_voc);
+ bt_pm25_history.shift(); bt_pm25_history.push(bt_current_pm25);
+ bt_humi_history.shift(); bt_humi_history.push(bt_current_humi);
+ bt_temp_history.shift(); bt_temp_history.push(bt_current_temp);
+ }
+ }
+
+ if (internal_last_update == -1) {
+ g.drawString("Waiting for connection", 88, 164);
+ } else if (internal_last_update > last_update + 5) {
+ g.drawString("Trying to reconnect since " + (internal_last_update - last_update), 88, 164);
+ }
+
+
+ for (i = 0; i < 10; i++) {
+ // max height = 32
+ g.drawLine(10+i*2, 150-(Math.min(Math.max(bt_co2_history[i],400), 1200)-400)/25, 10+i*2, 150);
+ g.drawLine(45+i*2, 150-(Math.min(Math.max(bt_voc_history[i],0), 1440)-0)/45, 45+i*2, 150);
+ g.drawLine(80+i*2, 150-(Math.min(Math.max(bt_pm25_history[i],0), 32)-0)/1, 80+i*2, 150);
+ g.drawLine(115+i*2, 150-(Math.min(Math.max(bt_humi_history[i],20), 60)-20)/1.25, 115+i*2, 150);
+ g.drawLine(150+i*2, 150-(Math.min(Math.max(bt_temp_history[i],19), 27)-19)*4, 150+i*2, 150);
+
+ // target humidity level
+ g.setColor("#00F").drawLine(115, 150-(40-20)/1.25, 115+18, 150-(40-20)/1.25);
+ g.reset();
+ }
+
+ if (internal_last_update != -1) { internal_last_update++; }
+ queueNextDraw();
+}
+
+// init
+require("FontHaxorNarrow7x17").add(Graphics);
+g.clear();
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+draw();
diff --git a/apps/awairmonitor/app.png b/apps/awairmonitor/app.png
new file mode 100644
index 000000000..26a5d0cff
Binary files /dev/null and b/apps/awairmonitor/app.png differ
diff --git a/apps/awairmonitor/awair_to_bangle.html b/apps/awairmonitor/awair_to_bangle.html
new file mode 100644
index 000000000..2926cca9e
--- /dev/null
+++ b/apps/awairmonitor/awair_to_bangle.html
@@ -0,0 +1,195 @@
+
+
+
+
+
+
+
+
+
+
+
+How to use
+
+Step 1: Enable the Local API on your Awair: https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
+
+Step 2: Modify this HTML file to input the IP address of your Awair on top (const awair_ip_1 = "192.168.xx.xx")
+
+Step 3: Launch the Awair Monitor app on your BangleJS
+
+Step 4: Click "Connect BangleJS"
+
+Step 5: Optionally, open the web inspector's console (Right click > Inspector > Console) to read the bluetooth logs
+
+
+
+ Connect BangleJS
+ Disconnect BangleJS
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/awairmonitor/screenshot.png b/apps/awairmonitor/screenshot.png
new file mode 100644
index 000000000..51ca0aa44
Binary files /dev/null and b/apps/awairmonitor/screenshot.png differ
diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog
index ffc2be495..b941a9937 100644
--- a/apps/boot/ChangeLog
+++ b/apps/boot/ChangeLog
@@ -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
diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js
index daf311fe6..3001bb5c1 100644
--- a/apps/boot/bootupdate.js
+++ b/apps/boot/bootupdate.js
@@ -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.
diff --git a/apps/calendar/ChangeLog b/apps/calendar/ChangeLog
index 8bafff34a..de887bfa7 100644
--- a/apps/calendar/ChangeLog
+++ b/apps/calendar/ChangeLog
@@ -1,2 +1,3 @@
0.01: Basic calendar
0.02: Make Bangle 2 compatible
+0.03: Add setting to start week on Sunday
diff --git a/apps/calendar/README.md b/apps/calendar/README.md
index 19a60afc0..e22d06573 100644
--- a/apps/calendar/README.md
+++ b/apps/calendar/README.md
@@ -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).
+
diff --git a/apps/calendar/calendar.js b/apps/calendar/calendar.js
index 6f3c33164..5707bd97a 100644
--- a/apps/calendar/calendar.js
+++ b/apps/calendar/calendar.js
@@ -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 = {
diff --git a/apps/calendar/settings.js b/apps/calendar/settings.js
new file mode 100644
index 000000000..f9c7783a3
--- /dev/null
+++ b/apps/calendar/settings.js
@@ -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();
+ }
+ },
+ });
+})
+
diff --git a/apps/chronowid/ChangeLog b/apps/chronowid/ChangeLog
index e173467a1..ded543397 100644
--- a/apps/chronowid/ChangeLog
+++ b/apps/chronowid/ChangeLog
@@ -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
\ No newline at end of file
+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
diff --git a/apps/chronowid/README.md b/apps/chronowid/README.md
index ec1d5dd46..6e0aba681 100644
--- a/apps/chronowid/README.md
+++ b/apps/chronowid/README.md
@@ -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
-
-
+
## 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/
\ No newline at end of file
+If you have any feature requests, please write here: http://forum.espruino.com/conversations/345972/
diff --git a/apps/chronowid/app.js b/apps/chronowid/app.js
index 0cacdee23..f38105e34 100644
--- a/apps/chronowid/app.js
+++ b/apps/chronowid/app.js
@@ -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();
\ No newline at end of file
+showMenu();
diff --git a/apps/chronowid/chrono_with_pastel.jpg b/apps/chronowid/chrono_with_pastel.jpg
deleted file mode 100644
index 2f5993e79..000000000
Binary files a/apps/chronowid/chrono_with_pastel.jpg and /dev/null differ
diff --git a/apps/chronowid/chrono_with_wave.jpg b/apps/chronowid/chrono_with_wave.jpg
deleted file mode 100644
index 5f35bd28b..000000000
Binary files a/apps/chronowid/chrono_with_wave.jpg and /dev/null differ
diff --git a/apps/chronowid/screenshot.png b/apps/chronowid/screenshot.png
new file mode 100644
index 000000000..f94eece94
Binary files /dev/null and b/apps/chronowid/screenshot.png differ
diff --git a/apps/chronowid/widget.js b/apps/chronowid/widget.js
index f0e785efd..2d1c78941 100644
--- a/apps/chronowid/widget.js
+++ b/apps/chronowid/widget.js
@@ -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();
-})();
\ No newline at end of file
+ // set width correctly, start countdown each second
+ WIDGETS["chronowid"].reload();
+})();
diff --git a/apps/cliclockJS2Enhanced/ChangeLog b/apps/cliclockJS2Enhanced/ChangeLog
index c7cb9e2c6..f4d146d5f 100644
--- a/apps/cliclockJS2Enhanced/ChangeLog
+++ b/apps/cliclockJS2Enhanced/ChangeLog
@@ -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)
diff --git a/apps/cliclockJS2Enhanced/app.js b/apps/cliclockJS2Enhanced/app.js
index 70e86f3d6..b6172b497 100644
--- a/apps/cliclockJS2Enhanced/app.js
+++ b/apps/cliclockJS2Enhanced/app.js
@@ -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();
diff --git a/apps/clicompleteclk/ChangeLog b/apps/clicompleteclk/ChangeLog
new file mode 100644
index 000000000..ee05bd582
--- /dev/null
+++ b/apps/clicompleteclk/ChangeLog
@@ -0,0 +1,2 @@
+0.01: New clock!
+0.02: Load steps from Health Tracking app (if installed)
diff --git a/apps/clicompleteclk/README.md b/apps/clicompleteclk/README.md
new file mode 100644
index 000000000..8b8094633
--- /dev/null
+++ b/apps/clicompleteclk/README.md
@@ -0,0 +1,24 @@
+# Command line complete clock
+
+Command line styled clock with lots of information:
+
+It can show the following (depending on availability) information:
+* Time data:
+ * Time
+ * Day of week
+ * Date
+* Additional information (can be toggled via settings):
+ * 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
diff --git a/apps/clicompleteclk/app-icon.js b/apps/clicompleteclk/app-icon.js
new file mode 100644
index 000000000..b874bb6fa
--- /dev/null
+++ b/apps/clicompleteclk/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwgI/8/4ACAqYv/F/PwAqgA6A=="))
diff --git a/apps/clicompleteclk/app.js b/apps/clicompleteclk/app.js
new file mode 100644
index 000000000..a39b37e58
--- /dev/null
+++ b/apps/clicompleteclk/app.js
@@ -0,0 +1,250 @@
+const storage = require('Storage');
+const locale = require("locale");
+
+const font12 = g.getFonts().includes("12x20");
+const font = font12 ? "12x20" : "6x8";
+const fontsize = font12 ? 1: 2;
+const fontheight = 19;
+
+const marginTop = 5;
+const marginLeftTopic = 3; // margin of topics
+const marginLeftData = font12 ? 64 : 75; // margin of data values
+
+const topicColor = g.theme.dark ? "#fff" : "#000";
+const textColor = g.theme.dark ? "#0f0" : "#080";
+const textColorRed = g.theme.dark ? "#FF0000" : "#FF0000";
+
+let hrtValue;
+let hrtValueIsOld = false;
+
+let localTempValue;
+let weatherTempString;
+let lastHeartRateRowIndex;
+let lastStepsRowIndex;
+let i = 2;
+
+let settings;
+
+function loadSettings() {
+ settings = storage.readJSON('clicompleteclk.json', 1) || {};
+}
+
+function setting(key) {
+ if (!settings) { loadSettings(); }
+ const DEFAULTS = {
+ 'battery': true,
+ 'batteryLvl': 30,
+ 'weather': true,
+ 'steps': true,
+ 'heartrate': true
+ };
+ return (key in settings) ? settings[key] : DEFAULTS[key];
+}
+
+
+let showBattery = setting('battery');
+let batteryWarnLevel = setting('batteryLvl');
+let showWeather = setting('weather');
+let showSteps = setting('steps');
+let showHeartRate = setting('heartrate');
+
+
+var drawTimeout;
+function queueDraw() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ drawAll(true);
+ }, 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);
+}
+
+function drawInfo(now) {
+ if (now == undefined)
+ now = new Date();
+
+ i = 2;
+
+ writeLineTopic("DOWK", i);
+ writeLine(locale.dow(now),i);
+ i++;
+
+ writeLineTopic("DATE", i);
+ writeLine(locale.date(now,1),i);
+ i++;
+
+ if (showBattery) {
+ writeLineTopic("BATT", i);
+ const b = E.getBattery();
+ writeLine(b + "%", i, b < batteryWarnLevel ? textColorRed : textColor);
+ i++;
+ }
+
+ if (showWeather) {
+ drawWeather();
+ }
+
+ if (showSteps) {
+ drawSteps(i);
+ i++;
+ }
+
+ if (showHeartRate) {
+ drawHeartRate(i);
+ }
+}
+
+function drawWeather() {
+ 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++;
+ }
+}
+
+function drawSteps(i) {
+ if (!showSteps) return;
+ if (i == undefined)
+ i = lastStepsRowIndex;
+ const steps = getSteps();
+ if (steps != undefined) {
+ writeLineTopic("STEP", i);
+ writeLine(steps, i);
+ }
+ lastStepsRowIndex = i;
+}
+
+function drawHeartRate(i) {
+ if (!showHeartRate) return;
+ 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) {
+ if (showHeartRate) {
+ Bangle.setHRMPower(1,"clicompleteclk");
+ if (hrtValue == undefined)
+ hrtValue = "...";
+ else
+ hrtValueIsOld = true;
+ }
+ } else {
+ if (showHeartRate) {
+ hrtValueIsOld = true;
+ Bangle.setHRMPower(0,"clicompleteclk");
+ }
+ }
+ // Update steps and heart rate
+ drawSteps();
+ drawHeartRate();
+});
+
+Bangle.on('lcdPower',function(on) {
+ if (on) {
+ drawAll(true);
+ } else {
+ if (showHeartRate) {
+ hrtValueIsOld = true;
+ }
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ }
+});
+
+if (showHeartRate) {
+ 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();
+loadSettings();
+drawAll(true);
diff --git a/apps/clicompleteclk/app.png b/apps/clicompleteclk/app.png
new file mode 100644
index 000000000..104e6124a
Binary files /dev/null and b/apps/clicompleteclk/app.png differ
diff --git a/apps/clicompleteclk/settings.js b/apps/clicompleteclk/settings.js
new file mode 100644
index 000000000..2df20ed3e
--- /dev/null
+++ b/apps/clicompleteclk/settings.js
@@ -0,0 +1,54 @@
+(function(back) {
+ const storage = require('Storage');
+ let settings = storage.readJSON('clicompleteclk.json', 1) || {};
+ function save(key, value) {
+ settings[key] = value;
+ storage.write('clicompleteclk.json', settings);
+ }
+ E.showMenu({
+ '': { 'title': 'CLI complete clk' },
+ 'Show battery': {
+ value: "battery" in settings ? settings.battery : false,
+ format: () => (settings.battery ? 'Yes' : 'No'),
+ onchange: () => {
+ settings.battery = !settings.battery;
+ save('battery', settings.battery);
+ },
+ },
+ 'Battery warn': {
+ value: "batteryLvl" in settings ? settings.batteryLvl : 30,
+ min: 0,
+ max : 100,
+ step: 10,
+ format: x => {
+ return x + "%";
+ },
+ onchange: x => save('batteryLvl', x),
+ },
+ 'Show weather': {
+ value: "weather" in settings ? settings.weather : false,
+ format: () => (settings.weather ? 'Yes' : 'No'),
+ onchange: () => {
+ settings.weather = !settings.weather;
+ save('weather', settings.weather);
+ },
+ },
+ 'Show steps': {
+ value: "steps" in settings ? settings.steps : false,
+ format: () => (settings.steps ? 'Yes' : 'No'),
+ onchange: () => {
+ settings.steps = !settings.steps;
+ save('steps', settings.steps);
+ },
+ },
+ 'Show heartrate': {
+ value: "heartrate" in settings ? settings.heartrate : false,
+ format: () => (settings.heartrate ? 'Yes' : 'No'),
+ onchange: () => {
+ settings.heartrate = !settings.heartrate;
+ save('heartrate', settings.heartrate);
+ },
+ },
+ '< Back': back,
+ });
+});
diff --git a/apps/cliock/ChangeLog b/apps/cliock/ChangeLog
index 2a93a0d5f..68249b622 100644
--- a/apps/cliock/ChangeLog
+++ b/apps/cliock/ChangeLog
@@ -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)
diff --git a/apps/cliock/app.js b/apps/cliock/app.js
index 0fd6ea580..d9271bf15 100644
--- a/apps/cliock/app.js
+++ b/apps/cliock/app.js
@@ -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();
-});
\ No newline at end of file
+});
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+drawAll();
diff --git a/apps/emojuino/ChangeLog b/apps/emojuino/ChangeLog
index 1c99f1970..04367183f 100644
--- a/apps/emojuino/ChangeLog
+++ b/apps/emojuino/ChangeLog
@@ -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.
diff --git a/apps/emojuino/emojuino.js b/apps/emojuino/emojuino.js
index 5b7670652..d241063e6 100644
--- a/apps/emojuino/emojuino.js
+++ b/apps/emojuino/emojuino.js
@@ -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();
diff --git a/apps/flow/README.md b/apps/flow/README.md
new file mode 100644
index 000000000..caeaf92d9
--- /dev/null
+++ b/apps/flow/README.md
@@ -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
+
+
+
+
diff --git a/apps/flow/app-icon.js b/apps/flow/app-icon.js
new file mode 100644
index 000000000..969a608f4
--- /dev/null
+++ b/apps/flow/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEw4X/AwX48EHgEC1WgCQkVqoDBBfuqBQcBqoLagEqGAguBqALaGAOoAoQuEBbEAKgIMBBQNUBbgMCyoKHBbBVBBYIKGBbEBtNVrQLfOgNaT4gLagp0CPQOABbcBFwNAgEKBgILbitVqAFClWq0ALZFwTDFGAQLZFwYwDBfg"))
diff --git a/apps/flow/app.js b/apps/flow/app.js
new file mode 100644
index 000000000..5f4da8f35
--- /dev/null
+++ b/apps/flow/app.js
@@ -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);
diff --git a/apps/flow/app.png b/apps/flow/app.png
new file mode 100644
index 000000000..b35c3ca77
Binary files /dev/null and b/apps/flow/app.png differ
diff --git a/apps/flow/screenshot1.png b/apps/flow/screenshot1.png
new file mode 100644
index 000000000..fd5dee427
Binary files /dev/null and b/apps/flow/screenshot1.png differ
diff --git a/apps/flow/screenshot2.png b/apps/flow/screenshot2.png
new file mode 100644
index 000000000..e29691b69
Binary files /dev/null and b/apps/flow/screenshot2.png differ
diff --git a/apps/flow/screenshot3.png b/apps/flow/screenshot3.png
new file mode 100644
index 000000000..3e1c80ba7
Binary files /dev/null and b/apps/flow/screenshot3.png differ
diff --git a/apps/fwupdate/ChangeLog b/apps/fwupdate/ChangeLog
index ec66c5568..96e7e4e9b 100644
--- a/apps/fwupdate/ChangeLog
+++ b/apps/fwupdate/ChangeLog
@@ -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
diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html
index 7230a77a8..8c2008e54 100644
--- a/apps/fwupdate/custom.html
+++ b/apps/fwupdate/custom.html
@@ -4,28 +4,45 @@
THIS IS CURRENTLY BETA - PLEASE USE THE NORMAL FIRMWARE UPDATE
- INSTRUCTIONS FOR BANGLE.JS 1 AND BANGLE.JS 2
+ INSTRUCTIONS FOR BANGLE.JS 1 AND BANGLE.JS 2 . For usage on Bangle.js 2 you'll likely need to have an updated bootloader.
+ Your current firmware version is unknown
-
Please upload a hex file here. This file should be the .app_hex
+
+
The currently available Espruino firmware releases are:
+
+
To update, click the link and then click the 'Upload' button that appears.
+
+
+
Or you can upload a hex or zip file here. This file should be an .app_hex
file, *not* the normal .hex (as that contains the bootloader as well).
-
-
Upload
+
+
Upload
+ Firmware updates via this tool work differently to the NRF Connect method mentioned on
+ the Bangle.js page . Firmware
+ is uploaded to a file on the Bangle. Once complete the Bangle reboots and the bootloader copies
+ the new firmware into internal Storage.
+
+
+
diff --git a/apps/gbmusic/ChangeLog b/apps/gbmusic/ChangeLog
index 9cebf0a31..316b98a84 100644
--- a/apps/gbmusic/ChangeLog
+++ b/apps/gbmusic/ChangeLog
@@ -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
diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js
index f514dfccd..1bddf70f7 100644
--- a/apps/gbmusic/app.js
+++ b/apps/gbmusic/app.js
@@ -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)
diff --git a/apps/gbridge/ChangeLog b/apps/gbridge/ChangeLog
index fddb1eb80..67d421f33 100644
--- a/apps/gbridge/ChangeLog
+++ b/apps/gbridge/ChangeLog
@@ -24,3 +24,5 @@
0.22: Respect Quiet Mode
0.23: Allow notification dismiss to remove from phone too
0.24: tag HRM power requests to allow this to work alongside other widgets/apps (fix #799)
+0.25: workaround call notification
+ Fix inflated step number
diff --git a/apps/gbridge/widget.js b/apps/gbridge/widget.js
index 53f832b07..7cb7147ec 100644
--- a/apps/gbridge/widget.js
+++ b/apps/gbridge/widget.js
@@ -184,7 +184,7 @@
case "call":
var note = { size: 55, title: event.name, id: "call",
body: event.number, icon:require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw="))}
- if (event.cmd === "incoming") {
+ if (event.cmd === "incoming" || event.cmd === "") {
require("notify").show(note);
if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) {
Bangle.buzz();
@@ -262,7 +262,7 @@
// Send a summary of activity to Gadgetbridge
function sendActivity(hrm) {
var steps = currentSteps - lastSentSteps;
- lastSentSteps = 0;
+ lastSentSteps = currentSteps;
gbSend({ t: "act", stp: steps, hrm:hrm });
}
diff --git a/apps/golfscore/ChangeLog b/apps/golfscore/ChangeLog
new file mode 100644
index 000000000..4995dd59a
--- /dev/null
+++ b/apps/golfscore/ChangeLog
@@ -0,0 +1,2 @@
+0.01: New App!
+0.02: multiple player score support
\ No newline at end of file
diff --git a/apps/golfscore/README.md b/apps/golfscore/README.md
new file mode 100644
index 000000000..68552ad4b
--- /dev/null
+++ b/apps/golfscore/README.md
@@ -0,0 +1,37 @@
+# Golf Score
+
+Lets you keep track of strokes during a game of Golf.
+
+
+
+
+
+
+## Usage
+
+1. Open the app,
+1. scroll to setup
+2. set the number of holes (18 by default, but can be configured)
+3. set the number of players (4 by default, but can be 1-20)
+4. click back
+5. scroll to a hole (hole 1)
+6. scroll to a player and set the number of strokes they took (repeat as needed)
+7. click next hole and repeat #6 and #7 as needed; or click back
+8. at any time, check the score card for a sum total of all the strokes for each player
+
+## Features
+
+Track strokes for multiple players (1-20)
+Set number of holes on course
+
+## Controls
+
+N/A
+
+## Requests
+
+Michael Salaverry (github.com/barakplasma)
+
+## Creator
+
+Michael Salaverry
diff --git a/apps/golfscore/app-icon.js b/apps/golfscore/app-icon.js
new file mode 100644
index 000000000..238001688
--- /dev/null
+++ b/apps/golfscore/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwIEBgOABQcD4AFDg1wAokYDokOAokDDwkBDwkADwn4nAFD/geDgP8gYFEDwn8gFgDocA+AFCkE/A4IABg//Aoc//4RDn/+Goc/8AFJj4FLEQYFGh4FLIAYFGg4FKh5sBApEfnhTEAok+Aol8vihEAon4AocB+F4ZQYFF8AFDg/AAocPAouAKYcfXQQFHjzEEhjvDA"))
diff --git a/apps/golfscore/app.js b/apps/golfscore/app.js
new file mode 100644
index 000000000..7c5c2d0e8
--- /dev/null
+++ b/apps/golfscore/app.js
@@ -0,0 +1,113 @@
+// @ts-check
+// @ts-ignore
+const menu = require("graphical_menu");
+/**
+ * @type {{showMenu: (config) => void}}
+ */
+let E;
+/**
+ * @type {{clear: () => void}}
+ */
+let g;
+
+let holes_count = 18;
+let player_count = 4;
+/**
+ * @type {number[][]}
+ */
+let course = new Array(holes_count).map(() => new Array(player_count).fill(0));
+
+const main_menu = {
+ "": {
+ "title": "-- Golf --"
+ },
+ "Setup": function () { E.showMenu(setup_menu); },
+ "Score Card": function () {
+ calculate_score();
+ E.showMenu(score_card);
+ },
+};
+
+function calculate_score() {
+ let scores = course.reduce((acc, hole) => {
+ hole.forEach((stroke_count, player) => {
+ acc[player] = acc[player]+stroke_count;
+ });
+ return acc;
+ }, new Array(player_count).fill(0));
+
+ score_card = {
+ "": {
+ "title": "score card"
+ },
+ "< Back": function () { E.showMenu(main_menu); },
+ };
+
+ for (let player = 0; player < player_count; player++) {
+ score_card["Player - " + (player + 1)] = {
+ value: scores[player]
+ };
+ }
+}
+
+let score_card = {};
+
+const setup_menu = {
+ "": {
+ "title": "-- Golf Setup --"
+ },
+ "Holes": {
+ value: holes_count,
+ min: 1, max: 20, step: 1, wrap: true,
+ onchange: v => { holes_count = v; add_holes(); }
+ },
+ "Players": {
+ value: player_count,
+ min: 1, max: 10, step: 1, wrap: true,
+ onchange: v => { player_count = v; }
+ },
+ "< Back": function () { E.showMenu(main_menu); },
+};
+
+function inc_hole(i, player) { return function (v) { course[i][player] = v; }; }
+
+function add_holes() {
+ for (let j = 0; j < 20; j++) {
+ delete main_menu["Hole - " + (j + 1)];
+ }
+ for (let i = 0; i < holes_count; i++) {
+ course[i] = new Array(player_count).fill(0);
+ main_menu["Hole - " + (i + 1)] = goto_hole_menu(i);
+ }
+ E.showMenu(main_menu);
+}
+
+function goto_hole_menu(i) {
+ return function () {
+ E.showMenu(hole_menu(i));
+ };
+}
+
+function hole_menu(i) {
+ let menu = {
+ "": {
+ "title": `-- Hole ${i + 1}--`
+ },
+ "Next hole": goto_hole_menu(i + 1),
+ "< Back": function () { E.showMenu(main_menu); },
+ };
+
+ for (let player = 0; player < player_count; player++) {
+ menu[`player - ${player + 1}`] = {
+ value: course[i][player],
+ min: 1, max: 20, step: 1, wrap: true,
+ onchange: inc_hole(i, player)
+ };
+ }
+
+ return menu;
+}
+
+// @ts-ignore
+g.clear();
+add_holes();
\ No newline at end of file
diff --git a/apps/golfscore/app.png b/apps/golfscore/app.png
new file mode 100644
index 000000000..fc5d51557
Binary files /dev/null and b/apps/golfscore/app.png differ
diff --git a/apps/golfscore/holemenu.png b/apps/golfscore/holemenu.png
new file mode 100644
index 000000000..ac214f182
Binary files /dev/null and b/apps/golfscore/holemenu.png differ
diff --git a/apps/golfscore/mainmenu.png b/apps/golfscore/mainmenu.png
new file mode 100644
index 000000000..3ebeb0ca7
Binary files /dev/null and b/apps/golfscore/mainmenu.png differ
diff --git a/apps/golfscore/scorecard.png b/apps/golfscore/scorecard.png
new file mode 100644
index 000000000..9e7ff1130
Binary files /dev/null and b/apps/golfscore/scorecard.png differ
diff --git a/apps/golfscore/setupmenu.png b/apps/golfscore/setupmenu.png
new file mode 100644
index 000000000..13158e2e7
Binary files /dev/null and b/apps/golfscore/setupmenu.png differ
diff --git a/apps/gpsrec/ChangeLog b/apps/gpsrec/ChangeLog
index cb22dd13f..365405846 100644
--- a/apps/gpsrec/ChangeLog
+++ b/apps/gpsrec/ChangeLog
@@ -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)
diff --git a/apps/gpsrec/README.md b/apps/gpsrec/README.md
index 72f744452..71b934111 100644
--- a/apps/gpsrec/README.md
+++ b/apps/gpsrec/README.md
@@ -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
+
+
diff --git a/apps/gpsrec/app.js b/apps/gpsrec/app.js
index df3353930..833a816ea 100644
--- a/apps/gpsrec/app.js
+++ b/apps/gpsrec/app.js
@@ -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);
diff --git a/apps/gpsrec/screenshot.png b/apps/gpsrec/screenshot.png
new file mode 100644
index 000000000..f6e001749
Binary files /dev/null and b/apps/gpsrec/screenshot.png differ
diff --git a/apps/gpstouch/README.md b/apps/gpstouch/README.md
index 7329f9833..172b5da57 100644
--- a/apps/gpstouch/README.md
+++ b/apps/gpstouch/README.md
@@ -14,3 +14,5 @@



+
+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/)
diff --git a/apps/hebrew_calendar/ChangeLog b/apps/hebrew_calendar/ChangeLog
new file mode 100644
index 000000000..fdd29db66
--- /dev/null
+++ b/apps/hebrew_calendar/ChangeLog
@@ -0,0 +1,4 @@
+0.01: New App!
+0.02: using TS and rollup to bundle
+0.03: bug fixes and support bangle 1
+0.04: removing TS
\ No newline at end of file
diff --git a/apps/hebrew_calendar/LICENSE b/apps/hebrew_calendar/LICENSE
new file mode 100644
index 000000000..bdcdec9e4
--- /dev/null
+++ b/apps/hebrew_calendar/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+Copyright (c) 2021 Michael Salaverry
+Copyright (c) 2016-20 Ionică Bizău (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.
\ No newline at end of file
diff --git a/apps/hebrew_calendar/README.md b/apps/hebrew_calendar/README.md
new file mode 100644
index 000000000..7a96a97db
--- /dev/null
+++ b/apps/hebrew_calendar/README.md
@@ -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 
+
+## 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)
+
\ No newline at end of file
diff --git a/apps/hebrew_calendar/app-icon.js b/apps/hebrew_calendar/app-icon.js
new file mode 100644
index 000000000..b6b0a53ae
--- /dev/null
+++ b/apps/hebrew_calendar/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("AAODFVM//4AC+Betj4zD/Azth4zD/jY/RKgAD8CJuet0HGY71uADsBKo4AC/w0nGZX/Gc9/GZWAWv5WVRkzyLRlAzN4C2/Kyv//jyx//+Gcc/NBy3/Ky3/+Azhj4zP/Azhh4zP/i5/KyoAB4Azfg4zR8AzfgYzR+C7/KyoABGb0BGaeAGjwzT4C9/AAMfK6f8GbsPGafwGbs/Gaf4Xv8Ag5WTAAOAGbcDGavAGbcBGavgX/8/K6vwGbcfGav4GbcPGav8X30BKyoAB4AzZg4zX8AzZgYzXwC/9v5XX/AzZn4zX/gzZj4zX/y+8gZWXAAIzYgIzZwA0YGbPAX/cfK7PgGa8PGbPwGa8HGbP4X/ZWZ//8Ga9/GbP+Ga8/NDS+6g5Wa/+AGasDGbfAGasBGbfgX/M/I5f8B4JXM+AzVj4jL/wPBv4PL/AzVh5YMO6IA2gKtRLJSbCACatRaJYzVcZStGaJeAX/4AC8ATHRhXAGacHeSCMMI5AALgbyQI5i/4O5JWICha/e/gUJn6/neRAABj4UTAFt/II/+CpaMIahSqTCpbUTVSQdMPqoAqgZ1IwAWLg6hUAA0BDhHAJUTdP8BKiX+RWMAAMfC6wADh4bH+AXlAAcHDY/4C6y/2U5DXmU5mAC5sBX8anPa6wAnHzF/DA38GaIaYn4YG/wzRDTEfQI6/94AYPgK/h8AYPg6/hwAYPga/8OPf4GaKLH4AYPgIYG/gzRv4aG8C/7+AaqX7UfX68PUjIaaAEM/Hg2AX9SkYbTSkHQSUBDQ38X/Q7UX73+Gad/X6wXGX6aDcADz7H8AcTU67XXAAcPU6zXXAAcHGY2AX/IcUga/dNyhQXX43ANCi//X7p0Pg6/j8BKkX/8Ah45F/AdVv6/b/gzVn6/b/wzVj4dF+C//Dqy/VUJy/kUKy/5v6hUbrqhVbp38UNbdG/y//AB8BX/6/PwC//X6o4XX7hSXX+SGeX/6qOX+ZIGX/4APgZWF+AfXVSbUG/AzXj6qTaigAJh4fF4C//X/6//X/6//X73gX/6/vg6/ZNbBTGX/6/rNZq/RO5i/zI6ZTrX/4TLh6/l+C//AEcfX/6//X5v4X/4AQX/6/NMzC//X+P+KjN/X/6//X/6//X/6//X/6/v/ggZX/6/mgE/X9MCpMkyQCHz6LFCJQChp6LFGVdJk4zFGVa9WgImMX/6//ATi+TgQjNX/6//ATq/SEZy/F/6//X/4CXXyAgPX43JX+WkX/4CiyC+OhIgPza/58i/y6S/uyVAX5ogQya/ypq/5+S/vpC+MgS//AQq/yky/2pK/MECK//AVC/3ki+Kgi/Y/K/y+i//AUuAX5IdSX/X8X+X+X+OQXxEBX6d/X/6//AUC/IhIdTX4v/Kdk/RYq//AU1AX/6/W/6Gsz4zF5K/6Dqi/G0i//X/4CZpC+GgQdUp5XF8i/y+S/y/K/xpK//ASEnX+WTX/8AiS/b/i/y/y/y/6/ykC/FDqxXGKFcmGYy//AU6//ASAzG5KGrv4zF8i/3gi/d+S/y/K/y/i/ywC/bn5XF/xQrz7AGQ1dPX/6/d//JX/6/l/6/3Dq8nK43pKFWTGY3kQ1VNGY3yX+OQX8f5X+X8Q1YzG/y//AR5XG/5Trv4zGQ1c/GY3JX+kBX8H8X+XyX+X5X+GSX7mfYA5Qqp4zH5KGpk4zH0i//ARuTK4/yKFNNGY/5X+X8X/6/W//JKdIzI0iGpGZC//AR1/K4/5KdM/GY/8Q1OfGY/+X/4CNp5XH//kKdEnGZHyQ1GbGZH5X+MJEDRXI//SX+P/5KGnyYzJ8i/toC/n//pKc4zKRlF/GZPyX/4CLn6MK/5Tmz4zL5KGlp4zLetC/hk5XLYU2TGZzCkGZzCoX70kK53+Kcd/GhyJjn4zOX/4CHz5XO5JTip4zO8iJik4zO+S//AQ2TK535K0YzO/iJjGZ3+X/4CHv5YOK0c/GZyJjz6//AS1NX+UnX+WTGZ3JX/4CHn6/xkmfX+OSp6//AS0nK5vkK0eTX+VJX/4CXz6/xyVvGZnyX8k/X/4CWya/ypIzMGUskX/4CXp5XLK0tJk6/yya//AS8/X+Mkz6/xyVPX/4CXX+WSv4yI/wynpM/GZIymX8uTRhH5X9FJRZH8GVEkX/6Mg5K/pRhHkGVOSv4zG+S//AR+fKwn+RNICCp6LFGVdJm4zFF86/oAQM/K1T1L5IyteonkX/4C/AX6//AX4C/X/4C/AX6//AX4C/X/4C/AX6//AX4C/X/4C/AX6//AX4C/X/4C/AX6//AX4C/X/4C/AX6//AX4C/X/4C/AX6//AX4C/X/4C/AX6//AX4C/X/4C/AX6//AX4C/X/4C/AX6//AX4C/X9UBIn4C/AXa+BX/4C/X/6//AX6//X/4C/X/4ABIn4C/AXWQX/4C/X/6/DghH/AX4C5wC/DgBH/AX4C5Xwi//AX6//gESI/4C/AW8gX4sCI/4C/AW6+FAAMJJX4C/AWtAX48BJX4C/AWq+HAAMEJX4C/AWeAX5MAgRN/AX4CxXpQADn//AH4A/AFn8Xxy//AH6//X/4A/X/6//AH6//X/4A/X/6//AH6//X/4A/X/6//AH6//X/4A/X/6//AH6//X/4A/X/6//AH6//X/4A/X/6//AH6//X/4A/X/6//AH6//X/4A/X/6//AH6//X/4A/X/6//AH6//X/4A/X/6//AH6//X/4A/X/6//AH6//X/4A/X/6//AH6//X7n7tu/CaH27YnRE3d/EyO3X/4AG/3btu2CZ/t23bt4mS74mRtom1/4mSX+32JQXbCZwRCtu/E34mvX+xcCAQN/CRn7OIe3E34muX+39JSQRDTH4mO2wmSt6//X5JKNX4hxNE34mhX/6Y/E36/29qY/E3NtX/6/W/yY/EyffX/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X7hKMX4xxME34mgX+RZGARBiDLIwCIE34mkX+xcOAQISB/YROtu3EyV/E34mOX+39JSFv/4RPAQIm/E0S/29pKR/xxSE0vfTCNtX6QmUX/4CJTCYm/E0C/3LiACB+xxSE0vbTCW+E0u/X/4C/AXy//AX4C/X/4C/AX6//AX4C/X/4C/AX6//AX4C/X/4C/AX6//AX4C/X/4C/AX6//AX4C/X/4C/AX6//AX4C/X/4C/AX6//AX4C/X/4C/AX6//ARX2L6PfTHO+CiXtX/6/wOKX+E3C//ABaY/E34CHTSS//TH6//X+1vOKQmm7/9TCQmmX+xKRt5xSE34miX/6Y/E36/2/5KQv///ZxP24m/E0S//TH4m/X+/2JR4TCOJ+/E34mjX/4CG75xSE0yYC/yYS9omlX+xKPTAZxPE34mkX+3/JRwSD/pxNt4m/E0i/3/ZKM24TEOJt/E34mlX+3/JRm/CQn2OJgm/E0y/39pKLCQv+OJffE34mmX+/9JRVvCYxxLE34moX+3/JRQSH/ZxJ34ml24TH+wmJv4THE0y/2/xKI74TI9pxIE34mqX+3//ZKG24TKOI9/E34mrX+3/9pKFCRf+OIvfE34msX/tvTH4mt/q//X/6//X/6//X/6//X/6//X/6//X/6//TH4m/X/5xw/qYrt6//X/6//X/6/9/6//X/6//X/6/TOkC//X8m/X7wREX/6//X/6/nExi/3TAy//X/6//X7//X8n7X4t/X/6//X7G3X/6//X9XbX/4AF+y//X/6/LQxa/SHAy//X/6/TQyK/ZHDy//X8H7X/44R26//X72/X8ytKX6IRGX8ImFX/6/fv4TLVrARcAARfFX/6//TY/bVrf2X/6/yL4y//X+ZfMX/5fgX4wUJX6JKF2xf1X/6/mt6/Z/q//X/5fOVRK/s242MCgy//X750NX6f7CgttX7PtX85KGX/6/1RJC/QEAy//X/4AC9oUFFBq/nVRhKUX/4AM+y/uFJC/PSoy/pSbK//X6gUG2y/XBwo1WX/6/fSpB0L34UM/q/GWA6/OBw3bt5eNaiYpGX/6/ZVQy/VO46/OI4y/VChq//X+v/X49/X6gdOX/6/oQwy/T26/VRgy/N9q/V/a/ZfA6//Jqa/U+yhGtu/HCQaGAQIzNX4zUMOKi//X8QpGC444MU4yVQX/6/82wqOX44sFHBbaIGSy//X9/2X7vbHB4vGGTBxhX/BiUR4xiO/a/It6GN/oXHtu3GJojGLiiSaX/6/W/6/Itu/X5gXMX/6//O7AAB9qnIWwS/IFg4CDGByqGcYa//X/4AD/q/J7d/X5AUKt6//X+X7R4zUTChoABVRQCUF6wUT26//X+f2X7xbPX/6//t4rO/y/d74uO/q//X8f/VSbUGX57XHASwtPX4yqNCg2/X/6/jtorPC44CUU5oAC9q//X/AUGX6AXHASiSQX4wXNX/6SXCihlQPowCTt5ZXIhrUUX/ZQGCihlRX7N/X65uiX/5lX24tQ+y/YFSH7X/6/s34UM/qVGFyK/XFLFvCprUUX/aqGX86AHARw+NX8AUOX/6ARTA3fF7ACOE6P+DQ1/Cpn7X/6PX26/U7YwSX6akNAAn2X/6/mR4y/OPo4wTX6IlTIKq/Gd6S/+QZx9HNCi/P35WaX5xWbX/4AL/xoGt4ybRjf9Do3fX6iQcX932NDdtGaq/LEKvtDqn+X/6//AA/9X49/ECy//X9JTGX537X42/GtgAJHw23Gtq/67aAkRM7+W+y//X+G2X+pTGX/6/jNY9/CpvtX4xrWX7odGAQIXOKYyPdX/4AF/prG7a/z+w7Gt6//X8f7Nai/Ia5y/kHY5TV26//X8aDXX8b7XX/4AWNYttCp32QYwXPX8XtX44XV36//X8ihGNyy/cHAwdQX/4AWKyq/I26/v/a/XC4yOeX/4AHX4+2X944YX/6/d24WO/aGHUia/aDQxQZX/4AP/puV/6/H7a/t+y/H34YOC41vX/6/W2wXP9q/Hv6/sGo9tDB6//ADBxGRh6/IOKS/Z/q/XGQ3bv6//X86JJ36/qGQz1RX/4AZ+xZGa6wCB26/p/a/IU55lXX/6MILKBxHAQK/pGRBNXGSK//X5F/C6yMSX67yJDSAXXX/4ACOg1vC6zaZUi4CCDJ/9C6y//X7f7X5FtX8vtF5G3X/6/rU44YQX5O/X8gvYbRDXQX/6/LOi4CDX8YsIAQJjQC4y//ACq/X/q/J7a/h+wsJt5ioX/5cL24YQX5SSMX6bsLv5IP/a//ADntO4wYQU4x6QX6YpJC5oAEDQ6LhX+f9O8DdOX6QpWF5nbt6//X9/7SpXbX7f2X5W3X/6/v/6hRAA6/LPpK/Q/omKtu/IqDdHv6//ACxfZTBm3X6/7cyoAIDQ6KiX/56XQBi/OHw5EX/q//AD6PG7dvbTICGX6ftcai/Sd46//ACKeNDSiDLX5b7HAQ5CScA6JjX/u/DSP7TxrjEX5SbHAQ+3LqTaaX/6kOPqahPAQN/X5AaQboZcrX/4AHQA4aS/qkRATNvICQdH36//X+v/+y/qH6a//AEftQA3fQDYChHqf+Do6IkX+39QY4cT/a/n249T+wdGt6//ADiDHv4cT9q/mLOK//MqFvDrgCdUKn9X/6/l+yGHDqn+X8ffHSntEA6HlX+6hI34eU/a/h25YVbrq//ABKGe/q/ft43VfBCGmX/Bof+y/eGywgH26//X9HfED4CTTy/+X/6/o/6MIEC/tX7QzX+wgH36//AECeIv6MgASBUYcEC//ABP9NY9vEbK/V35T8X/4AIR5Aja+y/SFzXtEY9/X/4AiTZG/Era/PFkqEoX/X+SQ+3E7q/LFLv7Ew/fX/4AkSpBufdIwmmc0C//X6HbX/4AF+y//X937X5F/X/4AEJo9t26//AEy/It6//AAf9X5G/X/4Am9pxmX8pKGAQSCqX/n/OJG3X/4AB/a/It6//AFH2X4+2X/4ABJRHbv6//X+XbX/5KJczq//ABq/Jv6/+I44CBQFi/+/a/It6/9/q/I26//AFi/Itu/X/hHkX/4AS9p3I26/7/a/JP9q///p3ITzS/gEAwCDt6//H9v/X5Pbv6/4Ika//AC32PRNvX+/9X5R+uX/4ABPRNt36/2IJO2Pt6//AAP7PsK/eHxACB26//X+P/X5XbX+YdGAQm/X/6/y9q/Kv6/yHxVtPmC//QEi/cf0K//AD/2X5W3X9/7HZICBPeK//AAi/KQai/aDQzgaX/4Ai/a/Lt6/r/o4Ktu3PWS//AAq/Ltu/X9Q4eX/4An/qGL2y/pGpfbt55zX/4AGX5nbv6/mGrq//AFahGAQ+/X8gyMbqK//AFi/N2y/jGTi//AGCMN7d/X8AvNtu/X/6/+/qPO26/d/bvOt522X/4AJX5wCBX7f2Fh9/X/6//Ug4CK26/X/YpPbRa//JO6VS7d/X6gmQdJK//X/f/9qYRWYq/LBYwCNOfK//ABi/SAQS/LECnbv6//X/4AGU4wCva4S//X/4AG/a/z25x7X/4AO9q/yOHi//AB6/xv6//X/4AM/y/v75v9X/4ARX9u/Nvy//ACS/rt5s/X/4AS/y/p75r/X/4AVX8+/NH6//AC6/lv5m/X/4AZX8Zj/X/4Ac/q/gMP6//AD3+X7vfL/6//AEP9X7Rb/X/4AlX65X/X/4Ap+y/SKf6//AFy8Nv5O/X/4Az/y8F75H/X/6//I/6//X/5H/X/6//I/6//X/5H/X/6//I/6//X/5H/X/6//I/6//X/5H/X/6//I/6//X/5H/X/6//I/6//X/5H/X/6//I/6//X/5H/X/6//I/6//X/5H/X/6//I/6//X/5H/X/6//I/6//X/5H/X/6//I/6//X/5H/X/6//I/6//X/5H/X/6//I/6//X/5H/X/4Al+ytFAUhr/X/4ASXlICD36//X/4AO/y8rAQffX/6//ABq8tAQd/X/6//ABftX+NtX/6//ABX7XmACC26//X/4AJXmICDX/6//ABH2X+vbX/6//AA680AQV/X/6//AAv9X+9vX/6//AAvtX+9tX/6//AAq82AQW/X/6//AAf7X/O3X/6//AAa83AQa//X/6//X/6//AAX+X/ffX/6//AAP2X/fbX/6//X/6//X/4ABXnQCCv6//X/6//X/6///q/9t6//X/6//X/6//9q/9tq//X/6//X/6//X/6//X/688AQW/X/6//X/6//X/6//X/6//X/6//X/6//X/6/9/a//X/6//X/+3X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/6//X/v/X/+/X/6//X/6//X/3tX/x22X/4AI/q/9t6//X///X/t/X/6////2X/h13X/4A/AH6//AH4A/X/4A/AH6//AH4A/X/4A/AH6//AH4A/X/4A/AH6//AH4A/X/4A/AH6//AH4A/X/4A/AH6//AH4A/X/4A/AH6//AH4A/X/4A/AH6/Qg5R/AH4At8C//AH6//X/4A/X/6/PgBR/AH4AtXyC//AH6//X/4A/X/8Aj5S/AH4Ar/C/RgZT/AH4Ar4C//AH6//X6MAv5U/AH4Ap/y+SgEPKv4A/AFPwX6cBKv4A/AFOAX/4A/X/6/TgEHK34A/AE/gXygABK/4A/AE6+WgEfLH4A/AEv4X68Av5a/AH4Aj/y+YgEBLf4A/AEeAX7MAg5c/AH4Ah8C+aAAV/L/4A/ADv+V54"))
\ No newline at end of file
diff --git a/apps/hebrew_calendar/app.js b/apps/hebrew_calendar/app.js
new file mode 100644
index 000000000..399d124f3
--- /dev/null
+++ b/apps/hebrew_calendar/app.js
@@ -0,0 +1,26 @@
+g.clear();
+
+let now = new Date();
+
+let today = require('hebrewDate').hebrewDate(now);
+
+var mainmenu = {
+ "": {
+ "title": "Hebrew Date"
+ },
+ greg: {
+ // @ts-ignore
+ value: require('locale').date(now, 1),
+ },
+ date: {
+ value: today.date,
+ },
+ month: {
+ value: today.month_name,
+ },
+ year: {
+ value: today.year,
+ }
+};
+// @ts-ignore
+E.showMenu(mainmenu);
\ No newline at end of file
diff --git a/apps/hebrew_calendar/app.png b/apps/hebrew_calendar/app.png
new file mode 100644
index 000000000..0dae731cd
Binary files /dev/null and b/apps/hebrew_calendar/app.png differ
diff --git a/apps/hebrew_calendar/hebrewDate.js b/apps/hebrew_calendar/hebrewDate.js
new file mode 100644
index 000000000..da0c9cf50
--- /dev/null
+++ b/apps/hebrew_calendar/hebrewDate.js
@@ -0,0 +1,311 @@
+/*!
+ * This script was taked from this page http://www.shamash.org/help/javadate.shtml and ported to Node.js by Ionică Bizău in https://github.com/IonicaBizau/hebrew-date
+ *
+ * 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) {
+ 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.
+ */
+function hebrewDate(inputDateOrYear) {
+ 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 = 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]
+ };
+}
+
+exports.hebrewDate = hebrewDate;
diff --git a/apps/impwclock/ChangeLog b/apps/impwclock/ChangeLog
index 0592d4d04..7bc119426 100644
--- a/apps/impwclock/ChangeLog
+++ b/apps/impwclock/ChangeLog
@@ -1,3 +1,4 @@
0.01: New App!
0.02: Stopped watchface from flashing every interval
0.03: Move to Bangle.setUI to launcher support
+0.04: Tweaks for compatibility with BangleJS2
diff --git a/apps/impwclock/README.md b/apps/impwclock/README.md
index 30e42c95e..ac1341097 100644
--- a/apps/impwclock/README.md
+++ b/apps/impwclock/README.md
@@ -1,4 +1,4 @@
# Imprecise Word Clock
-This clock tells time in very rough approximation, as in "Late morning" or "Early afternoon." Good for vacations and weekends. Press button 1 to see the time in accurate, digital form. But do you really need to know the exact time?
+This clock tells time in very rough approximation, as in "Late morning" or "Early afternoon." Good for vacations and weekends. Touch the screen to see the time in accurate, digital form. But do you really need to know the exact time?
diff --git a/apps/impwclock/clock-impword.js b/apps/impwclock/clock-impword.js
index 5492eac15..8bb5da6ba 100644
--- a/apps/impwclock/clock-impword.js
+++ b/apps/impwclock/clock-impword.js
@@ -2,7 +2,7 @@
A remix of word clock
by Gordon Williams https://github.com/gfwilliams
- Changes the representation of time to be more general
-- Shows accurate digital time when button 1 is pressed
+- Toggles showing of accurate digital time when screen touched.
*/
/* jshint esversion: 6 */
@@ -34,14 +34,16 @@ const timeOfDay = {
};
+var big = g.getWidth()>200;
// offsets and increments
-const xs = 35;
-const ys = 31;
-const dy = 22;
-const dx = 25;
+const xs = big ? 35 : 20;
+const ys = big ? 31 : 28;
+const dx = big ? 25 : 20;
+const dy = big ? 22 : 16;
+
// font size and color
-const fontSize = 3; // "6x8"
+const fontSize = big ? 3 : 2; // "6x8"
const passivColor = 0x3186 /*grey*/ ;
const activeColorNight = 0xF800 /*red*/ ;
const activeColorDay = 0xFFFF /* white */;
@@ -115,6 +117,8 @@ function drawWordClock() {
// check whether we need to redraw the watchface
if (hidx !== hidxPrev) {
+ // Turn off showDigitalTime
+ showDigitalTime = false;
// draw allWords
var c;
var y = ys;
@@ -138,15 +142,14 @@ function drawWordClock() {
hidxPrev = hidx;
}
- // Display digital time while button 1 is pressed
- g.clearRect(0, 215, 240, 240);
+ // Display digital time when button is pressed or screen touched
+ g.clearRect(0, big ? 215 : 160, big ? 240 : 176, big ? 240 : 176);
if (showDigitalTime){
g.setColor(activeColor);
- g.drawString(time, 120, 215);
+ g.drawString(time, big ? 120 : 90, big ? 215 : 160);
}
}
-
Bangle.on('lcdPower', function(on) {
if (on) drawWordClock();
});
@@ -157,17 +160,14 @@ Bangle.drawWidgets();
setInterval(drawWordClock, 1E4);
drawWordClock();
-// Show digital time while top button is pressed (if we have physical buttons)
-if (global.BTN3) setWatch(function() {
- showDigitalTime = BTN1.read();
- drawWordClock();
-}, BTN1, {repeat:true,edge:"both"});
-// If LCD pressed (on Bangle.js 2) draw digital time
-Bangle.on('drag',e=>{
- var pressed = e.b!=0;
- if (pressed!=showDigitalTime) {
- showDigitalTime = pressed;
+// If LCD pressed, toggle drawing digital time
+Bangle.on('touch',e=>{
+ if (showDigitalTime){
+ showDigitalTime = false;
+ drawWordClock();
+ } else {
+ showDigitalTime = true;
drawWordClock();
}
});
diff --git a/apps/ios/ChangeLog b/apps/ios/ChangeLog
index 895f50e04..28ad78dec 100644
--- a/apps/ios/ChangeLog
+++ b/apps/ios/ChangeLog
@@ -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
\ No newline at end of file
diff --git a/apps/ios/boot.js b/apps/ios/boot.js
index c3a30170d..d402facbb 100644
--- a/apps/ios/boot.js
+++ b/apps/ios/boot.js
@@ -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)
diff --git a/apps/launch/ChangeLog b/apps/launch/ChangeLog
index 3b9dbc30c..0b2f134ad 100644
--- a/apps/launch/ChangeLog
+++ b/apps/launch/ChangeLog
@@ -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
diff --git a/apps/launch/app-bangle2.js b/apps/launch/app-bangle2.js
index 9a7aa81ed..156eecdf4 100644
--- a/apps/launch/app-bangle2.js
+++ b/apps/launch/app-bangle2.js
@@ -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];
diff --git a/apps/launch/settings.js b/apps/launch/settings.js
new file mode 100644
index 000000000..8be1adb36
--- /dev/null
+++ b/apps/launch/settings.js
@@ -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);
+});
diff --git a/apps/locale/ChangeLog b/apps/locale/ChangeLog
index 288dc6dde..509d67077 100644
--- a/apps/locale/ChangeLog
+++ b/apps/locale/ChangeLog
@@ -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
diff --git a/apps/locale/locales.js b/apps/locale/locales.js
index 9e2624b77..2e1429ef8 100644
--- a/apps/locale/locales.js
+++ b/apps/locale/locales.js
@@ -40,7 +40,16 @@ const charFallbacks = {
"č":"c",
"ř":"r",
"ő":"o",
- "ě":"e"
+ "ě":"e",
+ "ę":"e",
+ "ą":"a",
+ "ó":"o",
+ "ż":"z",
+ "ź":"z",
+ "ń":"n",
+ "ł":"l",
+ "ś":"s",
+ "ć":"c",
};
/*
@@ -130,7 +139,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 +193,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",
@@ -586,6 +612,24 @@ var locales = {
day: "Domingo,Segunda-feira,Terça-feira,Quarta-feira,Quinta-feira,Sexta-feira,Sábado",
trans: { yes: "sim", Yes: "Sim", no: "não", No: "Não", ok: "ok", on: "on", off: "off" }
},
+ "pl_PL": {
+ lang: "pl_PL",
+ decimal_point: ",",
+ thousands_sep: " ",
+ currency_symbol: "zł",
+ int_curr_symbol: "PLN",
+ speed: "kmh",
+ distance: { 0: "m", 1: "km" },
+ temperature: "°C",
+ ampm: { 0: "", 1: "" },
+ timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
+ datePattern: { 0: "%d. %b %Y", "1": "%d.%m.%Y" }, // 1. Mar 2021 // 01.03.2021
+ abmonth: "Sty,Lut,Mar,Kwi,Maj,Cze,Lip,Sie,Wrz,Paź,Lis,Gru",
+ month: "Styczeń,Luty,Marzec,Kwiecień,Maj,Czerwiec,Lipiec,Sierpień,Wrzesień,Październik,Listopad,Grudzień",
+ abday: "Ndz,Pon,Wt,Śr,Czw,Pt,Sob",
+ day: "Niedziela,Poniedziałek,Wtorek,Środa,Czwartek,Piątek,Sobota",
+ trans: { yes: "tak", Yes: "Tak", no: "nie", No: "Nie", ok: "ok", on: "on", off: "off", "< Back": "< Wstecz" }
+ },
/*,
"he_IL": { // This won't work until we get a font - see https://github.com/espruino/BangleApps/issues/399
codePage : "ISO8859-8",
diff --git a/apps/mandlebrotclock/ChangeLog b/apps/mandelbrotclock/ChangeLog
similarity index 100%
rename from apps/mandlebrotclock/ChangeLog
rename to apps/mandelbrotclock/ChangeLog
diff --git a/apps/mandlebrotclock/README.md b/apps/mandelbrotclock/README.md
similarity index 55%
rename from apps/mandlebrotclock/README.md
rename to apps/mandelbrotclock/README.md
index 8628a61d0..387343a9e 100644
--- a/apps/mandlebrotclock/README.md
+++ b/apps/mandelbrotclock/README.md
@@ -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)
diff --git a/apps/mandlebrotclock/app.png b/apps/mandelbrotclock/app.png
similarity index 100%
rename from apps/mandlebrotclock/app.png
rename to apps/mandelbrotclock/app.png
diff --git a/apps/mandlebrotclock/mandlebrotclock-icon.js b/apps/mandelbrotclock/mandelbrotclock-icon.js
similarity index 100%
rename from apps/mandlebrotclock/mandlebrotclock-icon.js
rename to apps/mandelbrotclock/mandelbrotclock-icon.js
diff --git a/apps/mandlebrotclock/mandlebrotclock.js b/apps/mandelbrotclock/mandelbrotclock.js
similarity index 99%
rename from apps/mandlebrotclock/mandlebrotclock.js
rename to apps/mandelbrotclock/mandelbrotclock.js
index 16cc8dfb8..94636056e 100644
--- a/apps/mandlebrotclock/mandlebrotclock.js
+++ b/apps/mandelbrotclock/mandelbrotclock.js
@@ -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(),
diff --git a/apps/mandlebrotclock/mandlebrotclock.png b/apps/mandelbrotclock/mandelbrotclock.png
similarity index 100%
rename from apps/mandlebrotclock/mandlebrotclock.png
rename to apps/mandelbrotclock/mandelbrotclock.png
diff --git a/apps/mandlebrotclock/screenshot_mandlebrotclock.png b/apps/mandelbrotclock/screenshot_mandelbrotclock.png
similarity index 100%
rename from apps/mandlebrotclock/screenshot_mandlebrotclock.png
rename to apps/mandelbrotclock/screenshot_mandelbrotclock.png
diff --git a/apps/menuwheel/ChangeLog b/apps/menuwheel/ChangeLog
new file mode 100644
index 000000000..defdb5049
--- /dev/null
+++ b/apps/menuwheel/ChangeLog
@@ -0,0 +1 @@
+0.01: New menu!
diff --git a/apps/menuwheel/README.md b/apps/menuwheel/README.md
new file mode 100644
index 000000000..22cb49466
--- /dev/null
+++ b/apps/menuwheel/README.md
@@ -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:
+
+
+
+Bangle.js 2:
+
+
+
+
+
+## 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.
\ No newline at end of file
diff --git a/apps/menuwheel/boot.js b/apps/menuwheel/boot.js
new file mode 100644
index 000000000..3e708e9a8
--- /dev/null
+++ b/apps/menuwheel/boot.js
@@ -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 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.valueitem.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 {
+ // 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;
+};
diff --git a/apps/menuwheel/icon.png b/apps/menuwheel/icon.png
new file mode 100644
index 000000000..61f94a035
Binary files /dev/null and b/apps/menuwheel/icon.png differ
diff --git a/apps/menuwheel/screenshot_b1_dark.png b/apps/menuwheel/screenshot_b1_dark.png
new file mode 100644
index 000000000..c6dfb802b
Binary files /dev/null and b/apps/menuwheel/screenshot_b1_dark.png differ
diff --git a/apps/menuwheel/screenshot_b1_edit.png b/apps/menuwheel/screenshot_b1_edit.png
new file mode 100644
index 000000000..a39b0a832
Binary files /dev/null and b/apps/menuwheel/screenshot_b1_edit.png differ
diff --git a/apps/menuwheel/screenshot_b1_light.png b/apps/menuwheel/screenshot_b1_light.png
new file mode 100644
index 000000000..35ac01fe9
Binary files /dev/null and b/apps/menuwheel/screenshot_b1_light.png differ
diff --git a/apps/menuwheel/screenshot_b2_dark.png b/apps/menuwheel/screenshot_b2_dark.png
new file mode 100644
index 000000000..1393838a3
Binary files /dev/null and b/apps/menuwheel/screenshot_b2_dark.png differ
diff --git a/apps/menuwheel/screenshot_b2_edit.png b/apps/menuwheel/screenshot_b2_edit.png
new file mode 100644
index 000000000..bca98a9a5
Binary files /dev/null and b/apps/menuwheel/screenshot_b2_edit.png differ
diff --git a/apps/menuwheel/screenshot_b2_light.png b/apps/menuwheel/screenshot_b2_light.png
new file mode 100644
index 000000000..4ffe08fe3
Binary files /dev/null and b/apps/menuwheel/screenshot_b2_light.png differ
diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog
index 87094a091..a3a4f377c 100644
--- a/apps/messages/ChangeLog
+++ b/apps/messages/ChangeLog
@@ -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)
\ No newline at end of file
diff --git a/apps/messages/README.md b/apps/messages/README.md
index c243ec06a..e9aa128d1 100644
--- a/apps/messages/README.md
+++ b/apps/messages/README.md
@@ -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
diff --git a/apps/messages/app.js b/apps/messages/app.js
index cb2b5c2cd..c609acb4b 100644
--- a/apps/messages/app.js
+++ b/apps/messages/app.js
@@ -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
diff --git a/apps/messages/lib.js b/apps/messages/lib.js
index 3094b34e1..63f55dd03 100644
--- a/apps/messages/lib.js
+++ b/apps/messages/lib.js
@@ -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) {
diff --git a/apps/messages/settings.js b/apps/messages/settings.js
index ef6266cf6..fd8ce8f39 100644
--- a/apps/messages/settings.js
+++ b/apps/messages/settings.js
@@ -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);
})
diff --git a/apps/messages/widget.js b/apps/messages/widget.js
index 3a22b40fd..6403c6b8d 100644
--- a/apps/messages/widget.js
+++ b/apps/messages/widget.js
@@ -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.xw.x+w.width||c.yw.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);
+})();
diff --git a/apps/mywelcome/ChangeLog b/apps/mywelcome/ChangeLog
index b012da933..f2b54e42c 100644
--- a/apps/mywelcome/ChangeLog
+++ b/apps/mywelcome/ChangeLog
@@ -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
diff --git a/apps/mywelcome/app.js b/apps/mywelcome/app-bangle1.js
similarity index 100%
rename from apps/mywelcome/app.js
rename to apps/mywelcome/app-bangle1.js
diff --git a/apps/mywelcome/app-bangle2.js b/apps/mywelcome/app-bangle2.js
new file mode 100644
index 000000000..aeee6918d
--- /dev/null
+++ b/apps/mywelcome/app-bangle2.js
@@ -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 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);
diff --git a/apps/mywelcome/custom.html b/apps/mywelcome/custom.html
index c4c721765..340f178e8 100644
--- a/apps/mywelcome/custom.html
+++ b/apps/mywelcome/custom.html
@@ -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);
})();
`;
diff --git a/apps/numerals/ChangeLog b/apps/numerals/ChangeLog
index f94d719f4..57818c180 100644
--- a/apps/numerals/ChangeLog
+++ b/apps/numerals/ChangeLog
@@ -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'
\ No newline at end of file
diff --git a/apps/numerals/README.md b/apps/numerals/README.md
index ebf4c10fe..7a8c25212 100644
--- a/apps/numerals/README.md
+++ b/apps/numerals/README.md
@@ -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
diff --git a/apps/numerals/numerals.app.js b/apps/numerals/numerals.app.js
index 3c7607eb1..baf859915 100644
--- a/apps/numerals/numerals.app.js
+++ b/apps/numerals/numerals.app.js
@@ -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();
\ No newline at end of file
diff --git a/apps/numerals/numerals.settings.js b/apps/numerals/numerals.settings.js
index 70f6e0d98..ae321322a 100644
--- a/apps/numerals/numerals.settings.js
+++ b/apps/numerals/numerals.settings.js
@@ -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"},
diff --git a/apps/openstmap/ChangeLog b/apps/openstmap/ChangeLog
index 69c34ed4e..6cb9d061e 100644
--- a/apps/openstmap/ChangeLog
+++ b/apps/openstmap/ChangeLog
@@ -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
diff --git a/apps/openstmap/app.js b/apps/openstmap/app.js
index c33acd8ad..62597ca20 100644
--- a/apps/openstmap/app.js
+++ b/apps/openstmap/app.js
@@ -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");
diff --git a/apps/openstmap/custom.html b/apps/openstmap/custom.html
index eeb148f54..56dea1188 100644
--- a/apps/openstmap/custom.html
+++ b/apps/openstmap/custom.html
@@ -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();
};
}));
diff --git a/apps/openstmap/screenshot.png b/apps/openstmap/screenshot.png
new file mode 100644
index 000000000..2895b562e
Binary files /dev/null and b/apps/openstmap/screenshot.png differ
diff --git a/apps/pebble/ChangeLog b/apps/pebble/ChangeLog
index fc3ff3ba4..76f90de8b 100644
--- a/apps/pebble/ChangeLog
+++ b/apps/pebble/ChangeLog
@@ -1,3 +1,4 @@
0.01: first release
0.02: included deployment of pebble.settings.js in apps.json
0.03: Changed time+calendar font to LECO1976Regular, changed to slanting boot
+0.04: Fix widget hiding code (fix #1046)
diff --git a/apps/pebble/pebble.app.js b/apps/pebble/pebble.app.js
index ce9ab3340..106e09b82 100644
--- a/apps/pebble/pebble.app.js
+++ b/apps/pebble/pebble.app.js
@@ -34,7 +34,7 @@ function draw() {
// turn the warning on once we have dipped below 30%
if (E.getBattery() < 30)
batteryWarning = true;
-
+
// turn the warning off once we have dipped above 40%
if (E.getBattery() > 40)
batteryWarning = false;
@@ -57,7 +57,7 @@ function draw() {
g.setFontAlign(0, -1);
g.drawString(da[0].toUpperCase(), w/4, ha); // day of week
g.drawString(getSteps(), 3*w/4, ha);
-
+
// time
// white on red for battery warning
g.setColor(!batteryWarning ? g.theme.bg : '#f00');
@@ -71,7 +71,7 @@ function draw() {
// contrast bar
g.setColor(g.theme.fg);
g.fillRect(0, h3, w, h3 + t);
-
+
// the bottom
g.setColor(settings.bg);
g.fillRect(0, h3 + t, w, h);
@@ -111,9 +111,10 @@ g.clear();
Bangle.loadWidgets();
/*
* we are not drawing the widgets as we are taking over the whole screen
- * so we will blank out the draw() functions of each widget
+ * so we will blank out the draw() functions of each widget and change the
+ * area to the top bar doesn't get cleared.
*/
-for (let wd of WIDGETS) {wd.draw=()=>{};}
+for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
loadSettings();
setInterval(draw, 15000); // refresh every 15s
draw();
diff --git a/apps/pooqroman/ChangeLog b/apps/pooqroman/ChangeLog
new file mode 100644
index 000000000..9debf0efe
--- /dev/null
+++ b/apps/pooqroman/ChangeLog
@@ -0,0 +1,2 @@
+0.01: New App!
+0.02: Make internal menu time out + small fixes
diff --git a/apps/pooqroman/app.js b/apps/pooqroman/app.js
index d25fcf1a8..d59d4ef6c 100644
--- a/apps/pooqroman/app.js
+++ b/apps/pooqroman/app.js
@@ -1,3 +1,4 @@
+/* -*- mode: Javascript; c-basic-offset: 2; indent-tabs-mode: nil; coding: latin-1 -*- */
// pooqRoman
//
// Copyright (c) 2021 Stephen P Spackman
@@ -54,8 +55,9 @@ class Options {
this.id = this.constructor.id;
this.file = `${this.id}.json`;
this.backing = storage.readJSON(this.file, true) || {};
- this.defaults = this.constructor.defaults;
- Object.keys(this.defaults).forEach(k => this.bless(k));
+ Object.setPrototypeOf(this.backing, this.constructor.defaults);
+ this.reactivator = _ => this.active();
+ Object.keys(this.constructor.defaults).forEach(k => this.bless(k));
}
writeBack(delay) {
@@ -71,29 +73,41 @@ class Options {
bless(k) {
Object.defineProperty(this, k, {
- get: () => this.backing[k] == null ? this.defaults[k] : this.backing[k],
+ get: () => this.backing[k],
set: v => {
this.backing[k] = v;
// Ten second writeback delay, since the user will roll values up and down.
this.writeBack(10000);
}
});
- }
+ }
showMenu(m) {
+ if (m instanceof Function) m = m();
if (m) {
for (const k in m) if ('init' in m[k]) m[k].value = m[k].init();
m[''].selected = -1; // Workaround for self-selection bug.
+ Bangle.on('drag', this.reactivator);
+ this.active();
+ } else {
+ if (this.bored) clearTimeout(this.bored);
+ this.bored = null;
+ Bangle.removeListener('drag', this.reactivator);
+ this.emit('done');
}
+ g.clear(true);
E.showMenu(m);
}
- reset() {
- this.backing = {};
- this.writeBack(0);
+ active() {
+ if (this.bored) clearTimeout(this.bored);
+ this.bored = setTimeout(_ => this.showMenu(), 15000);
}
- interact() {this.showMenu(this.menu);}
+ reset() {
+ this.backing = {__proto__: this.constructor.defaults};
+ this.writeBack(0);
+ }
}
class RomanOptions extends Options {
@@ -101,7 +115,7 @@ class RomanOptions extends Options {
super();
this.menu = {
'': {title: '* face options *'},
- '< Back': _ => {this.showMenu(); this.emit('done');},
+ '< Back': _ => this.showMenu(),
Ticks: {
init: _ => this.resolution,
min: 0, max: 3,
@@ -124,9 +138,11 @@ class RomanOptions extends Options {
onchange: x => this.calendric = x,
format: x => ['none', 'day', 'date'][x]
},
- Defaults: _ => {this.reset();}
+ Defaults: _ => {this.reset(); this.interact();}
};
}
+
+ interact() {this.showMenu(this.menu);}
}
RomanOptions.id = 'pooqroman';
@@ -147,7 +163,7 @@ RomanOptions.defaults = {
hubFg: g.theme.fg,
alarmFg: '#f00',
timerFg: '#0f0',
- active: g.theme.fg2,
+ activeFg: g.theme.fg2,
};
//////////////////////////////////////////////////////////////////////////////
@@ -434,7 +450,7 @@ class Sidebar {
}
static gpsColour(o) {
const fix = Bangle.getGPSFix();
- return fix && fix.fix ? o.active : o.barFg;
+ return fix && fix.fix ? o.activeFg : o.barFg;
}
doPower() {
const c = Bangle.isCharging();
@@ -455,7 +471,7 @@ class Sidebar {
if (Bangle.isCompassOn()) {
const c = Bangle.getCompass();
const a = c && this.rate <= 1000;
- this.g.setColor(a ? this.options.active : this.options.barFg).drawImage(
+ this.g.setColor(a ? this.options.activeFg : this.options.barFg).drawImage(
compassI,
this.x + 4 + imageWidth(compassI) / 2,
this.y + 4 + imageHeight(compassI) / 2,
@@ -470,7 +486,7 @@ class Sidebar {
class Roman {
constructor(g, events) {
this.g = g;
- this.state = {};
+ this.state = null;
const options = this.options = new RomanOptions();
this.events = events.loadFromSystem(this.options);
this.timescales = [1000, [1000, 60000], 60000, 3600000];
@@ -480,7 +496,7 @@ class Roman {
this.seconds = Roman.hand(g, 1, 0.9, 60, _ => options.secondFg);
}
- reset() {this.state = {}; this.g.clear(true);}
+ reset() {this.state = null;}
doIcons(which) {this.state.iconsOk = null;}
@@ -544,7 +560,7 @@ class Roman {
render(d, rate) {
const g = this.g;
- const state = this.state;
+ const state = this.state || (g.clear(true), this.state = {});
const options = this.options;
const events = this.events;
events.clean(d, -39600000); // 11h
@@ -654,8 +670,8 @@ class Clock {
drag: e => {
if (this.t0) {
if (e.b) {
- e.x > this.xN && (this.xN = e.x) || e.x > this.xX && (this.xX = e.x);
- e.y > this.yN && (this.yN = e.y) || e.y > this.yX && (this.xY = e.y);
+ e.x < this.xN && (this.xN = e.x) || e.x > this.xX && (this.xX = e.x);
+ e.y < this.yN && (this.yN = e.y) || e.y > this.yX && (this.yX = e.y);
} else if (this.xX - this.xN < 20) {
if (e.y - this.e0.y < -50) {
this.options.resolution > 0 && this.options.resolution--;
@@ -697,6 +713,7 @@ class Clock {
this.exception && clearTimeout(this.exception);
this.interval && clearInterval(this.interval);
this.timeout = this.exception = this.interval = this.rate = null;
+ this.face.reset(); // Cancel any ongoing background rendering
return this;
}
diff --git a/apps/ptlaunch/ChangeLog b/apps/ptlaunch/ChangeLog
new file mode 100644
index 000000000..de38d715a
--- /dev/null
+++ b/apps/ptlaunch/ChangeLog
@@ -0,0 +1,4 @@
+0.01: Initial creation of the pattern launch app
+0.02: Turn on lcd when launching an app if the lock screen was disabled in the settings
+0.03: Make tap to confirm new pattern more reliable. Also allow for easier creation of single circle patterns.
+0.10: Improve the management of existing patterns: Draw the linked pattern on the left hand side of the app name within a scroller, similar to the default launcher. Slighlty clean up the code to make it less horrible.
\ No newline at end of file
diff --git a/apps/ptlaunch/README.md b/apps/ptlaunch/README.md
new file mode 100644
index 000000000..8d61afece
--- /dev/null
+++ b/apps/ptlaunch/README.md
@@ -0,0 +1,53 @@
+# Pattern Launcher
+
+Directly launch apps from the clock screen with custom patterns.
+
+## Usage
+
+Create patterns and link them to apps in the Pattern Launcher app.
+
+Then launch the linked apps directly from the clock screen by simply drawing the desired pattern.
+
+## Add Pattern Screenshots
+
+
+
+
+
+## Manage Pattern Screenshots
+
+
+
+
+## Detailed Steps
+
+From the main menu you can:
+- Add a new pattern and link it to an app (first entry)
+ - To create a new pattern first select "Add Pattern"
+ - Now draw any pattern you like, this will later launch the linked app from the clock screen
+ - You can also draw a single-circle pattern (meaning a single tap on one circle) instead of drawing a 'complex' pattern
+ - If you don't like the pattern, simply re-draw it. The previous pattern will be discarded.
+ - If you are happy with the pattern tap on screen or press the button to continue
+ - Now select the app you want to launch with the pattern.
+ - Note, you can bind multiple patterns to the same app.
+- Manage created patterns (second entry)
+ - To manage your patterns first select "Manage Patterns"
+ - You will now see a scrollabe list of patterns + linked apps
+ - If you want to deletion a pattern (and unlink the app) simply tap on it, and confirm the deletion
+- Disable the lock screen on the clock screen from the settings (third entry)
+ - To launch the app from the pattern on the clock screen the watch must be unlocked.
+ - If this annoys you, you can disable the lock on the clock screen from the setting here
+
+## FAQ
+
+1) Nothing happens when I draw on the clock screen!
+
+Please double-check if you actually have a pattern linked to an app.
+
+2) I have a pattern linked to an app and still nothing happens when I draw on the clock screen!
+
+Make sure the watch is unlocked before you start drawing. If this bothers you, you can permanently disable the watch-lock from within the Pattern Launcher app (via the Settings).
+
+3) I have done all that and still nothing happens!
+
+Please note that drawing on the clock screen will not visually show the pattern you drew. It will start the app as soon as the pattern was recognized - this might take 1 or 2 seconds! If still nothing happens, that might be a bug, sorry!
diff --git a/apps/ptlaunch/add_pattern.png b/apps/ptlaunch/add_pattern.png
new file mode 100644
index 000000000..c7cc38e82
Binary files /dev/null and b/apps/ptlaunch/add_pattern.png differ
diff --git a/apps/ptlaunch/app-icon.js b/apps/ptlaunch/app-icon.js
new file mode 100644
index 000000000..07f025d71
--- /dev/null
+++ b/apps/ptlaunch/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwkE//ziEAiM//4ACmMAgMvA4fziIIBCAUgEAUCBwXxFIYYDCAvyHIgPCGwIgFCA0wAYIRCh49BLQoXB+AHEgYUBgQaCE4JGEHAZGDAAMBAQMjC4UBEw0Aj5PFDIchC4Q/BC5CtIgIXUGwIXDI6EBiCaCCYJ3RIganTa5AnEgbXIewwPGn4ICCA8hgESAoQABmUQgI2CCBQA/AAvzbIRuD/8xNwMTCBTfDPwbYEPAaPDf4LnFB4T/EEAS/Fj7vFZ4LvBgMiFIQXBCAwmEE4Q3BiUikTnHJAQFEJ4XwgERHgI/CJ4oAIC4QYBiYXDCxgXDgUzLQQXIGwpHDLoRHJgJmFO4arCO5MCK4QACh6nCJ4poDCAbGFe4QnEY4IgGG4oOCc4ofCbAj3C/8hiMSAoQYCiMRMQQQKAH4AGkMAJwsyiEBL4wQER4Z+DR5AQFX4ooCX44QGVobvOgMREAUQBwg3B+IXFc4cTmYUBgIXFgImCAAkf/59BkERIgMBBwo/BC5AkDCgwXOAAIMGI5xFBBgR3SJYinXa5A4EfAQQHewoABJAgfCCA/zFAMRn4OC/8xIAIWDCAJGBgIQBA=="))
\ No newline at end of file
diff --git a/apps/ptlaunch/app.js b/apps/ptlaunch/app.js
new file mode 100644
index 000000000..b5a3bf610
--- /dev/null
+++ b/apps/ptlaunch/app.js
@@ -0,0 +1,608 @@
+var DEBUG = false;
+
+var storage = require("Storage");
+
+var showMainMenu = () => {
+ log("loading patterns");
+ var storedPatterns = storage.readJSON("ptlaunch.patterns.json", 1) || {};
+
+ var mainmenu = {
+ "": {
+ title: "Pattern Launcher",
+ },
+ "< Back": () => {
+ log("cancel");
+ load();
+ },
+ "Add Pattern": () => {
+ log("creating pattern");
+ recognizeAndDrawPattern().then((pattern) => {
+ log("got pattern");
+ log(pattern);
+ log(pattern.length);
+
+ var confirmPromise = new Promise((resolve) => resolve(true));
+
+ if (storedPatterns[pattern]) {
+ log("pattern already exists. show confirmation prompt");
+ confirmPromise = E.showPrompt("Pattern already exists\nOverwrite?", {
+ title: "Confirm",
+ buttons: { Yes: true, No: false },
+ });
+ }
+
+ confirmPromise.then((confirm) => {
+ log("confirmPromise resolved: " + confirm);
+ if (!confirm) {
+ showMainMenu();
+ return;
+ }
+
+ log("selecting app");
+ getSelectedApp().then((app) => {
+ E.showMessage("Saving...");
+ log("got app");
+ log("saving pattern");
+
+ storedPatterns[pattern] = {
+ app: { name: app.name, src: app.src },
+ };
+ storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
+ showMainMenu();
+ });
+ });
+ });
+ },
+ "Manage Patterns": () => {
+ log("selecting pattern through app");
+ showScrollerContainingAppsWithPatterns().then((selected) => {
+ var pattern = selected.pattern;
+ var appName = selected.appName;
+ if (pattern === "back") {
+ showMainMenu();
+ } else {
+ E.showPrompt(appName + "\n\npattern:\n" + pattern, {
+ title: "Delete?",
+ buttons: { Yes: true, No: false },
+ }).then((confirm) => {
+ if (confirm) {
+ E.showMessage("Deleting...");
+ delete storedPatterns[pattern];
+ storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
+ showMainMenu();
+ } else {
+ showMainMenu();
+ }
+ });
+ }
+ });
+ },
+ Settings: () => {
+ var settings = storedPatterns.settings || {};
+
+ var settingsmenu = {
+ "": {
+ title: "Pattern Settings",
+ },
+ "< Back": () => {
+ log("cancel");
+ load();
+ },
+ };
+
+ if (settings.lockDisabled) {
+ settingsmenu["Enable lock"] = () => {
+ settings.lockDisabled = false;
+ storedPatterns.settings = settings;
+ Bangle.setOptions({ lockTimeout: 1000 * 30 });
+ storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
+ showMainMenu();
+ };
+ } else {
+ settingsmenu["Disable lock"] = () => {
+ settings.lockDisabled = true;
+ storedPatterns.settings = settings;
+ storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
+ Bangle.setOptions({ lockTimeout: 1000 * 60 * 60 * 24 * 365 });
+ showMainMenu();
+ };
+ }
+
+ E.showMenu(settingsmenu);
+ },
+ };
+ E.showMenu(mainmenu);
+};
+
+var positions = [];
+var recognizeAndDrawPattern = () => {
+ return new Promise((resolve) => {
+ E.showMenu();
+ g.clear();
+ g.setColor(0, 0, 0);
+ CIRCLES.forEach((circle) => drawCircle(circle));
+
+ var pattern = [];
+
+ var isFinished = false;
+ var finishHandler = () => {
+ if (pattern.length === 0 || isFinished) {
+ return;
+ }
+ log("Pattern is finished.");
+ isFinished = true;
+ Bangle.removeListener("drag", dragHandler);
+ Bangle.removeListener("tap", finishHandler);
+ resolve(pattern.join(""));
+ };
+ setWatch(() => finishHandler(), BTN);
+ setTimeout(() => Bangle.on("tap", finishHandler), 250);
+
+ positions = [];
+ var dragHandler = (position) => {
+ log(position);
+ positions.push(position);
+
+ debounce().then(() => {
+ if (isFinished) {
+ return;
+ }
+
+ // This might actually be a 'tap' event.
+ // Use this check in addition to the actual tap handler to make it more reliable
+ if (pattern.length > 0 && positions.length === 2) {
+ if (
+ positions[0].x === positions[1].x &&
+ positions[0].y === positions[1].y
+ ) {
+ finishHandler();
+ positions = [];
+ return;
+ }
+ }
+
+ E.showMessage("Calculating...");
+ var t0 = Date.now();
+
+ log(positions.length);
+
+ var circlesClone = cloneCirclesArray();
+ pattern = [];
+
+ var step = Math.floor(positions.length / 100) + 1;
+
+ var p, a, b, circle;
+
+ for (var i = 0; i < positions.length; i += step) {
+ p = positions[i];
+
+ circle = circlesClone[0];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circlesClone.splice(0, 1);
+ }
+ }
+
+ circle = circlesClone[1];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circlesClone.splice(1, 1);
+ }
+ }
+
+ circle = circlesClone[2];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circlesClone.splice(2, 1);
+ }
+ }
+
+ circle = circlesClone[3];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circlesClone.splice(3, 1);
+ }
+ }
+
+ circle = circlesClone[4];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circlesClone.splice(4, 1);
+ }
+ }
+
+ circle = circlesClone[5];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circlesClone.splice(5, 1);
+ }
+ }
+
+ circle = circlesClone[6];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circlesClone.splice(6, 1);
+ }
+ }
+ circle = circlesClone[7];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circlesClone.splice(7, 1);
+ }
+ }
+
+ circle = circlesClone[8];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circlesClone.splice(8, 1);
+ }
+ }
+ }
+ var tx = Date.now();
+ log(tx - t0);
+ positions = [];
+ var t1 = Date.now();
+ log(t1 - t0);
+
+ log("pattern:");
+ log(pattern);
+
+ log("redrawing");
+ g.clear();
+ drawCirclesWithPattern(pattern);
+ });
+ };
+
+ Bangle.on("drag", dragHandler);
+ });
+};
+
+var getAppList = () => {
+ var appList = storage
+ .list(/\.info$/)
+ .map((appInfoFileName) => {
+ var appInfo = storage.readJSON(appInfoFileName, 1);
+ return (
+ appInfo && {
+ name: appInfo.name,
+ // type: appInfo.type,
+ // icon: appInfo.icon,
+ sortorder: appInfo.sortorder,
+ src: appInfo.src,
+ }
+ );
+ })
+ .filter((app) => app && !!app.src);
+ appList.sort((a, b) => {
+ var n = (0 | a.sortorder) - (0 | b.sortorder);
+ if (n) return n; // do sortorder first
+ if (a.name < b.name) return -1;
+ if (a.name > b.name) return 1;
+ return 0;
+ });
+
+ return appList;
+};
+
+var getSelectedApp = () => {
+ E.showMessage("Loading apps...");
+ return new Promise((resolve) => {
+ var selectAppMenu = {
+ "": {
+ title: "Select App",
+ },
+ "< Cancel": () => {
+ log("cancel");
+ showMainMenu();
+ },
+ };
+
+ var appList = getAppList();
+ appList.forEach((app) => {
+ selectAppMenu[app.name] = () => {
+ log("app selected");
+ log(app);
+ resolve(app);
+ };
+ });
+
+ E.showMenu(selectAppMenu);
+ });
+};
+
+//////
+// manage pattern related variables and functions
+// - draws all saved patterns and their linked app names
+// - uses the scroller to allow the user to browse through them
+//////
+
+var scrollerFont = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
+
+var drawBackButton = (r) => {
+ g.clearRect(r.x, r.y, r.x + r.w - 1, r.y + r.h - 1);
+ g.setFont(scrollerFont)
+ .setFontAlign(-1, 0)
+ .drawString("< Back", 64, r.y + 32);
+};
+
+var drawAppWithPattern = (i, r, storedPatterns) => {
+ log("draw app with pattern");
+ log({ i: i, r: r, storedPatterns: storedPatterns });
+ var storedPattern = storedPatterns[i];
+ var pattern = storedPattern.pattern;
+ var app = storedPattern.app;
+
+ g.clearRect(r.x, r.y, r.x + r.w - 1, r.y + r.h - 1);
+
+ g.drawLine(r.x, r.y, 176, r.y);
+
+ drawCirclesWithPattern(pattern, {
+ enableCaching: true,
+ scale: 0.33,
+ offset: { x: 1, y: 3 + r.y },
+ });
+
+ g.setColor(0, 0, 0);
+ if (!storedPattern.wrappedAppName) {
+ storedPattern.wrappedAppName = g
+ .wrapString(app.name, g.getWidth() - 64)
+ .join("\n");
+ }
+ log(g.getWidth());
+ log(storedPattern.wrappedAppName);
+ g.setFont(scrollerFont)
+ .setFontAlign(-1, 0)
+ .drawString(storedPattern.wrappedAppName, 64, r.y + 32);
+};
+
+var showScrollerContainingAppsWithPatterns = () => {
+ var storedPatternsArray = getStoredPatternsArray();
+ log("drawing scroller for stored patterns");
+ log(storedPatternsArray);
+ log(storedPatternsArray.length);
+
+ g.clear();
+
+ var c = Math.max(storedPatternsArray.length + 1, 3);
+
+ return new Promise((resolve) => {
+ E.showScroller({
+ h: 64,
+ c: c,
+ draw: (i, r) => {
+ log("draw");
+ log({ i: i, r: r });
+ if (i <= 0) {
+ drawBackButton(r);
+ } else if (i <= storedPatternsArray.length) {
+ drawAppWithPattern(i - 1, r, storedPatternsArray);
+ }
+ },
+ select: (i) => {
+ log("selected: " + i);
+ var pattern = "back";
+ var appName = "";
+ if (i > 0) {
+ var storedPattern = storedPatternsArray[i - 1];
+ pattern = storedPattern.pattern.join("");
+ appName = storedPattern.app.name;
+ }
+ clearCircleDrawingCache();
+ resolve({ pattern: pattern, appName: appName });
+ },
+ });
+ });
+};
+
+//////
+// storage related functions:
+// - stored patterns
+// - stored settings
+//////
+
+var getStoredPatternsMap = () => {
+ log("loading stored patterns map");
+ var storedPatternsMap = storage.readJSON("ptlaunch.patterns.json", 1) || {};
+ delete storedPatternsMap.settings;
+ log(storedPatternsMap);
+ return storedPatternsMap;
+};
+
+var getStoredPatternsArray = () => {
+ var storedPatternsMap = getStoredPatternsMap();
+ log("converting stored patterns map to array");
+ var patterns = Object.keys(storedPatternsMap);
+ var storedPatternsArray = [];
+ for (var i = 0; i < patterns.length; i++) {
+ var pattern = "" + patterns[i];
+ storedPatternsArray.push({
+ pattern: pattern
+ .split("")
+ .map((circleIndex) => parseInt(circleIndex, 10)),
+ app: storedPatternsMap[pattern].app,
+ });
+ }
+ log(storedPatternsArray);
+ return storedPatternsArray;
+};
+
+//////
+// circle related variables and functions:
+// - the circle array itself
+// - the radius and the squared radius of the circles
+// - circle draw function
+//////
+
+var CIRCLE_RADIUS = 25;
+var CIRCLE_RADIUS_2 = CIRCLE_RADIUS * CIRCLE_RADIUS;
+
+var CIRCLES = [
+ { x: 25, y: 25, i: 0 },
+ { x: 87, y: 25, i: 1 },
+ { x: 150, y: 25, i: 2 },
+ { x: 25, y: 87, i: 3 },
+ { x: 87, y: 87, i: 4 },
+ { x: 150, y: 87, i: 5 },
+ { x: 25, y: 150, i: 6 },
+ { x: 87, y: 150, i: 7 },
+ { x: 150, y: 150, i: 8 },
+];
+
+var drawCircle = (circle, drawBuffer, scale) => {
+ if (!drawBuffer) {
+ drawBuffer = g;
+ }
+ if (!scale) {
+ scale = 1;
+ }
+
+ var x = circle.x * scale;
+ var y = circle.y * scale;
+ var r = CIRCLE_RADIUS * scale;
+
+ log("drawing circle");
+ log({ x: x, y: y, r: r });
+
+ drawBuffer.fillCircle(x, y, r);
+};
+
+var cachedCirclesDrawings = {};
+
+var clearCircleDrawingCache = () => {
+ cachedCirclesDrawings = {};
+};
+
+var drawCirclesWithPattern = (pattern, options) => {
+ if (!pattern || pattern.length === 0) {
+ pattern = [];
+ }
+ if (!options) {
+ options = {};
+ }
+ var enableCaching = options.enableCaching;
+ var scale = options.scale;
+ var offset = options.offset;
+ if (!enableCaching) {
+ enableCaching = false;
+ }
+ if (!scale) {
+ scale = 1;
+ }
+ if (!offset) {
+ offset = { x: 0, y: 0 };
+ }
+
+ log("drawing circles with pattern, scale and offset");
+ log(pattern);
+ log(scale);
+ log(offset);
+
+ // cache drawn patterns. especially useful for the manage pattern menu
+ var image = cachedCirclesDrawings[pattern.join("")];
+ if (!image) {
+ log("circle image not cached");
+ var drawBuffer = Graphics.createArrayBuffer(
+ g.getWidth() * scale,
+ g.getHeight() * scale,
+ 1,
+ { msb: true }
+ );
+
+ drawBuffer.setColor(1);
+ CIRCLES.forEach((circle) => drawCircle(circle, drawBuffer, scale));
+
+ drawBuffer.setColor(0);
+ drawBuffer.setFontAlign(0, 0);
+ drawBuffer.setFont("6x8", 4 * scale);
+ pattern.forEach((circleIndex, patternIndex) => {
+ var circle = CIRCLES[circleIndex];
+ drawBuffer.drawString(
+ patternIndex + 1,
+ circle.x * scale,
+ circle.y * scale
+ );
+ });
+
+ image = {
+ width: drawBuffer.getWidth(),
+ height: drawBuffer.getHeight(),
+ bpp: 1,
+ buffer: drawBuffer.buffer,
+ };
+
+ if (enableCaching) {
+ cachedCirclesDrawings[pattern.join("")] = image;
+ }
+ } else {
+ log("using cached circle image");
+ }
+
+ g.drawImage(image, offset.x, offset.y);
+};
+
+var cloneCirclesArray = () => {
+ var circlesClone = Array(CIRCLES.length);
+
+ for (var i = 0; i < CIRCLES.length; i++) {
+ circlesClone[i] = CIRCLES[i];
+ }
+
+ return circlesClone;
+};
+
+//////
+// misc lib functions
+//////
+
+var log = (message) => {
+ if (DEBUG) {
+ console.log(JSON.stringify(message));
+ }
+};
+
+var debounceTimeoutId;
+var debounce = (delay) => {
+ if (debounceTimeoutId) {
+ clearTimeout(debounceTimeoutId);
+ }
+
+ return new Promise((resolve) => {
+ debounceTimeoutId = setTimeout(() => {
+ debounceTimeoutId = undefined;
+ resolve();
+ }, delay || 500);
+ });
+};
+
+//////
+// run main function
+//////
+
+showMainMenu();
diff --git a/apps/ptlaunch/app.png b/apps/ptlaunch/app.png
new file mode 100644
index 000000000..14ed77f1d
Binary files /dev/null and b/apps/ptlaunch/app.png differ
diff --git a/apps/ptlaunch/boot.js b/apps/ptlaunch/boot.js
new file mode 100644
index 000000000..a23607768
--- /dev/null
+++ b/apps/ptlaunch/boot.js
@@ -0,0 +1,190 @@
+var DEBUG = true;
+var log = (message) => {
+ if (DEBUG) {
+ console.log(JSON.stringify(message));
+ }
+};
+
+var storedPatterns;
+var positions = [];
+var dragHandler = (position) => {
+ positions.push(position);
+
+ debounce().then(() => {
+ log(positions.length);
+
+ var CIRCLE_RADIUS = 25;
+ var CIRCLE_RADIUS_2 = CIRCLE_RADIUS * CIRCLE_RADIUS;
+
+ var circles = [
+ { x: 25, y: 25, i: 0 },
+ { x: 87, y: 25, i: 1 },
+ { x: 150, y: 25, i: 2 },
+ { x: 25, y: 87, i: 3 },
+ { x: 87, y: 87, i: 4 },
+ { x: 150, y: 87, i: 5 },
+ { x: 25, y: 150, i: 6 },
+ { x: 87, y: 150, i: 7 },
+ { x: 150, y: 150, i: 8 },
+ ];
+ var pattern = [];
+
+ var step = Math.floor(positions.length / 100) + 1;
+
+ var p, a, b, circle;
+
+ for (var i = 0; i < positions.length; i += step) {
+ p = positions[i];
+
+ circle = circles[0];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circles.splice(0, 1);
+ }
+ }
+
+ circle = circles[1];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circles.splice(1, 1);
+ }
+ }
+
+ circle = circles[2];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circles.splice(2, 1);
+ }
+ }
+
+ circle = circles[3];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circles.splice(3, 1);
+ }
+ }
+
+ circle = circles[4];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circles.splice(4, 1);
+ }
+ }
+
+ circle = circles[5];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circles.splice(5, 1);
+ }
+ }
+
+ circle = circles[6];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circles.splice(6, 1);
+ }
+ }
+ circle = circles[7];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circles.splice(7, 1);
+ }
+ }
+
+ circle = circles[8];
+ if (circle) {
+ a = p.x - circle.x;
+ b = p.y - circle.y;
+ if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
+ pattern.push(circle.i);
+ circles.splice(8, 1);
+ }
+ }
+ }
+ positions = [];
+
+ pattern = pattern.join("");
+
+ if (pattern) {
+ if (storedPatterns[pattern]) {
+ var app = storedPatterns[pattern].app;
+ if (!!app && !!app.src) {
+ if (storedPatterns.settings) {
+ if (storedPatterns.settings.lockDisabled) {
+ Bangle.setLCDPower(true);
+ }
+ }
+
+ Bangle.removeListener("drag", dragHandler);
+ load(app.src);
+ }
+ }
+ }
+ });
+};
+
+var debounceTimeoutId;
+var debounce = (delay) => {
+ if (debounceTimeoutId) {
+ clearTimeout(debounceTimeoutId);
+ }
+
+ return new Promise((resolve) => {
+ debounceTimeoutId = setTimeout(() => {
+ debounceTimeoutId = undefined;
+ resolve();
+ }, delay || 500);
+ });
+};
+
+(function () {
+ var sui = Bangle.setUI;
+ Bangle.setUI = function (mode, cb) {
+ sui(mode, cb);
+ if (!mode) {
+ Bangle.removeListener("drag", dragHandler);
+ storedPatterns = {};
+ return;
+ }
+ if (!mode.startsWith("clock")) {
+ storedPatterns = {};
+ Bangle.removeListener("drag", dragHandler);
+ return;
+ }
+
+ var storage = require("Storage");
+ storedPatterns = storage.readJSON("ptlaunch.patterns.json", 1) || {};
+ if (Object.keys(storedPatterns).length > 0) {
+ Bangle.on("drag", dragHandler);
+ if (storedPatterns.settings) {
+ if (storedPatterns.settings.lockDisabled) {
+ Bangle.setOptions({ lockTimeout: 1000 * 60 * 60 * 24 * 365 });
+ }
+ }
+ }
+ };
+})();
diff --git a/apps/ptlaunch/main_menu_add.png b/apps/ptlaunch/main_menu_add.png
new file mode 100644
index 000000000..e9a5c52a9
Binary files /dev/null and b/apps/ptlaunch/main_menu_add.png differ
diff --git a/apps/ptlaunch/main_menu_manage.png b/apps/ptlaunch/main_menu_manage.png
new file mode 100644
index 000000000..a6aee1427
Binary files /dev/null and b/apps/ptlaunch/main_menu_manage.png differ
diff --git a/apps/ptlaunch/manage_patterns.png b/apps/ptlaunch/manage_patterns.png
new file mode 100644
index 000000000..82b10ad43
Binary files /dev/null and b/apps/ptlaunch/manage_patterns.png differ
diff --git a/apps/ptlaunch/select_app.png b/apps/ptlaunch/select_app.png
new file mode 100644
index 000000000..56f0dfc83
Binary files /dev/null and b/apps/ptlaunch/select_app.png differ
diff --git a/apps/qmsched/ChangeLog b/apps/qmsched/ChangeLog
index 0b8d67e76..f41fe3416 100644
--- a/apps/qmsched/ChangeLog
+++ b/apps/qmsched/ChangeLog
@@ -2,3 +2,4 @@
0.02: Add widget
0.03: Bangle.js 2 support
0.04: Move Quiet Mode LCD options from global settings to this app
+0.05: Avoid immediately redrawing widgets on load
\ No newline at end of file
diff --git a/apps/qmsched/widget.js b/apps/qmsched/widget.js
index 8a8333ba5..b25192b06 100644
--- a/apps/qmsched/widget.js
+++ b/apps/qmsched/widget.js
@@ -1,32 +1,36 @@
-WIDGETS["qmsched"] = {
- area: "tl", width: 24, draw: function() {
- const mode = (require("Storage").readJSON("setting.json", 1) || {}).quiet|0;
- if (mode===0) { // Off
- if (this.width!==0) {
- this.width = 0;
- Bangle.drawWidgets();
+(function() {
+ WIDGETS["qmsched"] = {
+ area: "tl",
+ width: ((require("Storage").readJSON("setting.json", 1) || {}).quiet|0) ? 24 : 0,
+ draw: function() {
+ const mode = (require("Storage").readJSON("setting.json", 1) || {}).quiet|0;
+ if (mode===0) { // Off
+ if (this.width!==0) {
+ this.width = 0;
+ Bangle.drawWidgets();
+ }
+ return;
}
- return;
- }
- // not Off: make sure width is correct
- if (this.width!==24) {
- this.width = 24;
- Bangle.drawWidgets();
- return; // drawWidgets will call draw again
- }
- let x = this.x, y = this.y;
- g.clearRect(x, y, x+23, y+23);
- // quiet mode: draw red one-way-street sign (dim red on Bangle.js 1)
- x = this.x+11;y = this.y+11; // center of widget
- g.setColor(process.env.HWVERSION===2 ? 1 : 0.8, 0, 0).fillCircle(x, y, 8);
- g.setColor(g.theme.bg).fillRect(x-6, y-2, x+6, y+2);
- if (mode>1) {return;} // no alarms
- // alarms still on: draw alarm icon in bottom-right corner
- x = this.x+18;y = this.y+17; // center of alarm
- g.setColor(1, 1, 0)
- .fillCircle(x, y, 3) // alarm body
- .fillRect(x-5, y+2, x+5, y+3) // bottom ridge
- .fillRect(x-1, y-5, x+1, y+5).drawLine(x, y-6, x, y+6) // top+bottom
- .drawLine(x+5, y-3, x+3, y-5).drawLine(x-5, y-3, x-3, y-5); // wriggles
- },
-};
\ No newline at end of file
+ // not Off: make sure width is correct
+ if (this.width!==24) {
+ this.width = 24;
+ Bangle.drawWidgets();
+ return; // drawWidgets will call draw again
+ }
+ let x = this.x, y = this.y;
+ g.clearRect(x, y, x+23, y+23);
+ // quiet mode: draw red one-way-street sign (dim red on Bangle.js 1)
+ x = this.x+11;y = this.y+11; // center of widget
+ g.setColor(process.env.HWVERSION===2 ? 1 : 0.8, 0, 0).fillCircle(x, y, 8);
+ g.setColor(g.theme.bg).fillRect(x-6, y-2, x+6, y+2);
+ if (mode>1) {return;} // no alarms
+ // alarms still on: draw alarm icon in bottom-right corner
+ x = this.x+18;y = this.y+17; // center of alarm
+ g.setColor(1, 1, 0)
+ .fillCircle(x, y, 3) // alarm body
+ .fillRect(x-5, y+2, x+5, y+3) // bottom ridge
+ .fillRect(x-1, y-5, x+1, y+5).drawLine(x, y-6, x, y+6) // top+bottom
+ .drawLine(x+5, y-3, x+3, y-5).drawLine(x-5, y-3, x-3, y-5); // wriggles
+ },
+ };
+})();
\ No newline at end of file
diff --git a/apps/qrcode/ChangeLog b/apps/qrcode/ChangeLog
index e2ae6b02a..edcc41cfd 100644
--- a/apps/qrcode/ChangeLog
+++ b/apps/qrcode/ChangeLog
@@ -1,2 +1,4 @@
0.01: New App!
0.02: Add posibillity to generate Wifi code.
+0.03: Forces integer scaling and adds more configuration (error correction, description, display)
+0.04: Allow scanning of QR codes from camera or file
diff --git a/apps/qrcode/custom.html b/apps/qrcode/custom.html
index 618840da9..4920be655 100644
--- a/apps/qrcode/custom.html
+++ b/apps/qrcode/custom.html
@@ -3,56 +3,168 @@
-
- Use URL:
-
-
+ Datasource:
+
+ Text
- Use Wifi Credentials:
-
- Wifi password:
-