diff --git a/.gitignore b/.gitignore index 47233d1f5..523dc5f20 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ appdates.csv _config.yml tests/Layout/bin/tmp.* tests/Layout/testresult.bmp +apps.local.json \ No newline at end of file diff --git a/README.md b/README.md index 8e186cf79..0a92aae30 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ and that it is not licensed in another way that would make this impossible. ## How does it work? -* A list of apps is in `apps.json` +* A list of apps is in `apps.json` (this is auto-generated from all the `apps/yourapp/metadata.json` using Jekyll or `bin/create_apps_json.sh`) * Each element references an app in `apps/` which is uploaded * When it starts, BangleAppLoader checks the JSON and compares it with the files it sees in the watch's storage. @@ -53,10 +53,10 @@ easily distinguish between file types, we use the following: is limited to 28 char filenames and appends a file extension (eg `.js`) so please try and keep filenames short to avoid overflowing the buffer. * Create a folder called `apps/`, lets assume `apps/myappid` -* We'd recommend that you copy files from 'Example Applications' (below) as a base, or... +* We'd recommend that you copy files from one of the Examples in `apps/_example_*` (see below), or... * `apps/myappid/app.png` should be a 48px icon * Use http://www.espruino.com/Image+Converter to create `apps/myappid/app-icon.js`, using a 1 bit, 4 bit or 8 bit Web Palette "Image String" -* Create an entry in `apps.json` as follows: +* Create/modify `apps/myappid/metadata.json` as follows: ``` { "id": "myappid", @@ -116,8 +116,7 @@ and set it to `Load default application`. To make the process easier we've come up with some example applications that you can use as a base when creating your own. Just come up with a unique name (ideally lowercase, under 20 chars), copy `apps/_example_app` -or `apps/_example_widget` to `apps/myappid`, and add `apps/_example_X/add_to_apps.json` to -`apps.json`. +or `apps/_example_widget` to `apps/myappid`, and edit `apps/myappid/metadata.json` accordingly. **Note:** the max filename length is 28 chars, so we suggest an app ID of under 20 so that when `.app.js`/etc gets added to the end the filename isn't cropped. @@ -131,7 +130,7 @@ The app example is available in [`apps/_example_app`](apps/_example_app) Apps are listed in the Bangle.js menu, accessible from a clock app via the middle button. -* `add_to_apps.json` - insert into `apps.json`, describes the app to bootloader and loader +* `metadata.json` - describes the app to bootloader and loader * `app.png` - app icon - 48x48px * `app-icon.js` - JS version of the icon (made with http://www.espruino.com/Image+Converter) for use in Bangle.js's menu * `app.js` - app code @@ -144,11 +143,11 @@ Use the Espruino [image converter](https://www.espruino.com/Image+Converter) and Follow this steps to create a readable icon as image string. -1. upload a png file +1. upload a 48x48 png file - THE IMAGE SHOULD BE 48x48 OR LESS 2. set _X_ Use Compression 3. set _X_ Transparency (optional) 4. set Diffusion: _flat_ -5. set Colours: _1 bit_, _4 bit_ or _8 bit Web Palette_ +5. set Colours: _1 bit_, any of the Optimised options, or _8 bit Web Palette_ are best 6. set Output as: _Image String_ Replace this line with the image converter output: @@ -157,6 +156,8 @@ Replace this line with the image converter output: require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA==")) ``` +**Do not add a trailing semicolon** + You can also use this converter for creating images you like to draw with `g.drawImage()` with your app. Apps that need widgets can call `Bangle.loadWidgets()` **once** at startup to load @@ -167,17 +168,18 @@ has call to completely clear the screen. Widgets themselves will update as and w The widget example is available in [`apps/_example_widget`](apps/_example_widget) -* `add_to_apps.json` - insert into `apps.json`, describes the widget to bootloader and loader +* `metadata.json` - describes the widget to bootloader and loader * `widget.js` - widget code Widgets are just small bits of code that run whenever an app that supports them calls `Bangle.loadWidgets()`. If they want to display something in the 24px high -widget bars at the top and bottom of the screen they can add themselves to -the global `WIDGETS` array with: +widget bar at the top of the screen they can add themselves to the global +`WIDGETS` array with: ``` WIDGETS["mywidget"]={ - area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right) + area:"tl", // tl (top left), tr (top right) + sortorder:0, // (Optional) determines order of widgets in the same corner width: 24, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout draw:draw // called to draw the widget }; @@ -202,7 +204,7 @@ and which gives information about the app for the Launcher. // if it's 'clock' then it'll be loaded by default at boot time // if this is 'bootloader' then it's code that is run at boot time, but is not in a menu "version":"1.23", - // added by BangleApps loader on upload based on apps.json + // added by BangleApps loader on upload based on metadata.json "files:"file1,file2,file3", // added by BangleApps loader on upload - lists all files // that belong to the app so it can be deleted @@ -214,7 +216,7 @@ and which gives information about the app for the Launcher. } ``` -### `apps.json` format +### `metadata.json` format ``` { "id": "appid", // 7 character app id @@ -293,9 +295,9 @@ and which gives information about the app for the Launcher. * storage is used to identify the app files and how to handle them * data is used to clean up files when the app is uninstalled -### `apps.json`: `custom` element +### `metadata.json`: `custom` element -Apps that can be customised need to define a `custom` element in `apps.json`, +Apps that can be customised need to define a `custom` element in `metadata.json`, which names an HTML file in that app's folder. When `custom` is defined, the 'upload' button is replaced by a customize @@ -303,7 +305,7 @@ button, and when clicked it opens the HTML page specified in an iframe. In that HTML file you're then responsible for handling a button press and calling `sendCustomizedApp` with your own customised -version of what's in `apps.json`: +version of what's in `metadata.json`: ``` @@ -335,9 +337,9 @@ for a clean example. and will never be loaded. This is so the app loader can tell if it's a JavaScript file based on the extension, and if so it can minify and pretokenise it. -### `apps.json`: `interface` element +### `metadata.json`: `interface` element -Apps that create data that can be read back can define a `interface` element in `apps.json`, +Apps that create data that can be read back can define a `interface` element in `metadata.json`, which names an HTML file in that app's folder. When `interface` is defined, a `Download from App` button is added to @@ -401,7 +403,7 @@ Example `settings.js` E.showMenu(appMenu) }) ``` -In this example the app needs to add `myappid.settings.js` to `storage` in `apps.json`. +In this example the app needs to add `myappid.settings.js` to `storage` in `metadata.json`. It should also add `myappid.json` to `data`, to make sure it is cleaned up when the app is uninstalled. ```json { "id": "myappid", @@ -461,16 +463,13 @@ The screen is parted in a widget and app area for lcd mode `direct`(default). | areas | as rectangle or point | | :-:| :-: | | Widget | (0,0,239,23) | -| Widget bottom bar (optional) | (0,216,239,239) | -| Apps | (0,24,239,239) (see below) | +| Apps | (0,24,239,239) | | BTN1 | (230, 55) | | BTN2 | (230, 140) | | BTN3 | (230, 210) | | BTN4 | (0,0,119, 239)| | BTN5 | (120,0,239,239) | -- If there are widgets at the bottom of the screen, apps should actually keep the bottom 24px free, so should keep to the area (0,24,239,215) - - Use `g.setFontAlign(0, 0, 3)` to draw rotated string to BTN1-BTN3 with `g.drawString()`. - For BTN4-5 the touch area is named @@ -515,7 +514,6 @@ The [`testing`](testing) folder contains snippets of code that might be useful f * `testing/colors.js` - 16 bit colors as name value pairs * `testing/gpstrack.js` - code to store a GPS track in Bangle.js storage and output it back to the console -* `testing/map` - code for splitting an image into map tiles and then displaying them ## Credits diff --git a/apps.json b/apps.json index 8551481aa..537a4f697 100644 --- a/apps.json +++ b/apps.json @@ -1,5431 +1,38 @@ +--- +# ================================================================= +# ALL THE INFORMATION INSIDE APPS.JSON HAS NOW BEEN MOVED +# +# You'll find it inside a file called apps/yourapp/metadata.json +# +# Otherwise nothing has changed. GitHub Pages will automatically +# create apps.json as your site is hosted, or if you're hosting +# yourself you can run bin/create_apps_json.sh +# +# If you serve the store from localhost for development/testing, +# the loader looks for apps.local.json instead, you can run +# `bin/create_apps_json.sh apps.local.json` to create that file. +# ================================================================= + +# Uncomment the following line if you only want explicitly listed +# apps to be available on your site + +# restricted: ["boot", "launch", "antonclk", "health", "setting", "about", "widbat", "widbt", "widlock", "widid"] +--- +{%- if page.restricted == nil -%} + {%- assign apps = site.static_files | where: "name", "metadata.json" | map: "path" -%} +{%- else -%} + {%- capture temp -%} + {%- for app in page.restricted %} /apps/{{app}}/metadata.json {%- endfor -%} + {%- endcapture -%} + {%- assign apps = temp | strip | split: " " -%} +{%- endif -%} + [ - { - "id": "fwupdate", - "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", - "supports": ["BANGLEJS2"], - "custom": "custom.html", - "customConnect": true, - "storage": [], - "sortorder": 20 - }, - { - "id": "boot", - "name": "Bootloader", - "version": "0.40", - "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", - "icon": "bootloader.png", - "type": "bootloader", - "tags": "tool,system", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":".boot0","url":"boot0.js"}, - {"name":".bootcde","url":"bootloader.js"}, - {"name":"bootupdate.js","url":"bootupdate.js"} - ], - "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.17", - "description": "App to display notifications from iOS and Gadgetbridge", - "icon": "app.png", - "type": "app", - "tags": "tool,system", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"messages.app.js","url":"app.js"}, - {"name":"messages.settings.js","url":"settings.js"}, - {"name":"messages.img","url":"app-icon.js","evaluate":true}, - {"name":"messages.wid.js","url":"widget.js"}, - {"name":"messages","url":"lib.js"} - ], - "data": [{"name":"messages.json"},{"name":"messages.settings.json"}], - "screenshots": [{"url":"screenshot.png"},{"url":"screenshot-notify.gif"}], - "sortorder": -9 - }, - { - "id": "android", - "name": "Android Integration", - "shortName": "Android", - "version": "0.05", - "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", - "icon": "app.png", - "tags": "tool,system,messages,notifications", - "dependencies": {"messages":"app"}, - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"android.app.js","url":"app.js"}, - {"name":"android.settings.js","url":"settings.js"}, - {"name":"android.img","url":"app-icon.js","evaluate":true}, - {"name":"android.boot.js","url":"boot.js"} - ], - "sortorder": -8 - }, - { - "id": "ios", - "name": "iOS Integration", - "version": "0.08", - "description": "Display notifications/music/etc from iOS devices", - "icon": "app.png", - "tags": "tool,system,ios,apple,messages,notifications", - "dependencies": {"messages":"app"}, - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"ios.app.js","url":"app.js"}, - {"name":"ios.img","url":"app-icon.js","evaluate":true}, - {"name":"ios.boot.js","url":"boot.js"} - ], - "sortorder": -8 - }, - { - "id": "health", - "name": "Health Tracking", - "version": "0.09", - "description": "Logs health data and provides an app to view it (requires firmware 2v10.100 or later)", - "icon": "app.png", - "tags": "tool,system,health", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "interface": "interface.html", - "storage": [ - {"name":"health.app.js","url":"app.js"}, - {"name":"health.img","url":"app-icon.js","evaluate":true}, - {"name":"health.boot.js","url":"boot.js"}, - {"name":"health","url":"lib.js"} - ] - }, - { - "id": "launch", - "name": "Launcher", - "shortName": "Launcher", - "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", - "tags": "tool,system,launcher", - "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.settings.js","url":"settings.js","supports":["BANGLEJS2"]} - ], - "data": [{"name":"launch.json"}], - "sortorder": -10 - }, - { - "id": "setting", - "name": "Settings", - "version": "0.40", - "description": "A menu for setting up Bangle.js", - "icon": "settings.png", - "tags": "tool,system", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"setting.app.js","url":"settings.js"}, - {"name":"setting.img","url":"settings-icon.js","evaluate":true} - ], - "data": [{"name":"setting.json","url":"settings.min.json","evaluate":true}], - "sortorder": -5 - }, - { - "id": "about", - "name": "About", - "version": "0.12", - "description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers", - "icon": "app.png", - "tags": "tool,system", - "supports": ["BANGLEJS","BANGLEJS2"], - "screenshots": [{"url":"bangle1-about-screenshot.png"}], - "allow_emulator": true, - "storage": [ - {"name":"about.app.js","url":"app-bangle1.js","supports": ["BANGLEJS"]}, - {"name":"about.app.js","url":"app-bangle2.js","supports": ["BANGLEJS2"]}, - {"name":"about.img","url":"app-icon.js","evaluate":true} - ], - "sortorder": -4 - }, - { - "id": "alarm", - "name": "Default Alarm & Timer", - "shortName": "Alarms", - "version": "0.14", - "description": "Set and respond to alarms and timers", - "icon": "app.png", - "tags": "tool,alarm,widget", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"alarm.app.js","url":"app.js"}, - {"name":"alarm.boot.js","url":"boot.js"}, - {"name":"alarm.js","url":"alarm.js"}, - {"name":"alarm.img","url":"app-icon.js","evaluate":true}, - {"name":"alarm.wid.js","url":"widget.js"} - ], - "data": [{"name":"alarm.json"}] - }, - { - "id": "locale", - "name": "Languages", - "version": "0.14", - "description": "Translations for different countries", - "icon": "locale.png", - "type": "locale", - "tags": "tool,system,locale,translate", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "custom": "locale.html", - "storage": [ - {"name":"locale"} - ], - "sortorder": -10 - }, - { - "id": "notify", - "name": "Notifications (default)", - "shortName": "Notifications", - "version": "0.11", - "description": "Provides the default `notify` module used by applications to display notifications in a bar at the top of the screen. This module is installed by default by client applications such as the Gadgetbridge app. Installing `Fullscreen Notifications` replaces this module with a version that displays the notifications using the full screen", - "icon": "notify.png", - "type": "notify", - "tags": "widget", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"notify","url":"notify.js"} - ] - }, - { - "id": "notifyfs", - "name": "Fullscreen Notifications", - "shortName": "Notifications", - "version": "0.12", - "description": "Provides a replacement for the `Notifications (default)` `notify` module. This version is used by applications to display notifications fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notify module.", - "icon": "notify.png", - "type": "notify", - "tags": "widget", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"notify","url":"notify.js"} - ] - }, - { - "id": "welcome", - "name": "Welcome", - "shortName": "Welcome", - "version": "0.14", - "description": "Appears at first boot and explains how to use Bangle.js", - "icon": "app.png", - "screenshots": [{"url":"screenshot_welcome.png"}], - "tags": "start,welcome", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"welcome.boot.js","url":"boot.js"}, - {"name":"welcome.app.js","url":"app-bangle1.js","supports": ["BANGLEJS"]}, - {"name":"welcome.app.js","url":"app-bangle2.js","supports": ["BANGLEJS2"]}, - {"name":"welcome.settings.js","url":"settings.js"}, - {"name":"welcome.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"name":"welcome.json"}] - }, - { - "id": "mywelcome", - "name": "Customised Welcome", - "shortName": "My Welcome", - "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","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-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} - ], - "data": [{"name":"mywelcome.json"}] - }, - { - "id": "gbridge", - "name": "Gadgetbridge", - "version": "0.25", - "description": "(NOT RECOMMENDED) Displays Gadgetbridge notifications from Android. Please use the 'Android' Bangle.js app instead.", - "icon": "app.png", - "type": "widget", - "tags": "tool,system,android,widget", - "supports": ["BANGLEJS","BANGLEJS2"], - "dependencies": {"notify":"type"}, - "readme": "README.md", - "storage": [ - {"name":"gbridge.settings.js","url":"settings.js"}, - {"name":"gbridge.img","url":"app-icon.js","evaluate":true}, - {"name":"gbridge.wid.js","url":"widget.js"} - ], - "data": [{"name":"gbridge.json"}] - }, - { "id": "gbdebug", - "name": "Gadgetbridge Debug", - "shortName":"GB Debug", - "version":"0.01", - "description": "Debug info for Gadgetbridge. Run this app and when Gadgetbridge messages arrive they are displayed on-screen.", - "icon": "app.png", - "tags": "", - "supports" : ["BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"gbdebug.app.js","url":"app.js"}, - {"name":"gbdebug.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "mclock", - "name": "Morphing Clock", - "version": "0.07", - "description": "7 segment clock that morphs between minutes and hours", - "icon": "clock-morphing.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "allow_emulator": true, - "screenshots": [{"url":"bangle1-morphing-clock-screenshot.png"}], - "storage": [ - {"name":"mclock.app.js","url":"clock-morphing.js"}, - {"name":"mclock.img","url":"clock-morphing-icon.js","evaluate":true} - ], - "sortorder": -9 - }, - { - "id": "moonphase", - "name": "Moonphase", - "version": "0.02", - "description": "Shows current moon phase. Now with GPS function.", - "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], - "screenshots": [{"url":"bangle1-moon-phase-screenshot.png"}], - "allow_emulator": true, - "storage": [ - {"name":"moonphase.app.js","url":"app.js"}, - {"name":"moonphase.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "daysl", - "name": "Days left", - "version": "0.03", - "description": "Shows you the days left until a certain date. Date can be set with a settings app and is written to a file.", - "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], - "allow_emulator": false, - "storage": [ - {"name":"daysl.app.js","url":"app.js"}, - {"name":"daysl.img","url":"app-icon.js","evaluate":true}, - {"name":"daysl.wid.js","url":"widget.js"} - ] - }, - { - "id": "wclock", - "name": "Word Clock", - "version": "0.03", - "description": "Display Time as Text", - "icon": "clock-word.png", - "screenshots": [{"url":"screenshot_word.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"wclock.app.js","url":"clock-word.js"}, - {"name":"wclock.img","url":"clock-word-icon.js","evaluate":true} - ] - }, - { - "id": "fontclock", - "name": "Font Clock", - "version": "0.01", - "description": "Choose the font and design of clock face from a library of available designs", - "icon": "fontclock.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "readme": "README.md", - "custom": "custom.html", - "allow_emulator": false, - "storage": [ - {"name":"fontclock.app.js","url":"fontclock.js"}, - {"name":"fontclock.img","url":"fontclock-icon.js","evaluate":true}, - {"name":"fontclock.hand.js","url":"fontclock.hand.js"}, - {"name":"fontclock.thinhand.js","url":"fontclock.thinhand.js"}, - {"name":"fontclock.thickhand.js","url":"fontclock.thickhand.js"}, - {"name":"fontclock.hourscriber.js","url":"fontclock.hourscriber.js"}, - {"name":"fontclock.font.js","url":"fontclock.font.js"}, - {"name":"fontclock.font.abril_ff50.js","url":"fontclock.font.abril_ff50.js"}, - {"name":"fontclock.font.cpstc58.js","url":"fontclock.font.cpstc58.js"}, - {"name":"fontclock.font.mntn25.js","url":"fontclock.font.mntn25.js"}, - {"name":"fontclock.font.mntn50.js","url":"fontclock.font.mntn50.js"}, - {"name":"fontclock.font.vector25.js","url":"fontclock.font.vector25.js"}, - {"name":"fontclock.font.vector50.js","url":"fontclock.font.vector50.js"} - ] - }, - { - "id": "slidingtext", - "name": "Sliding Clock", - "version": "0.07", - "description": "Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently English, French, Japanese, Spanish and German are supported", - "icon": "slidingtext.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "custom": "custom.html", - "allow_emulator": false, - "storage": [ - {"name":"slidingtext.app.js","url":"slidingtext.js"}, - {"name":"slidingtext.img","url":"slidingtext-icon.js","evaluate":true}, - {"name":"slidingtext.locale.en.js","url":"slidingtext.locale.en.js"}, - {"name":"slidingtext.locale.en2.js","url":"slidingtext.locale.en2.js"}, - {"name":"slidingtext.utils.en.js","url":"slidingtext.utils.en.js"}, - {"name":"slidingtext.locale.es.js","url":"slidingtext.locale.es.js"}, - {"name":"slidingtext.locale.fr.js","url":"slidingtext.locale.fr.js"}, - {"name":"slidingtext.locale.jp.js","url":"slidingtext.locale.jp.js"}, - {"name":"slidingtext.locale.de.js","url":"slidingtext.locale.de.js"}, - {"name":"slidingtext.dtfmt.js","url":"slidingtext.dtfmt.js"} - ] - }, - { - "id": "solarclock", - "name": "Solar Clock", - "version": "0.02", - "description": "Using your current or chosen location the solar watch face shows the Sun's sky position, time and date. Also allows you to wind backwards and forwards in time to see the sun's position", - "icon": "solar_clock.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "readme": "README.md", - "custom": "custom.html", - "allow_emulator": false, - "storage": [ - {"name":"solarclock.app.js","url":"solar_clock.js"}, - {"name":"solarclock.img","url":"solar_clock-icon.js","evaluate":true}, - {"name":"solar_colors.js","url":"solar_colors.js"}, - {"name":"solar_controller.js","url":"solar_controller.js"}, - {"name":"solar_date_utils.js","url":"solar_date_utils.js"}, - {"name":"solar_graphic_utils.js","url":"solar_graphic_utils.js"}, - {"name":"solar_location.js","url":"solar_location.js"}, - {"name":"solar_math_utils.js","url":"solar_math_utils.js"}, - {"name":"solar_loc.Reykjavik.json","url":"solar_loc.Reykjavik.json"}, - {"name":"solar_loc.Hong_Kong.json","url":"solar_loc.Hong_Kong.json"}, - {"name":"solar_loc.Honolulu.json","url":"solar_loc.Honolulu.json"}, - {"name":"solar_loc.Rio.json","url":"solar_loc.Rio.json"}, - {"name":"solar_loc.Tokyo.json","url":"solar_loc.Tokyo.json"}, - {"name":"solar_loc.Seoul.json","url":"solar_loc.Seoul.json"} - ] - }, - { - "id": "sweepclock", - "name": "Sweep Clock", - "version": "0.04", - "description": "Smooth sweep secondhand with single hour numeral. Use button 1 to toggle the numeral font, button 3 to change the colour theme and button 4 to change the date placement", - "icon": "sweepclock.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "readme": "README.md", - "allow_emulator": true, - "screenshots": [{"url":"bangle1-sweep-clock-screenshot.png"}], - "storage": [ - {"name":"sweepclock.app.js","url":"sweepclock.js"}, - {"name":"sweepclock.img","url":"sweepclock-icon.js","evaluate":true} - ] - }, - { - "id": "matrixclock", - "name": "Matrix Clock", - "version": "0.02", - "description": "inspired by The Matrix, a clock of the same style", - "icon": "matrixclock.png", - "screenshots": [{"url":"screenshot_matrix.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"matrixclock.app.js","url":"matrixclock.js"}, - {"name":"matrixclock.img","url":"matrixclock-icon.js","evaluate":true} - ] - }, - { - "id": "mandelbrotclock", - "name": "Mandelbrot Clock", - "version": "0.01", - "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": "mandelbrotclock.app.js", "url": "mandelbrotclock.js" }, - { - "name": "mandelbrotclock.img", - "url": "mandelbrotclock-icon.js", - "evaluate": true - } - ] - }, - { - "id": "imgclock", - "name": "Image background clock", - "shortName": "Image Clock", - "version": "0.08", - "description": "A clock with an image as a background", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "custom": "custom.html", - "storage": [ - {"name":"imgclock.app.js","url":"app.js"}, - {"name":"imgclock.img","url":"app-icon.js","evaluate":true}, - {"name":"imgclock.face.img"}, - {"name":"imgclock.face.json"}, - {"name":"imgclock.face.bg","content":""} - ] - }, - { - "id": "impwclock", - "name": "Imprecise Word Clock", - "version": "0.04", - "description": "Imprecise word clock for vacations, weekends, and those who never need accurate time.", - "icon": "clock-impword.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "screenshots": [{"url":"bangle1-impercise-word-clock-screenshot.png"}], - "allow_emulator": true, - "storage": [ - {"name":"impwclock.app.js","url":"clock-impword.js"}, - {"name":"impwclock.img","url":"clock-impword-icon.js","evaluate":true} - ] - }, - { - "id": "aclock", - "name": "Analog Clock", - "version": "0.15", - "description": "An Analog Clock", - "icon": "clock-analog.png", - "screenshots": [{"url":"screenshot_analog.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"aclock.app.js","url":"clock-analog.js"}, - {"name":"aclock.img","url":"clock-analog-icon.js","evaluate":true} - ] - }, - { - "id": "clock2x3", - "name": "2x3 Pixel Clock", - "version": "0.05", - "description": "This is a simple clock using minimalist 2x3 pixel numerical digits", - "icon": "clock2x3.png", - "screenshots": [{"url":"screenshot_pixel.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"clock2x3.app.js","url":"clock2x3-app.js"}, - {"name":"clock2x3.img","url":"clock2x3-icon.js","evaluate":true} - ] - }, - { - "id": "geissclk", - "name": "Geiss Clock", - "version": "0.03", - "description": "7 segment clock with animated background in the style of Ryan Geiss' music visualisation. NOTE: The first run will take ~1 minute to do some precalculation", - "icon": "clock.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"geissclk.app.js","url":"clock.js"}, - {"name":"geissclk.precompute.js","url":"precompute.js"}, - {"name":"geissclk.img","url":"clock-icon.js","evaluate":true} - ], - "data": [{"name":"geissclk.0.map"},{"name":"geissclk.1.map"},{"name":"geissclk.2.map"},{"name":"geissclk.3.map"},{"name":"geissclk.4.map"},{"name":"geissclk.5.map"},{"name":"geissclk.0.pal"},{"name":"geissclk.1.pal"},{"name":"geissclk.2.pal"}] - }, - { - "id": "trex", - "name": "T-Rex", - "version": "0.04", - "description": "T-Rex game in the style of Chrome's offline game", - "icon": "trex.png", - "screenshots": [{"url":"screenshot_trex.png"}], - "tags": "game", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"trex.app.js","url":"trex.js"}, - {"name":"trex.img","url":"trex-icon.js","evaluate":true}, - {"name":"trex.settings.js","url":"settings.js"} - ], - "data": [{"name":"trex.score","storageFile":true}] - }, - { - "id": "cubescramble", - "name": "Cube Scramble", - "version":"0.04", - "description": "A random scramble generator for the 3x3 Rubik's cube with a basic timer", - "icon": "cube-scramble.png", - "tags": "", - "supports" : ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "screenshots": [{"url":"bangle2-cube-scramble-screenshot.png"},{"url":"bangle1-cube-scramble-screenshot.png"}], - "storage": [ - {"name":"cubescramble.app.js","url":"cube-scramble.js"}, - {"name":"cubescramble.img","url":"cube-scramble-icon.js","evaluate":true} - ] - }, - { - "id": "astroid", - "name": "Asteroids!", - "version": "0.03", - "description": "Retro asteroids game", - "icon": "asteroids.png", - "screenshots": [{"url":"screenshot_asteroids.png"}], - "tags": "game", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"astroid.app.js","url":"asteroids.js"}, - {"name":"astroid.img","url":"asteroids-icon.js","evaluate":true} - ] - }, - { - "id": "clickms", - "name": "Click Master", - "version": "0.01", - "description": "Get several friends to start the game, then compete to see who can press BTN1 the most!", - "icon": "click-master.png", - "tags": "game", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"clickms.app.js","url":"click-master.js"}, - {"name":"clickms.img","url":"click-master-icon.js","evaluate":true} - ] - }, - { - "id": "horsey", - "name": "Horse Race!", - "version": "0.01", - "description": "Get several friends to start the game, then compete to see who can press BTN1 the most!", - "icon": "horse-race.png", - "tags": "game", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"horsey.app.js","url":"horse-race.js"}, - {"name":"horsey.img","url":"horse-race-icon.js","evaluate":true} - ] - }, - { - "id": "compass", - "name": "Compass", - "version": "0.05", - "description": "Simple compass that points North", - "icon": "compass.png", - "screenshots": [{"url":"screenshot_compass.png"}], - "tags": "tool,outdoors", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"compass.app.js","url":"compass.js"}, - {"name":"compass.img","url":"compass-icon.js","evaluate":true} - ] - }, - { - "id": "gpstime", - "name": "GPS Time", - "version": "0.05", - "description": "Update the Bangle.js's clock based on the time from the GPS receiver", - "icon": "gpstime.png", - "tags": "tool,gps", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"gpstime.app.js","url":"gpstime.js"}, - {"name":"gpstime.img","url":"gpstime-icon.js","evaluate":true} - ] - }, - { - "id": "openloc", - "name": "Open Location / Plus Codes", - "shortName": "Open Location", - "version": "0.01", - "description": "Convert your current GPS location to a series of characters", - "icon": "app.png", - "tags": "tool,outdoors,gps", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"openloc.app.js","url":"app.js"}, - {"name":"openloc.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "speedo", - "name": "Speedo", - "version": "0.05", - "description": "Show the current speed according to the GPS", - "icon": "speedo.png", - "tags": "tool,outdoors,gps", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"speedo.app.js","url":"speedo.js"}, - {"name":"speedo.img","url":"speedo-icon.js","evaluate":true} - ] - }, - { - "id": "gpsrec", - "name": "GPS Recorder", - "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", - "storage": [ - {"name":"gpsrec.app.js","url":"app.js"}, - {"name":"gpsrec.img","url":"app-icon.js","evaluate":true}, - {"name":"gpsrec.wid.js","url":"widget.js"}, - {"name":"gpsrec.settings.js","url":"settings.js"} - ], - "data": [{"name":"gpsrec.json"},{"wildcard":".gpsrc?","storageFile":true}] - }, - { - "id": "recorder", - "name": "Recorder (BETA)", - "shortName": "Recorder", - "version": "0.05", - "description": "Record GPS position, heart rate and more in the background, then download to your PC.", - "icon": "app.png", - "tags": "tool,outdoors,gps,widget", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "interface": "interface.html", - "storage": [ - {"name":"recorder.app.js","url":"app.js"}, - {"name":"recorder.img","url":"app-icon.js","evaluate":true}, - {"name":"recorder.wid.js","url":"widget.js"}, - {"name":"recorder.settings.js","url":"settings.js"} - ], - "data": [{"name":"recorder.json"},{"wildcard":"recorder.log?.csv","storageFile":true}] - }, - { - "id": "gpsnav", - "name": "GPS Navigation", - "version": "0.05", - "description": "Displays GPS Course and Speed, + Directions to waypoint and waypoint recording, now with waypoint editor", - "icon": "icon.png", - "tags": "tool,outdoors,gps", - "supports": ["BANGLEJS"], - "readme": "README.md", - "interface": "waypoints.html", - "storage": [ - {"name":"gpsnav.app.js","url":"app.min.js"}, - {"name":"gpsnav.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"name":"waypoints.json","url":"waypoints.json"}] - }, - { - "id": "heart", - "name": "Heart Rate Recorder", - "shortName": "HRM Record", - "version": "0.07", - "description": "Application that allows you to record your heart rate. Can run in background", - "icon": "app.png", - "tags": "tool,health,widget", - "supports": ["BANGLEJS","BANGLEJS2"], - "interface": "interface.html", - "storage": [ - {"name":"heart.app.js","url":"app.js"}, - {"name":"heart.img","url":"app-icon.js","evaluate":true}, - {"name":"heart.wid.js","url":"widget.js"} - ], - "data": [{"name":"heart.json"},{"wildcard":".heart?","storageFile":true}] - }, - { - "id": "slevel", - "name": "Spirit Level", - "version": "0.02", - "description": "Show the current angle of the watch, so you can use it to make sure something is absolutely flat", - "icon": "spiritlevel.png", - "tags": "tool", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"slevel.app.js","url":"spiritlevel.js"}, - {"name":"slevel.img","url":"spiritlevel-icon.js","evaluate":true} - ] - }, - { - "id": "files", - "name": "App Manager", - "version": "0.07", - "description": "Show currently installed apps, free space, and allow their deletion from the watch", - "icon": "files.png", - "tags": "tool,system,files", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"files.app.js","url":"files.js"}, - {"name":"files.img","url":"files-icon.js","evaluate":true} - ] - }, - { - "id": "weather", - "name": "Weather", - "version": "0.15", - "description": "Show Gadgetbridge weather report", - "icon": "icon.png", - "screenshots": [{"url":"screenshot.png"}], - "tags": "widget,outdoors", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "readme.md", - "storage": [ - {"name":"weather.app.js","url":"app.js"}, - {"name":"weather.wid.js","url":"widget.js"}, - {"name":"weather","url":"lib.js"}, - {"name":"weather.img","url":"icon.js","evaluate":true}, - {"name":"weather.settings.js","url":"settings.js"} - ], - "data": [{"name":"weather.json"}] - }, - { - "id": "chargeanim", - "name": "Charge Animation", - "version": "0.02", - "description": "When charging, show a sideways charging animation and keep the screen on. When removed from the charger load the clock again.", - "icon": "icon.png", - "tags": "battery", - "supports": ["BANGLEJS", "BANGLEJS2"], - "allow_emulator": true, - "screenshots": [{"url":"bangle2-charge-animation-screenshot.png"},{"url":"bangle-charge-animation-screenshot.png"}], - "storage": [ - {"name":"chargeanim.app.js","url":"app.js"}, - {"name":"chargeanim.boot.js","url":"boot.js"}, - {"name":"chargeanim.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "bluetoothdock", - "name": "Bluetooth Dock", - "shortName": "Dock", - "version": "0.01", - "description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen", - "icon": "app.png", - "tags": "bluetooth", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"bluetoothdock.app.js","url":"app.js"}, - {"name":"bluetoothdock.boot.js","url":"boot.js"}, - {"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "widbat", - "name": "Battery Level Widget", - "version": "0.09", - "description": "Show the current battery level and charging status in the top right of the clock", - "icon": "widget.png", - "type": "widget", - "tags": "widget,battery", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widbat.wid.js","url":"widget.js"} - ] - }, - { - "id": "widbatv", - "name": "Battery Level Widget (Vertical)", - "version": "0.01", - "description": "Slim, vertical battery widget that only takes up 14px", - "icon": "widget.png", - "type": "widget", - "tags": "widget,battery", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widbatv.wid.js","url":"widget.js"} - ] - }, - { - "id": "widlock", - "name": "Lock Widget", - "version": "0.03", - "description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked", - "icon": "widget.png", - "type": "widget", - "tags": "widget,lock", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widlock.wid.js","url":"widget.js"} - ] - }, - { - "id": "widbatpc", - "name": "Battery Level Widget (with percentage)", - "shortName": "Battery Widget", - "version": "0.15", - "description": "Show the current battery level and charging status in the top right of the clock, with charge percentage", - "icon": "widget.png", - "type": "widget", - "tags": "widget,battery", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"widbatpc.wid.js","url":"widget.js"}, - {"name":"widbatpc.settings.js","url":"settings.js"} - ], - "data": [{"name":"widbatpc.json"}] - }, - { - "id": "widbatwarn", - "name": "Battery Warning", - "shortName": "Battery Warning", - "version": "0.02", - "description": "Show a warning when the battery runs low.", - "icon": "widget.png", - "screenshots": [{"url":"screenshot.png"}], - "type": "widget", - "tags": "tool,battery", - "supports": ["BANGLEJS"], - "dependencies": {"notify":"type"}, - "readme": "README.md", - "storage": [ - {"name":"widbatwarn.wid.js","url":"widget.js"}, - {"name":"widbatwarn.settings.js","url":"settings.js"} - ], - "data": [{"name":"widbatwarn.json"}] - }, - { - "id": "widbt", - "name": "Bluetooth Widget", - "version": "0.08", - "description": "Show the current Bluetooth connection status in the top right of the clock", - "icon": "widget.png", - "type": "widget", - "tags": "widget,bluetooth", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widbt.wid.js","url":"widget.js"} - ] - }, - { - "id": "widchime", - "name": "Hour Chime", - "version": "0.02", - "description": "Buzz or beep on every whole hour.", - "icon": "widget.png", - "type": "widget", - "tags": "widget", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widchime.wid.js","url":"widget.js"}, - {"name":"widchime.settings.js","url":"settings.js"} - ], - "data": [{"name":"widchime.json"}] - }, - { - "id": "widram", - "name": "RAM Widget", - "shortName": "RAM Widget", - "version": "0.01", - "description": "Display your Bangle's available RAM percentage in a widget", - "icon": "widget.png", - "type": "widget", - "tags": "widget", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widram.wid.js","url":"widget.js"} - ] - }, - { - "id": "hrm", - "name": "Heart Rate Monitor", - "version": "0.06", - "description": "Measure your heart rate and see live sensor data", - "icon": "heartrate.png", - "tags": "health", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"hrm.app.js","url":"heartrate.js"}, - {"name":"hrm.img","url":"heartrate-icon.js","evaluate":true} - ] - }, - { - "id": "widhrm", - "name": "Simple Heart Rate widget", - "version": "0.05", - "description": "When the screen is on, the widget turns on the heart rate monitor and displays the current heart rate (or last known in grey). For this to work well you'll need at least a 15 second LCD Timeout.", - "icon": "widget.png", - "type": "widget", - "tags": "health,widget", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widhrm.wid.js","url":"widget.js"} - ] - }, - { - "id": "bthrm", - "name": "Bluetooth Heart Rate Monitor", - "shortName": "BT HRM", - "version": "0.01", - "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", - "icon": "app.png", - "type": "boot", - "tags": "health,bluetooth", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"bthrm.boot.js","url":"boot.js"}, - {"name":"bthrm.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "stetho", - "name": "Stethoscope", - "version": "0.01", - "description": "Hear your heart rate", - "icon": "stetho.png", - "tags": "health", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"stetho.app.js","url":"stetho.js"}, - {"name":"stetho.img","url":"stetho-icon.js","evaluate":true} - ] - }, - { - "id": "swatch", - "name": "Stopwatch", - "version": "0.07", - "description": "Simple stopwatch with Lap Time logging to a JSON file", - "icon": "stopwatch.png", - "tags": "health", - "supports": ["BANGLEJS"], - "readme": "README.md", - "interface": "interface.html", - "allow_emulator": true, - "screenshots": [{"url":"bangle1-stopwatch-screenshot.png"}], - "storage": [ - {"name":"swatch.app.js","url":"stopwatch.js"}, - {"name":"swatch.img","url":"stopwatch-icon.js","evaluate":true} - ] - }, - { - "id": "hidmsic", - "name": "Bluetooth Music Controls", - "shortName": "Music Control", - "version": "0.02", - "description": "Enable HID in settings, pair with your phone, then use this app to control music from your watch!", - "icon": "hid-music.png", - "tags": "bluetooth", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"hidmsic.app.js","url":"hid-music.js"}, - {"name":"hidmsic.img","url":"hid-music-icon.js","evaluate":true} - ] - }, - { - "id": "hidkbd", - "name": "Bluetooth Keyboard", - "shortName": "Bluetooth Kbd", - "version": "0.02", - "description": "Enable HID in settings, pair with your phone/PC, then use this app to control other apps", - "icon": "hid-keyboard.png", - "tags": "bluetooth", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"hidkbd.app.js","url":"hid-keyboard.js"}, - {"name":"hidkbd.img","url":"hid-keyboard-icon.js","evaluate":true} - ] - }, - { - "id": "hidbkbd", - "name": "Binary Bluetooth Keyboard", - "shortName": "Binary BT Kbd", - "version": "0.02", - "description": "Enable HID in settings, pair with your phone/PC, then type messages using the onscreen keyboard by tapping repeatedly on the key you want", - "icon": "hid-binary-keyboard.png", - "tags": "bluetooth", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"hidbkbd.app.js","url":"hid-binary-keyboard.js"}, - {"name":"hidbkbd.img","url":"hid-binary-keyboard-icon.js","evaluate":true} - ] - }, - { - "id": "animals", - "name": "Animals Game", - "version": "0.01", - "description": "Simple toddler's game - displays a different number of animals each time the screen is pressed", - "icon": "animals.png", - "tags": "game", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"animals.app.js","url":"animals.js"}, - {"name":"animals.img","url":"animals-icon.js","evaluate":true}, - {"name":"animals-snake.img","url":"animals-snake.js","evaluate":true}, - {"name":"animals-duck.img","url":"animals-duck.js","evaluate":true}, - {"name":"animals-swan.img","url":"animals-swan.js","evaluate":true}, - {"name":"animals-fox.img","url":"animals-fox.js","evaluate":true}, - {"name":"animals-camel.img","url":"animals-camel.js","evaluate":true}, - {"name":"animals-pig.img","url":"animals-pig.js","evaluate":true}, - {"name":"animals-sheep.img","url":"animals-sheep.js","evaluate":true}, - {"name":"animals-mouse.img","url":"animals-mouse.js","evaluate":true} - ] - }, - { - "id": "qrcode", - "name": "Custom QR Code", - "version": "0.05", - "description": "Use this to upload a customised QR code to Bangle.js", - "icon": "app.png", - "tags": "qrcode", - "supports": ["BANGLEJS","BANGLEJS2"], - "custom": "custom.html", - "customConnect": true, - "storage": [ - {"name":"qrcode.app.js"}, - {"name":"qrcode.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "beer", - "name": "Beer Compass", - "version": "0.01", - "description": "Uploads all the pubs in an area onto your watch, so it can always point you at the nearest one", - "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], - "custom": "custom.html", - "storage": [ - {"name":"beer.app.js"}, - {"name":"beer.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "route", - "name": "Route Viewer", - "version": "0.02", - "description": "Upload a KML file of a route, and have your watch display a map with how far around it you are", - "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], - "custom": "custom.html", - "storage": [ - {"name":"route.app.js"}, - {"name":"route.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "ncstart", - "name": "NCEU Startup", - "version": "0.06", - "description": "NodeConfEU 2019 'First Start' Sequence", - "icon": "start.png", - "tags": "start,welcome", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"ncstart.app.js","url":"start.js"}, - {"name":"ncstart.boot.js","url":"boot.js"}, - {"name":"ncstart.settings.js","url":"settings.js"}, - {"name":"ncstart.img","url":"start-icon.js","evaluate":true}, - {"name":"nc-bangle.img","url":"start-bangle.js","evaluate":true}, - {"name":"nc-nceu.img","url":"start-nceu.js","evaluate":true}, - {"name":"nc-nfr.img","url":"start-nfr.js","evaluate":true}, - {"name":"nc-nodew.img","url":"start-nodew.js","evaluate":true}, - {"name":"nc-tf.img","url":"start-tf.js","evaluate":true} - ], - "data": [{"name":"ncstart.json"}] - }, - { - "id": "ncfrun", - "name": "NCEU 5K Fun Run", - "version": "0.01", - "description": "Display a map of the NodeConf EU 2019 5K Fun Run route and your location on it", - "icon": "nceu-funrun.png", - "tags": "health", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"ncfrun.app.js","url":"nceu-funrun.js"}, - {"name":"ncfrun.img","url":"nceu-funrun-icon.js","evaluate":true} - ] - }, - { - "id": "widnceu", - "name": "NCEU Logo Widget", - "version": "0.02", - "description": "Show the NodeConf EU logo in the top left", - "icon": "widget.png", - "type": "widget", - "tags": "widget", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"widnceu.wid.js","url":"widget.js"} - ] - }, - { - "id": "sclock", - "name": "Simple Clock", - "version": "0.07", - "description": "A Simple Digital Clock", - "icon": "clock-simple.png", - "screenshots": [{"url":"screenshot_simplec.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"sclock.app.js","url":"clock-simple.js"}, - {"name":"sclock.img","url":"clock-simple-icon.js","evaluate":true} - ] - }, - { - "id": "s7clk", - "name": "Simple 7 segment Clock", - "version": "0.03", - "description": "A simple 7 segment Clock with date", - "icon": "icon.png", - "screenshots": [{"url":"screenshot_s7segment.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"s7clk.app.js","url":"app.js"}, - {"name":"s7clk.img","url":"icon.js","evaluate":true} - ] - }, - { - "id": "vibrclock", - "name": "Vibrate Clock", - "version": "0.03", - "description": "When BTN1 is pressed, vibrate out the time as a series of buzzes, one digit at a time. Hours, then Minutes. Zero is signified by one long buzz. Otherwise a simple digital clock.", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "allow_emulator": true, - "screenshots": [{"url":"bangle1-vibrate-clock-screenshot.png"}], - "storage": [ - {"name":"vibrclock.app.js","url":"app.js"}, - {"name":"vibrclock.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "svclock", - "name": "Simple V-Clock", - "version": "0.04", - "description": "Modification of Simple Clock 0.04 to use Vectorfont", - "icon": "vclock-simple.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "screenshots": [{"url":"bangle2-simple-v-clock-screenshot.png"}], - "storage": [ - {"name":"svclock.app.js","url":"vclock-simple.js"}, - {"name":"svclock.img","url":"vclock-simple-icon.js","evaluate":true} - ] - }, - { - "id": "dclock", - "name": "Dev Clock", - "version": "0.10", - "description": "A Digital Clock including timestamp (tst), beats(@), days in current month (dm) and days since new moon (l)", - "icon": "clock-dev.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "screenshots": [{"url":"bangle2-dev-clock-screenshot.png"},{"url":"bangle1-dev-clock-screenshot.png"}], - "storage": [ - {"name":"dclock.app.js","url":"clock-dev.js"}, - {"name":"dclock.img","url":"clock-dev-icon.js","evaluate":true} - ] - }, - { - "id": "gesture", - "name": "Gesture Test", - "version": "0.01", - "description": "BETA! Uploads a basic Tensorflow Gesture model, and then outputs each gesture as a message", - "icon": "gesture.png", - "type": "app", - "tags": "gesture,ai", - "supports": ["BANGLEJS", "BANGLEJS2"], - "storage": [ - {"name":"gesture.app.js","url":"gesture.js"}, - {"name":".tfnames","url":"gesture-tfnames.js","evaluate":true}, - {"name":".tfmodel","url":"gesture-tfmodel.js","evaluate":true}, - {"name":"gesture.img","url":"gesture-icon.js","evaluate":true} - ] - }, - { - "id": "pparrot", - "name": "Party Parrot", - "version": "0.01", - "description": "Party with a parrot on your wrist", - "icon": "party-parrot.png", - "type": "app", - "tags": "party,parrot,lol", - "supports": ["BANGLEJS"], - "allow_emulator": true, - "screenshots": [{"url":"bangle1-party-parrot-screenshot.png"}], - "storage": [ - {"name":"pparrot.app.js","url":"party-parrot.js"}, - {"name":"pparrot.img","url":"party-parrot-icon.js","evaluate":true} - ] - }, - { - "id": "hrings", - "name": "Hypno Rings", - "version": "0.01", - "description": "Experiment with trippy rings, press buttons for change", - "icon": "hypno-rings.png", - "type": "app", - "tags": "rings,hypnosis,psychadelic", - "supports": ["BANGLEJS"], - "allow_emulator": true, - "screenshots": [{"url":"bangle1-hypno-rings-screenshot.png"}], - "storage": [ - {"name":"hrings.app.js","url":"hypno-rings.js"}, - {"name":"hrings.img","url":"hypno-rings-icon.js","evaluate":true} - ] - }, - { - "id": "morse", - "name": "Morse Code", - "version": "0.01", - "description": "Learn morse code by hearing/seeing/feeling the code. Tap to toggle buzz!", - "icon": "morse-code.png", - "type": "app", - "tags": "morse,sound,visual,input", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"morse.app.js","url":"morse-code.js"}, - {"name":"morse.img","url":"morse-code-icon.js","evaluate":true} - ] - }, - { - "id": "blescan", - "name": "BLE Scanner", - "version": "0.01", - "description": "Scan for advertising BLE devices", - "icon": "blescan.png", - "tags": "bluetooth", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"blescan.app.js","url":"blescan.js"}, - {"name":"blescan.img","url":"blescan-icon.js","evaluate":true} - ] - }, - { - "id": "mmonday", - "name": "Manic Monday Tone", - "version": "0.02", - "description": "The Bangles make a comeback", - "icon": "manic-monday-icon.png", - "tags": "sound", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"mmonday.app.js","url":"manic-monday.js"}, - {"name":"mmonday.img","url":"manic-monday-icon.js","evaluate":true} - ] - }, - { - "id": "jbells", - "name": "Jingle Bells", - "version": "0.01", - "description": "Play Jingle Bells", - "icon": "jbells.png", - "type": "app", - "tags": "sound", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"jbells.app.js","url":"jbells.js"}, - {"name":"jbells.img","url":"jbells-icon.js","evaluate":true} - ] - }, - { - "id": "scolor", - "name": "Show Color", - "version": "0.01", - "description": "Display all available Colors and Names", - "icon": "show-color.png", - "type": "app", - "tags": "tool", - "screenshots": [{"url":"bangle1-view-color-screenshot.png"}], - "supports": ["BANGLEJS"], - "allow_emulator": true, - "storage": [ - {"name":"scolor.app.js","url":"show-color.js"}, - {"name":"scolor.img","url":"show-color-icon.js","evaluate":true} - ] - }, - { - "id": "miclock", - "name": "Mixed Clock", - "version": "0.05", - "description": "A mix of analog and digital Clock", - "icon": "clock-mixed.png", - "type": "clock", - "tags": "clock", - "screenshots": [{"url":"bangle1-mixed-clock-screenshot.png"}], - "supports": ["BANGLEJS"], - "allow_emulator": true, - "storage": [ - {"name":"miclock.app.js","url":"clock-mixed.js"}, - {"name":"miclock.img","url":"clock-mixed-icon.js","evaluate":true} - ] - }, - { - "id": "bclock", - "name": "Binary Clock", - "version": "0.03", - "description": "A simple binary clock watch face", - "icon": "clock-binary.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "allow_emulator": true, - "screenshots": [{"url":"bangle1-binary-clock-screenshot.png"}], - "storage": [ - {"name":"bclock.app.js","url":"clock-binary.js"}, - {"name":"bclock.img","url":"clock-binary-icon.js","evaluate":true} - ] - }, - { - "id": "clotris", - "name": "Clock-Tris", - "version": "0.01", - "description": "A fully functional clone of a classic game of falling blocks", - "icon": "clock-tris.png", - "tags": "game", - "supports": ["BANGLEJS"], - "screenshots": [{"url":"bangle1-clock-tris-screenshot.png"}], - "allow_emulator": true, - "storage": [ - {"name":"clotris.app.js","url":"clock-tris.js"}, - {"name":"clotris.img","url":"clock-tris-icon.js","evaluate":true}, - {"name":".trishig","url":"clock-tris-high"} - ] - }, - { - "id": "flappy", - "name": "Flappy Bird", - "version": "0.05", - "description": "A Flappy Bird game clone", - "icon": "app.png", - "screenshots": [{"url":"screenshot1_flappy.png"},{"url":"screenshot2_flappy.png"}], - "tags": "game", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"flappy.app.js","url":"app.js"}, - {"name":"flappy.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "gpsinfo", - "name": "GPS Info", - "version": "0.06", - "description": "An application that displays information about altitude, lat/lon, satellites and time", - "icon": "gps-info.png", - "type": "app", - "tags": "gps", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"gpsinfo.app.js","url":"gps-info.js"}, - {"name":"gpsinfo.img","url":"gps-info-icon.js","evaluate":true} - ] - }, - { - "id": "assistedgps", - "name": "Assisted GPS Update (AGPS)", - "version": "0.01", - "description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.", - "icon": "app.png", - "type": "RAM", - "tags": "tool,outdoors,agps", - "supports": ["BANGLEJS"], - "custom": "custom.html", - "storage": [] - }, - { - "id": "pomodo", - "name": "Pomodoro", - "version": "0.02", - "description": "A simple pomodoro timer.", - "icon": "pomodoro.png", - "type": "app", - "tags": "pomodoro,cooking,tools", - "supports": ["BANGLEJS", "BANGLEJS2"], - "allow_emulator": true, - "screenshots": [{"url":"bangle2-pomodoro-screenshot.png"}], - "storage": [ - {"name":"pomodo.app.js","url":"pomodoro.js"}, - {"name":"pomodo.img","url":"pomodoro-icon.js","evaluate":true} - ] - }, - { - "id": "blobclk", - "name": "Large Digit Blob Clock", - "shortName": "Blob Clock", - "version": "0.06", - "description": "A clock with big digits", - "icon": "clock-blob.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "screenshots": [{"url":"bangle2-large-digit-blob-clock-screenshot.png"},{"url":"bangle1-large-digit-blob-clock-screenshot.png"}], - "storage": [ - {"name":"blobclk.app.js","url":"clock-blob.js"}, - {"name":"blobclk.img","url":"clock-blob-icon.js","evaluate":true} - ] - }, - { - "id": "boldclk", - "name": "Bold Clock", - "version": "0.05", - "description": "Simple, readable and practical clock", - "icon": "bold_clock.png", - "screenshots": [{"url":"screenshot_bold.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"boldclk.app.js","url":"bold_clock.js"}, - {"name":"boldclk.img","url":"bold_clock-icon.js","evaluate":true} - ] - }, - { - "id": "widclk", - "name": "Digital clock widget", - "version": "0.06", - "description": "A simple digital clock widget", - "icon": "widget.png", - "type": "widget", - "tags": "widget,clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widclk.wid.js","url":"widget.js"} - ] - }, - { - "id": "widpedom", - "name": "Pedometer widget", - "version": "0.20", - "description": "Daily pedometer widget", - "icon": "widget.png", - "type": "widget", - "tags": "widget", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widpedom.wid.js","url":"widget.js"}, - {"name":"widpedom.settings.js","url":"settings.js"} - ] - }, - { - "id": "berlinc", - "name": "Berlin Clock", - "version": "0.05", - "description": "Berlin Clock (see https://en.wikipedia.org/wiki/Mengenlehreuhr)", - "icon": "berlin-clock.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "screenshots": [{"url":"berlin-clock-screenshot.png"}], - "storage": [ - {"name":"berlinc.app.js","url":"berlin-clock.js"}, - {"name":"berlinc.img","url":"berlin-clock-icon.js","evaluate":true} - ] - }, - { - "id": "ctrclk", - "name": "Centerclock", - "version": "0.03", - "description": "Watch-centered digital 24h clock with date in dd.mm.yyyy format.", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "screenshots": [{"url":"bangle1-center-clock-screenshot.png"}], - "allow_emulator": true, - "storage": [ - {"name":"ctrclk.app.js","url":"app.js"}, - {"name":"ctrclk.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "demoapp", - "name": "Demo Loop", - "version": "0.02", - "description": "Simple demo app - displays Bangle.js, JS logo, graphics, and Bangle.js information", - "icon": "app.png", - "type": "app", - "tags": "", - "screenshots": [{"url":"bangle1-demo-loop-screenshot1.png"},{"url":"bangle1-demo-loop-screenshot2.png"},{"url":"bangle1-demo-loop-screenshot3.png"},{"url":"bangle1-demo-loop-screenshot4.png"}], - "supports": ["BANGLEJS"], - "allow_emulator": true, - "storage": [ - {"name":"demoapp.app.js","url":"app.js"}, - {"name":"demoapp.img","url":"app-icon.js","evaluate":true} - ], - "sortorder": -9 - }, - { - "id": "flagrse", - "name": "Espruino Flag Raiser", - "version": "0.01", - "description": "App to send a command to another Espruino to cause it to raise a flag", - "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"flagrse.app.js","url":"app.js"}, - {"name":"flagrse.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "pipboy", - "name": "Pipboy", - "version": "0.04", - "description": "Pipboy themed clock", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "allow_emulator": true, - "screenshots": [{"url":"bangle1-pipboy-themed-clock-screenshot.png"}], - "storage": [ - {"name":"pipboy.app.js","url":"app.js"}, - {"name":"pipboy.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "torch", - "name": "Torch", - "shortName": "Torch", - "version": "0.02", - "description": "Turns screen white to help you see in the dark. Select from the launcher or press BTN1,BTN3,BTN1,BTN3 quickly to start when in any app that shows widgets", - "icon": "app.png", - "tags": "tool,torch", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"torch.app.js","url":"app.js"}, - {"name":"torch.wid.js","url":"widget.js"}, - {"name":"torch.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "rtorch", - "name": "Red Torch", - "shortName": "RedTorch", - "version": "0.02", - "description": "Turns screen RED to help you see in the dark without breaking your night vision. Select from the launcher or on Bangle 1 press BTN3,BTN1,BTN3,BTN1 quickly to start when in any app that shows widgets", - "icon": "app.png", - "tags": "tool,torch", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"rtorch.app.js","url":"app.js"}, - {"name":"rtorch.wid.js","url":"widget.js", "supports": ["BANGLEJS"]}, - {"name":"rtorch.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "wohrm", - "name": "Workout HRM", - "version": "0.08", - "description": "Workout heart rate monitor notifies you with a buzz if your heart rate goes above or below the set limits.", - "icon": "app.png", - "type": "app", - "tags": "hrm,workout", - "supports": ["BANGLEJS"], - "readme": "README.md", - "allow_emulator": true, - "screenshots": [{"url":"bangle1-workout-HRM-screenshot.png"}], - "storage": [ - {"name":"wohrm.app.js","url":"app.js"}, - {"name":"wohrm.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "widid", - "name": "Bluetooth ID Widget", - "version": "0.03", - "description": "Display the last two tuple of your Bangle.js MAC address in the widget section. This is useful for figuring out which Bangle.js to connect to if you have more than one Bangle.js!", - "icon": "widget.png", - "type": "widget", - "tags": "widget,address,mac", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widid.wid.js","url":"widget.js"} - ] - }, - { - "id": "grocery", - "name": "Grocery", - "version": "0.02", - "description": "Simple grocery (shopping) list - Display a list of product and track if you already put them in your cart.", - "icon": "grocery.png", - "type": "app", - "tags": "tool,outdoors,shopping,list", - "supports": ["BANGLEJS", "BANGLEJS2"], - "custom": "grocery.html", - "allow_emulator": true, - "storage": [ - {"name":"grocery.app.js","url":"app.js"}, - {"name":"grocery.img","url":"grocery-icon.js","evaluate":true} - ] - }, - { - "id": "marioclock", - "name": "Mario Clock", - "version": "0.15", - "description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.", - "icon": "marioclock.png", - "type": "clock", - "tags": "clock,mario,retro", - "supports": ["BANGLEJS"], - "readme": "README.md", - "allow_emulator": false, - "screenshots": [{"url":"bangle1-mario-clock-screenshot.png"}], - "storage": [ - {"name":"marioclock.app.js","url":"marioclock-app.js"}, - {"name":"marioclock.img","url":"marioclock-icon.js","evaluate":true} - ] - }, - { - "id": "cliock", - "name": "Commandline-Clock", - "shortName": "CLI-Clock", - "version": "0.15", - "description": "Simple CLI-Styled Clock", - "icon": "app.png", - "screenshots": [{"url":"screenshot_cli.png"}], - "type": "clock", - "tags": "clock,cli,command,bash,shell", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"cliock.app.js","url":"app.js"}, - {"name":"cliock.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "widver", - "name": "Firmware Version Widget", - "version": "0.03", - "description": "Display the version of the installed firmware in the top widget section.", - "icon": "widget.png", - "type": "widget", - "tags": "widget,tool,system", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widver.wid.js","url":"widget.js"} - ] - }, - { - "id": "barclock", - "name": "Bar Clock", - "version": "0.09", - "description": "A simple digital clock showing seconds as a bar", - "icon": "clock-bar.png", - "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"barclock.app.js","url":"clock-bar.js"}, - {"name":"barclock.img","url":"clock-bar-icon.js","evaluate":true} - ] - }, - { - "id": "dotclock", - "name": "Dot Clock", - "version": "0.03", - "description": "A Minimal Dot Analog Clock", - "icon": "clock-dot.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "screenshots": [{"url":"bangle2-dot-clcok-screenshot.png"},{"url":"bangle1-dot-clock-screenshot.png"}], - "storage": [ - {"name":"dotclock.app.js","url":"clock-dot.js"}, - {"name":"dotclock.img","url":"clock-dot-icon.js","evaluate":true} - ] - }, - { - "id": "widtbat", - "name": "Tiny Battery Widget", - "version": "0.02", - "description": "Tiny blueish battery widget, vibs and changes level color when charging", - "icon": "widget.png", - "type": "widget", - "tags": "widget,tool,system", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widtbat.wid.js","url":"widget.js"} - ] - }, - { - "id": "chrono", - "name": "Chrono", - "shortName": "Chrono", - "version": "0.01", - "description": "Single click BTN1 to add 5 minutes. Single click BTN2 to add 30 seconds. Single click BTN3 to add 5 seconds. Tap to pause or play to timer. Double click BTN1 to reset. When timer finishes the watch vibrates.", - "icon": "chrono.png", - "tags": "tool", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"chrono.app.js","url":"chrono.js"}, - {"name":"chrono.img","url":"chrono-icon.js","evaluate":true} - ] - }, - { - "id": "astrocalc", - "name": "Astrocalc", - "version": "0.02", - "description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.", - "icon": "astrocalc.png", - "tags": "app,sun,moon,cycles,tool,outdoors", - "supports": ["BANGLEJS"], - "allow_emulator": true, - "storage": [ - {"name":"astrocalc.app.js","url":"astrocalc-app.js"}, - {"name":"suncalc.js","url":"suncalc.js"}, - {"name":"astrocalc.img","url":"astrocalc-icon.js","evaluate":true}, - {"name":"first-quarter.img","url":"first-quarter-icon.js","evaluate":true}, - {"name":"last-quarter.img","url":"last-quarter-icon.js","evaluate":true}, - {"name":"waning-crescent.img","url":"waning-crescent-icon.js","evaluate":true}, - {"name":"waning-gibbous.img","url":"waning-gibbous-icon.js","evaluate":true}, - {"name":"full.img","url":"full-icon.js","evaluate":true}, - {"name":"new.img","url":"new-icon.js","evaluate":true}, - {"name":"waxing-gibbous.img","url":"waxing-gibbous-icon.js","evaluate":true}, - {"name":"waxing-crescent.img","url":"waxing-crescent-icon.js","evaluate":true} - ] - }, - { - "id": "widhwt", - "name": "Hand Wash Timer", - "version": "0.01", - "description": "Swipe your wrist over the watch face to start your personal Bangle.js hand wash timer for 35 sec. Start washing after the short buzz and stop after the long buzz.", - "icon": "widget.png", - "type": "widget", - "tags": "widget,tool", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"widhwt.wid.js","url":"widget.js"} - ] - }, - { - "id": "toucher", - "name": "Touch Launcher", - "shortName": "Toucher", - "version": "0.07", - "description": "Touch enable left to right launcher.", - "icon": "app.png", - "type": "launch", - "tags": "tool,system,launcher", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"toucher.app.js","url":"app.js"}, - {"name":"toucher.settings.js","url":"settings.js"} - ], - "data": [{"name":"toucher.json"}] - }, - { - "id": "balltastic", - "name": "Balltastic", - "version": "0.02", - "description": "Simple but fun ball eats dots game.", - "icon": "app.png", - "type": "app", - "tags": "game,fun", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"balltastic.app.js","url":"app.js"}, - {"name":"balltastic.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "rpgdice", - "name": "RPG dice", - "version": "0.02", - "description": "Simple RPG dice rolling app.", - "icon": "rpgdice.png", - "type": "app", - "tags": "game,fun", - "supports": ["BANGLEJS"], - "allow_emulator": true, - "screenshots": [{"url":"bangle1-rpg-dice-screenshot.png"}], - "storage": [ - {"name":"rpgdice.app.js","url":"app.js"}, - {"name":"rpgdice.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "widmp", - "name": "Moon Phase Widget", - "version": "0.02", - "description": "Display the current moon phase in blueish for the northern hemisphere in eight phases", - "icon": "widget.png", - "type": "widget", - "tags": "widget,tools", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"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", - "version": "0.05", - "description": "Minion themed clock.", - "icon": "minionclk.png", - "type": "clock", - "tags": "clock,minion", - "supports": ["BANGLEJS"], - "allow_emulator": true, - "screenshots": [{"url":"bangle1-minion-clock-screenshot.png"}], - "storage": [ - {"name":"minionclk.app.js","url":"app.js"}, - {"name":"minionclk.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "openstmap", - "name": "OpenStreetMap", - "shortName": "OpenStMap", - "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,osm", - "supports": ["BANGLEJS","BANGLEJS2"], - "screenshots": [{"url":"screenshot.png"}], - "custom": "custom.html", - "customConnect": true, - "storage": [ - {"name":"openstmap","url":"openstmap.js"}, - {"name":"openstmap.app.js","url":"app.js"}, - {"name":"openstmap.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "activepedom", - "name": "Active Pedometer", - "shortName": "Active Pedometer", - "version": "0.09", - "description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.", - "icon": "app.png", - "tags": "outdoors,widget", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"activepedom.wid.js","url":"widget.js"}, - {"name":"activepedom.settings.js","url":"settings.js"}, - {"name":"activepedom.img","url":"app-icon.js","evaluate":true}, - {"name":"activepedom.app.js","url":"app.js"} - ] - }, - { - "id": "chronowid", - "name": "Chrono Widget", - "shortName": "Chrono Widget", - "version": "0.05", - "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"}, - {"name":"chronowid.app.js","url":"app.js"}, - {"name":"chronowid.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "tabata", - "name": "Tabata", - "shortName": "Tabata - Control High-Intensity Interval Training", - "version": "0.01", - "description": "Control high-intensity interval training (according to tabata: https://en.wikipedia.org/wiki/Tabata_method).", - "icon": "tabata.png", - "tags": "workout,health", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"tabata.app.js","url":"tabata.js"}, - {"name":"tabata.img","url":"tabata-icon.js","evaluate":true} - ] - }, - { - "id": "custom", - "name": "Custom Boot Code ", - "version": "0.01", - "description": "Add code you want to run at boot time", - "icon": "custom.png", - "type": "bootloader", - "tags": "tool,system", - "supports": ["BANGLEJS","BANGLEJS2"], - "custom": "custom.html", - "storage": [ - {"name":"custom"} - ] - }, - { - "id": "devstopwatch", - "name": "Dev Stopwatch", - "shortName": "Dev Stopwatch", - "version": "0.03", - "description": "Stopwatch with 5 laps supported (cyclically replaced)", - "icon": "app.png", - "tags": "stopwatch,chrono,timer,chronometer", - "supports": ["BANGLEJS","BANGLEJS2"], - "screenshots": [{"url":"bangle1-dev-stopwatch-screenshot.png"}], - "allow_emulator": true, - "storage": [ - {"name":"devstopwatch.app.js","url":"app.js"}, - {"name":"devstopwatch.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "batchart", - "name": "Battery Chart", - "shortName": "Battery Chart", - "version": "0.10", - "description": "A widget and an app for recording and visualizing battery percentage over time.", - "icon": "app.png", - "tags": "app,widget,battery,time,record,chart,tool", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"batchart.wid.js","url":"widget.js"}, - {"name":"batchart.app.js","url":"app.js"}, - {"name":"batchart.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "nato", - "name": "NATO Alphabet", - "shortName": "NATOAlphabet", - "version": "0.01", - "description": "Learn the NATO Phonetic alphabet plus some numbers.", - "icon": "nato.png", - "type": "app", - "tags": "app,learn,visual", - "supports": ["BANGLEJS"], - "allow_emulator": true, - "screenshots": [{"url":"bangle1-NATO-alphabet-screenshot.png"},{"url":"bangle1-NATO-alphabet-screenshot2.png"}], - "storage": [ - {"name":"nato.app.js","url":"nato.js"}, - {"name":"nato.img","url":"nato-icon.js","evaluate":true} - ] - }, - { - "id": "numerals", - "name": "Numerals Clock", - "shortName": "Numerals Clock", - "version": "0.10", - "description": "A simple big numerals clock", - "icon": "numerals.png", - "type": "clock", - "tags": "numerals,clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "screenshots": [{"url":"bangle1-numerals-screenshot.png"}], - "storage": [ - {"name":"numerals.app.js","url":"numerals.app.js"}, - {"name":"numerals.img","url":"numerals-icon.js","evaluate":true}, - {"name":"numerals.settings.js","url":"numerals.settings.js"} - ], - "data": [{"name":"numerals.json"}] - }, - { - "id": "bledetect", - "name": "BLE Detector", - "shortName": "BLE Detector", - "version": "0.03", - "description": "Detect BLE devices and show some informations.", - "icon": "bledetect.png", - "tags": "app,bluetooth,tool", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"bledetect.app.js","url":"bledetect.js"}, - {"name":"bledetect.img","url":"bledetect-icon.js","evaluate":true} - ] - }, - { - "id": "snake", - "name": "Snake", - "shortName": "Snake", - "version": "0.02", - "description": "The classic snake game. Eat apples and don't bite your tail.", - "icon": "snake.png", - "tags": "game,fun", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"snake.app.js","url":"snake.js"}, - {"name":"snake.img","url":"snake-icon.js","evaluate":true} - ] - }, - { "id": "snek", - "name": "The snek game", - "shortName":"Snek", - "version": "0.02", - "description": "A snek game where you control a snek to eat all the apples!", - "screenshots": [{"url":"screenshot_snek.png"}], - "icon": "snek.png", - "supports": ["BANGLEJS2"], - "tags": "game,fun", - "storage": [ - {"name":"snek.app.js","url":"snek.js"}, - {"name":"snek.img","url":"snek.icon.js","evaluate":true} - ] - }, - { - "id": "calculator", - "name": "Calculator", - "shortName": "Calculator", - "version": "0.05", - "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.", - "icon": "calculator.png", - "screenshots": [{"url":"screenshot_calculator.png"}], - "tags": "app,tool", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"calculator.app.js","url":"app.js"}, - {"name":"calculator.img","url":"calculator-icon.js","evaluate":true} - ] - }, - { - "id": "dane", - "name": "Digital Assistant, not EDITH", - "shortName": "DANE", - "version": "0.16", - "description": "A Watchface inspired by Tony Stark's EDITH and based on https://arwes.dev/", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "allow_emulator": true, - "storage": [ - {"name":"dane.app.js","url":"app.js"}, - {"name":"dane.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "dane_tcr", - "name": "DANE Touch Launcher", - "shortName": "DANE Toucher", - "version": "0.07", - "description": "Touch enable left to right launcher in the style of the DANE Watchface", - "icon": "app.png", - "type": "launch", - "tags": "tool,system,launcher", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"dane_tcr.app.js","url":"app.js"}, - {"name":"dane_tcr.settings.js","url":"settings.js"} - ], - "data": [{"name":"dane_tcr.json"}] - }, - { - "id": "buffgym", - "name": "BuffGym", - "version": "0.02", - "description": "BuffGym is the famous 5x5 workout program for the BangleJS", - "icon": "buffgym.png", - "type": "app", - "tags": "tool,outdoors,gym,exercise", - "supports": ["BANGLEJS"], - "readme": "README.md", - "interface": "buffgym.html", - "allow_emulator": false, - "storage": [ - {"name":"buffgym.app.js","url":"buffgym.app.js"}, - {"name":"buffgym-set.js","url":"buffgym-set.js"}, - {"name":"buffgym-exercise.js","url":"buffgym-exercise.js"}, - {"name":"buffgym-workout.js","url":"buffgym-workout.js"}, - {"name":"buffgym-workout-a.json","url":"buffgym-workout-a.json"}, - {"name":"buffgym-workout-b.json","url":"buffgym-workout-b.json"}, - {"name":"buffgym-workout-index.json","url":"buffgym-workout-index.json"}, - {"name":"buffgym.img","url":"buffgym-icon.js","evaluate":true} - ] - }, - { - "id": "banglerun", - "name": "BangleRun", - "shortName": "BangleRun", - "version": "0.10", - "description": "An app for running sessions. Displays info and logs your run for later viewing.", - "icon": "banglerun.png", - "tags": "run,running,fitness,outdoors", - "supports": ["BANGLEJS"], - "interface": "interface.html", - "allow_emulator": false, - "storage": [ - {"name":"banglerun.app.js","url":"app.js"}, - {"name":"banglerun.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "metronome", - "name": "Metronome", - "version": "0.07", - "readme": "README.md", - "description": "Makes the watch blinking and vibrating with a given rate", - "icon": "metronome_icon.png", - "tags": "tool", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "screenshots": [{"url":"bangle1-metronome-screenshot.png"}], - "storage": [ - {"name":"metronome.app.js","url":"metronome.js"}, - {"name":"metronome.img","url":"metronome-icon.js","evaluate":true}, - {"name":"metronome.settings.js","url":"settings.js"} - ] - }, - { - "id": "blackjack", - "name": "Black Jack game", - "shortName": "Black Jack game", - "version": "0.02", - "description": "Simple implementation of card game Black Jack", - "icon": "blackjack.png", - "tags": "game", - "supports": ["BANGLEJS"], - "screenshots": [{"url":"bangle1-black-jack-game-screenshot.png"}], - "allow_emulator": true, - "storage": [ - {"name":"blackjack.app.js","url":"blackjack.app.js"}, - {"name":"blackjack.img","url":"blackjack-icon.js","evaluate":true} - ] - }, - { - "id": "hidcam", - "name": "Camera shutter", - "shortName": "Cam shutter", - "version": "0.03", - "description": "Enable HID, connect to your phone, start your camera and trigger the shot on your Bangle", - "icon": "app.png", - "tags": "bluetooth,tool", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"hidcam.app.js","url":"app.js"}, - {"name":"hidcam.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "swlclk", - "name": "SWL Clock / Short Wave Listner Clock", - "shortName": "SWL Clock", - "version": "0.02", - "description": "Display Local, UTC time and some programs on the shorts waves along the day, with the frequencies", - "icon": "swlclk.png", - "type": "clock", - "tags": "tool,clock", - "supports": ["BANGLEJS"], - "readme": "README.md", - "allow_emulator": true, - "screenshots": [{"url":"bangle1-SWL-clock-screenshot.png"}], - "storage": [ - {"name":"swlclk.app.js","url":"app.js"}, - {"name":"swlclk.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "rclock", - "name": "Round clock with seconds, minutes and date", - "shortName": "Round Clock", - "version": "0.06", - "description": "Designed round clock with ticks for minutes and seconds and heart rate indication", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"rclock.app.js","url":"rclock.app.js"}, - {"name":"rclock.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "fclock", - "name": "fclock", - "shortName": "F Clock", - "version": "0.02", - "description": "Simple design of a digital clock", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"fclock.app.js","url":"fclock.app.js"}, - {"name":"fclock.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "hamloc", - "name": "QTH Locator / Maidenhead Locator System", - "shortName": "QTH Locator", - "version": "0.01", - "description": "Convert your current GPS location to the Maidenhead locator system used by HAM amateur radio operators", - "icon": "app.png", - "tags": "tool,outdoors,gps", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"hamloc.app.js","url":"app.js"}, - {"name":"hamloc.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "osmpoi", - "name": "POI Compass", - "version": "0.03", - "description": "Uploads all the points of interest in an area onto your watch, same as Beer Compass with more p.o.i.", - "icon": "app.png", - "tags": "tool,outdoors,gps", - "supports": ["BANGLEJS"], - "readme": "README.md", - "custom": "custom.html", - "storage": [ - {"name":"osmpoi.app.js"}, - {"name":"osmpoi.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "pong", - "name": "Pong", - "shortName": "Pong", - "version": "0.03", - "description": "A clone of the Atari game Pong", - "icon": "pong.png", - "type": "app", - "tags": "game", - "supports": ["BANGLEJS"], - "readme": "README.md", - "allow_emulator": true, - "screenshots": [{"url":"bangle1-pong-screenshot.png"}], - "storage": [ - {"name":"pong.app.js","url":"app.js"}, - {"name":"pong.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "ballmaze", - "name": "Ball Maze", - "version": "0.02", - "description": "Navigate a ball through a maze by tilting your watch.", - "icon": "icon.png", - "type": "app", - "tags": "game", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"ballmaze.app.js","url":"app.js"}, - {"name":"ballmaze.img","url":"icon.js","evaluate":true} - ], - "data": [{"name":"ballmaze.json"}] - }, - { - "id": "calendar", - "name": "Calendar", - "version": "0.06", - "description": "Simple calendar", - "icon": "calendar.png", - "screenshots": [{"url":"screenshot_calendar.png"}], - "tags": "calendar", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "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", - "name": "Bluetooth Joystick", - "shortName": "Joystick", - "version": "0.01", - "description": "Emulates a 2 axis/5 button Joystick using the accelerometer as stick input and buttons 1-3, touch left as button 4 and touch right as button 5.", - "icon": "app.png", - "tags": "bluetooth", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"hidjoystick.app.js","url":"app.js"}, - {"name":"hidjoystick.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "largeclock", - "name": "Large Clock", - "version": "0.10", - "description": "A readable and informational digital watch, with date, seconds and moon phase", - "icon": "largeclock.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "readme": "README.md", - "allow_emulator": true, - "screenshots": [{"url":"bangle1-large-clock-screenshot.png"}], - "storage": [ - {"name":"largeclock.app.js","url":"largeclock.js"}, - {"name":"largeclock.img","url":"largeclock-icon.js","evaluate":true}, - {"name":"largeclock.settings.js","url":"settings.js"} - ], - "data": [{"name":"largeclock.json"}] - }, - { - "id": "smtswch", - "name": "Smart Switch", - "shortName": "Smart Switch", - "version": "0.01", - "description": "Using EspruinoHub, control your smart devices on and off via Bluetooth Low Energy!", - "icon": "app.png", - "type": "app", - "tags": "bluetooth,btle,smart,switch", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"smtswch.app.js","url":"app.js"}, - {"name":"smtswch.img","url":"app-icon.js","evaluate":true}, - {"name":"light-on.img","url":"light-on.js","evaluate":true}, - {"name":"light-off.img","url":"light-off.js","evaluate":true}, - {"name":"switch-on.img","url":"switch-on.js","evaluate":true}, - {"name":"switch-off.img","url":"switch-off.js","evaluate":true} - ] - }, - { - "id": "miplant", - "name": "Xiaomi Plant Sensor", - "shortName": "Mi Plant", - "version": "0.02", - "description": "Reads and displays data from Xiaomi bluetooth plant moisture sensors", - "icon": "app.png", - "tags": "xiaomi,mi,plant,ble,bluetooth", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"miplant.app.js","url":"app.js"}, - {"name":"miplant.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "simpletimer", - "name": "Timer", - "version": "0.07", - "description": "Simple timer, useful when playing board games or cooking", - "icon": "app.png", - "tags": "timer", - "supports": ["BANGLEJS"], - "readme": "README.md", - "allow_emulator": true, - "screenshots": [{"url":"bangle1-timer-screenshot.png"}], - "storage": [ - {"name":"simpletimer.app.js","url":"app.js"}, - {"name":".tfnames","url":"gesture-tfnames.js","evaluate":true}, - {"name":".tfmodel","url":"gesture-tfmodel.js","evaluate":true}, - {"name":"simpletimer.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"name":"simpletimer.json"}] - }, - { - "id": "beebclock", - "name": "Beeb Clock", - "version": "0.05", - "description": "Clock face that may be coincidentally familiar to BBC viewers", - "icon": "beebclock.png", - "type": "clock", - "tags": "clock", - "screenshots": [{"url":"bangle1-beeb-clock-screenshot.png"}], - "supports": ["BANGLEJS"], - "allow_emulator": true, - "storage": [ - {"name":"beebclock.app.js","url":"beebclock.js"}, - {"name":"beebclock.img","url":"beebclock-icon.js","evaluate":true} - ] - }, - { - "id": "findphone", - "name": "Find Phone", - "shortName": "Find Phone", - "version": "0.03", - "description": "Find your phone via Gadgetbridge. Click any button to let your phone ring. 📳 Note: The functionality is available even without this app, just go to Settings, App Settings, Gadgetbridge, Find Phone.", - "icon": "app.png", - "tags": "tool,android", - "supports": ["BANGLEJS"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"findphone.app.js","url":"app.js"}, - {"name":"findphone.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "getup", - "name": "Get Up", - "shortName": "Get Up", - "version": "0.01", - "description": "Reminds you to getup every x minutes. Sitting to long is dangerous!", - "icon": "app.png", - "tags": "tools,health", - "supports": ["BANGLEJS"], - "readme": "README.md", - "screenshots": [{"url":"bangle1-get-up-screenshot.png"}], - "allow_emulator": true, - "storage": [ - {"name":"getup.app.js","url":"app.js"}, - {"name":"getup.settings.js","url":"settings.js"}, - {"name":"getup.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "gallifr", - "name": "Time Traveller's Chronometer", - "shortName": "Time Travel Clock", - "version": "0.02", - "description": "A clock for time travellers. The light pie segment shows the minutes, the black circle, the hour. The dial itself reads 'time' just in case you forget.", - "icon": "gallifr.png", - "screenshots": [{"url":"screenshot_time.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"gallifr.app.js","url":"app.js"}, - {"name":"gallifr.img","url":"app-icon.js","evaluate":true}, - {"name":"gallifr.settings.js","url":"settings.js"} - ], - "data": [{"name":"gallifr.json"}] - }, - { - "id": "rndmclk", - "name": "Random Clock Loader", - "version": "0.03", - "description": "Load a different clock whenever the LCD is switched on.", - "icon": "rndmclk.png", - "type": "widget", - "tags": "widget,clock", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"rndmclk.wid.js","url":"widget.js"} - ] - }, - { - "id": "dotmatrixclock", - "name": "Dotmatrix Clock", - "version": "0.01", - "description": "A clear white-on-blue dotmatrix simulated clock", - "icon": "dotmatrixclock.png", - "type": "clock", - "tags": "clock,dotmatrix,retro", - "supports": ["BANGLEJS"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"dotmatrixclock.app.js","url":"app.js"}, - {"name":"dotmatrixclock.img","url":"dotmatrixclock-icon.js","evaluate":true} - ] - }, - { - "id": "jbm8b", - "name": "Magic 8 Ball", - "shortName": "Magic 8 Ball", - "version": "0.03", - "description": "A simple fortune telling app", - "icon": "app.png", - "tags": "game", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"jbm8b.app.js","url":"app.js"}, - {"name":"jbm8b.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "jbm8b_IT", - "name": "Magic 8 Ball Italiano", - "shortName": "Magic 8 Ball IT", - "version": "0.01", - "description": "La palla predice il futuro", - "icon": "app.png", - "screenshots": [{"url":"bangle1-magic-8-ball-italiano-screenshot.png"}], - "tags": "game", - "supports": ["BANGLEJS"], - "allow_emulator": true, - "storage": [ - {"name":"jbm8b_IT.app.js","url":"app.js"}, - {"name":"jbm8b_IT.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "BLEcontroller", - "name": "BLE Customisable Controller with Joystick", - "shortName": "BLE Controller", - "version": "0.01", - "description": "A configurable controller for BLE devices and robots, with a basic four direction joystick. Designed to be easy to customise so you can add your own menus.", - "icon": "BLEcontroller.png", - "tags": "tool,bluetooth", - "supports": ["BANGLEJS"], - "readme": "README.md", - "allow_emulator": false, - "storage": [ - {"name":"BLEcontroller.app.js","url":"app.js"}, - {"name":"BLEcontroller.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "widviz", - "name": "Widget Visibility Widget", - "shortName": "Viz Widget", - "version": "0.03", - "description": "Swipe left to hide top bar widgets, swipe right to redisplay.", - "icon": "eye.png", - "type": "widget", - "tags": "widget", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widviz.wid.js","url":"widget.js"} - ] - }, - { - "id": "binclock", - "name": "Binary Clock", - "shortName": "Binary Clock", - "version": "0.03", - "description": "A binary clock with hours and minutes. BTN1 toggles a digital clock.", - "icon": "app.png", - "type": "clock", - "tags": "clock,binary", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"binclock.app.js","url":"app.js"}, - {"name":"binclock.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "pizzatimer", - "name": "Pizza Timer", - "shortName": "Pizza Timer", - "version": "0.01", - "description": "A timer app for when you cook Pizza. Some say it can also time other things", - "icon": "pizza.png", - "tags": "timer,tool,pizza", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"pizzatimer.app.js","url":"app.js"}, - {"name":"pizzatimer.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "animclk", - "name": "Animated Clock", - "shortName": "Anim Clock", - "version": "0.03", - "description": "An animated clock face using Mark Ferrari's amazing 8 bit game art and palette cycling: http://www.markferrari.com/art/8bit-game-art", - "icon": "app.png", - "type": "clock", - "tags": "clock,animated", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"animclk.app.js","url":"app.js"}, - {"name":"animclk.pixels1","url":"animclk.pixels1"}, - {"name":"animclk.pixels2","url":"animclk.pixels2"}, - {"name":"animclk.pal","url":"animclk.pal"}, - {"name":"animclk.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "analogimgclk", - "name": "Analog Clock (Image background)", - "shortName": "Analog Clock", - "version": "0.03", - "description": "An analog clock with an image background", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"analogimgclk.app.js","url":"app.js"}, - {"name":"analogimgclk.bg.img","url":"bg.img"}, - {"name":"analogimgclk.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "verticalface", - "name": "Vertical watch face", - "shortName": "Vertical Face", - "version": "0.09", - "description": "A simple vertical watch face with the date. Heart rate monitor is toggled with BTN1", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "allow_emulator": true, - "screenshots": [{"url":"bangle1-vertical-watch-face-screenshot.png"}], - "storage": [ - {"name":"verticalface.app.js","url":"app.js"}, - {"name":"verticalface.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "sleepphasealarm", - "name": "SleepPhaseAlarm", - "shortName": "SleepPhaseAlarm", - "version": "0.02", - "description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.", - "icon": "app.png", - "tags": "alarm", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"sleepphasealarm.app.js","url":"app.js"}, - {"name":"sleepphasealarm.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "life", - "name": "Game of Life", - "version": "0.04", - "description": "Conway's Game of Life - 16x16 board", - "icon": "life.png", - "tags": "game", - "supports": ["BANGLEJS"], - "screenshots": [{"url":"bangle1-game-of-life-screenshot.png"}], - "allow_emulator": true, - "storage": [ - {"name":"life.app.js","url":"life.min.js"}, - {"name":"life.img","url":"life-icon.js","evaluate":true} - ] - }, - { - "id": "magnav", - "name": "Navigation Compass", - "version": "0.05", - "description": "Compass with linear display as for GPSNAV. Has Tilt compensation and remembers calibration.", - "screenshots": [{"url":"screenshot-b2.png"},{"url":"screenshot-light-b2.png"}], - "icon": "magnav.png", - "tags": "tool,outdoors", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"magnav.app.js","url":"magnav_b1.js","supports":["BANGLEJS"]}, - {"name":"magnav.app.js","url":"magnav_b2.js","supports":["BANGLEJS2"]}, - {"name":"magnav.img","url":"magnav-icon.js","evaluate":true} - ], - "data": [{"name":"magnav.json"}] - }, - { - "id": "gpspoilog", - "name": "GPS POI Logger", - "shortName": "GPS POI Log", - "version": "0.01", - "description": "A simple app to log points of interest with their GPS coordinates and read them back onto your PC. Based on the https://www.espruino.com/Bangle.js+Storage tutorial", - "icon": "app.png", - "tags": "outdoors", - "supports": ["BANGLEJS"], - "interface": "interface.html", - "storage": [ - {"name":"gpspoilog.app.js","url":"app.js"}, - {"name":"gpspoilog.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "miclock2", - "name": "Mixed Clock 2", - "version": "0.01", - "description": "White color variant of the Mixed Clock with thicker clock hands for better readability in the bright sunlight, extra space under the clock for widgets and seconds in the digital clock.", - "icon": "clock-mixed.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "screenshots": [{"url":"bangle1-mixed-clock-2-screenshot.png"}], - "allow_emulator": true, - "storage": [ - {"name":"miclock2.app.js","url":"clock-mixed.js"}, - {"name":"miclock2.img","url":"clock-mixed-icon.js","evaluate":true} - ] - }, - { - "id": "1button", - "name": "One-Button-Tracker", - "version": "0.01", - "description": "A widget that turns BTN1 into a tracker, records time of button press/release.", - "icon": "widget.png", - "type": "widget", - "tags": "tool,quantifiedself,widget", - "supports": ["BANGLEJS"], - "readme": "README.md", - "interface": "interface.html", - "storage": [ - {"name":"1button.wid.js","url":"widget.js"} - ], - "data": [{"name":"one_button_presses.csv","storageFile":true}] - }, - { - "id": "gpsautotime", - "name": "GPS auto time", - "shortName": "GPS auto time", - "version": "0.01", - "description": "A widget that automatically updates the Bangle.js time to the GPS time whenever there is a valid GPS fix.", - "icon": "widget.png", - "type": "widget", - "tags": "widget,gps", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"gpsautotime.wid.js","url":"widget.js"} - ] - }, - { - "id": "espruinoctrl", - "name": "Espruino Control", - "shortName": "Espruino Ctrl", - "version": "0.01", - "description": "Send commands to other Espruino devices via the Bluetooth UART interface. Customisable commands!", - "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], - "readme": "README.md", - "custom": "custom.html", - "storage": [ - {"name":"espruinoctrl.app.js"}, - {"name":"espruinoctrl.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "multiclock", - "name": "Multi Clock", - "version": "0.09", - "description": "Clock with multiple faces. Switch between faces with BTN1 & BTN3 (Bangle 2 touch top-right, bottom right). For best display set theme Background 2 to cyan or some other bright colour in settings.", - "screenshots": [{"url":"screen-ana.png"},{"url":"screen-big.png"},{"url":"screen-td.png"},{"url":"screen-nifty.png"},{"url":"screen-word.png"},{"url":"screen-sec.png"}], - "icon": "multiclock.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"multiclock.app.js","url":"multiclock.app.js"}, - {"name":"big.face.js","url":"big.face.js"}, - {"name":"ana.face.js","url":"ana.face.js"}, - {"name":"digi.face.js","url":"digi.face.js"}, - {"name":"txt.face.js","url":"txt.face.js"}, - {"name":"dk.face.js","url":"dk.face.js"}, - {"name":"nifty.face.js","url":"nifty.face.js"}, - {"name":"multiclock.img","url":"multiclock-icon.js","evaluate":true} - ] - }, - { - "id": "widancs", - "name": "Apple Notification Widget", - "shortName": "ANCS Widget", - "version": "0.07", - "description": "Displays call, message etc notifications from a paired iPhone. Read README before installation as it only works with compatible apps", - "icon": "widget.png", - "type": "widget", - "tags": "widget", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"widancs.wid.js","url":"ancs.min.js"}, - {"name":"widancs.settings.js","url":"settings.js"} - ] - }, - { - "id": "accelrec", - "name": "Acceleration Recorder", - "shortName": "Accel Rec", - "version": "0.02", - "description": "This app puts the Bangle's accelerometer into 100Hz mode and reads 2 seconds worth of data after movement starts. The data can then be exported back to the PC.", - "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], - "readme": "README.md", - "interface": "interface.html", - "storage": [ - {"name":"accelrec.app.js","url":"app.js"}, - {"name":"accelrec.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"wildcard":"accelrec.?.csv"}] - }, - { - "id": "accellog", - "name": "Acceleration Logger", - "shortName": "Accel Log", - "version": "0.03", - "description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC", - "icon": "app.png", - "tags": "outdoor", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "interface": "interface.html", - "storage": [ - {"name":"accellog.app.js","url":"app.js"}, - {"name":"accellog.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"wildcard":"accellog.?.csv"}] - }, - { - "id": "cprassist", - "name": "CPR Assist", - "version": "0.02", - "description": "Provides assistance while performing a CPR", - "icon": "cprassist-icon.png", - "tags": "tool,firstaid", - "supports": ["BANGLEJS", "BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "screenshots": [{"url":"bangle1-CPR-assist-screenshot.png"}], - "storage": [ - {"name":"cprassist.app.js","url":"cprassist.js"}, - {"name":"cprassist.img","url":"cprassist-icon.js","evaluate":true}, - {"name":"cprassist.settings.js","url":"settings.js"} - ] - }, - { - "id": "osgridref", - "name": "Ordnance Survey Grid Reference", - "shortName": "OS Grid ref", - "version": "0.01", - "description": "Displays the UK Ordnance Survey grid reference of your current GPS location. Useful when in the United Kingdom with an Ordnance Survey map", - "icon": "app.png", - "tags": "outdoors,gps", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"osgridref.app.js","url":"app.js"}, - {"name":"osgridref.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "openseizure", - "name": "OpenSeizureDetector Widget", - "shortName": "Short Name", - "version": "0.01", - "description": "[BETA!] A widget to work alongside [OpenSeizureDetector](https://www.openseizuredetector.org.uk/)", - "icon": "widget.png", - "type": "widget", - "tags": "widget", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"openseizure.wid.js","url":"widget.js"} - ] - }, - { - "id": "counter", - "name": "Counter", - "version": "0.03", - "description": "Simple counter", - "icon": "counter_icon.png", - "tags": "tool", - "supports": ["BANGLEJS"], - "screenshots": [{"url":"bangle1-counter-screenshot.png"}], - "allow_emulator": true, - "storage": [ - {"name":"counter.app.js","url":"counter.js"}, - {"name":"counter.img","url":"counter-icon.js","evaluate":true} - ] - }, - { - "id": "bootgattbat", - "name": "BLE GATT Battery Service", - "shortName": "BLE Battery Service", - "version": "0.01", - "description": "Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth.\n", - "icon": "bluetooth.png", - "type": "bootloader", - "tags": "battery,ble,bluetooth,gatt", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"gattbat.boot.js","url":"boot.js"} - ] - }, - { - "id": "viewstl", - "name": "STL file viewer", - "shortName": "ViewSTL", - "version": "0.02", - "description": "This app allows you to view STL 3D models on your watch", - "icon": "icons8-octahedron-48.png", - "tags": "tool", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"viewstl.app.js","url":"viewstl.min.js"}, - {"name":"viewstl.img","url":"viewstl-icon.js","evaluate":true}, - {"name":"tetra.stl","url":"tetra.stl"}, - {"name":"cube.stl","url":"cube.stl"}, - {"name":"icosa.stl","url":"icosa.stl"} - ] - }, - { - "id": "cscsensor", - "name": "Cycling speed sensor", - "shortName": "CSCSensor", - "version": "0.06", - "description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch", - "icon": "icons8-cycling-48.png", - "tags": "outdoors,exercise,ble,bluetooth", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"cscsensor.app.js","url":"cscsensor.app.js"}, - {"name":"cscsensor.settings.js","url":"settings.js"}, - {"name":"cscsensor.img","url":"cscsensor-icon.js","evaluate":true} - ] - }, - { - "id": "fileman", - "name": "File manager", - "shortName": "FileManager", - "version": "0.03", - "description": "Simple file manager, allows user to examine watch storage and display, load or delete individual files", - "icon": "icons8-filing-cabinet-48.png", - "tags": "tools", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"fileman.app.js","url":"fileman.app.js"}, - {"name":"fileman.img","url":"fileman-icon.js","evaluate":true} - ] - }, - { - "id": "worldclock", - "name": "World Clock - 4 time zones", - "shortName": "World Clock", - "version": "0.05", - "description": "Current time zone plus up to four others", - "icon": "app.png", - "screenshots": [{"url":"screenshot_world.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "custom": "custom.html", - "storage": [ - {"name":"worldclock.app.js","url":"app.js"}, - {"name":"worldclock.img","url":"worldclock-icon.js","evaluate":true} - ], - "data": [{"name":"worldclock.settings.json"}] - }, - { - "id": "digiclock", - "name": "Digital Clock Face", - "shortName": "Digi Clock", - "version": "0.02", - "description": "A simple digital clock with the time, day, month, and year", - "icon": "digiclock.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"digiclock.app.js","url":"digiclock.js"}, - {"name":"digiclock.img","url":"digiclock-icon.js","evaluate":true} - ] - }, - { - "id": "dsdrelay", - "name": "DSD BLE Relay controller", - "shortName": "DSDRelay", - "version": "0.01", - "description": "Control BLE relay board from the watch", - "icon": "icons8-relay-48.png", - "tags": "ble,bluetooth", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"dsdrelay.app.js","url":"dsdrelay.app.js"}, - {"name":"dsdrelay.img","url":"dsdrelay-icon.js","evaluate":true} - ] - }, - { - "id": "mandel", - "name": "Mandelbrot", - "shortName": "Mandel", - "version": "0.01", - "description": "Draw a zoomable Mandelbrot set", - "icon": "mandel.png", - "tags": "game", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"mandel.app.js","url":"mandel.min.js"}, - {"name":"mandel.img","url":"mandel-icon.js","evaluate":true} - ] - }, - { - "id": "petrock", - "name": "Pet rock", - "version": "0.02", - "description": "A virtual pet rock with wobbly eyes", - "icon": "petrock.png", - "type": "app", - "tags": "game", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"petrock.app.js","url":"app.js"}, - {"name":"petrock.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "smartibot", - "name": "Smartibot controller", - "shortName": "Smartibot", - "version": "0.01", - "description": "Control a [Smartibot Robot](https://thecraftyrobot.net/) straight from your Bangle.js", - "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"smartibot.app.js","url":"app.js"}, - {"name":"smartibot.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "widncr", - "name": "NCR Logo Widget", - "version": "0.01", - "description": "Show the NodeConf Remote logo in the top left", - "icon": "widget.png", - "type": "widget", - "tags": "widget", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"widncr.wid.js","url":"widget.js"} - ] - }, - { - "id": "ncrclk", - "name": "NCR Clock", - "shortName": "NCR Clock", - "version": "0.02", - "description": "NodeConf Remote clock", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"ncrclk.app.js","url":"app.js"}, - {"name":"ncrclk.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "isoclock", - "name": "ISO Compliant Clock Face", - "shortName": "ISO Clock", - "version": "0.02", - "description": "Tweaked fork of digiclock for ISO date and time", - "icon": "isoclock.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"isoclock.app.js","url":"isoclock.js"}, - {"name":"isoclock.img","url":"isoclock-icon.js","evaluate":true} - ] - }, - { - "id": "gpstimeserver", - "name": "GPS Time Server", - "version": "0.01", - "description": "A widget which automatically starts the GPS and turns Bangle.js into a Bluetooth time server.", - "icon": "widget.png", - "type": "widget", - "tags": "widget", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"gpstimeserver.wid.js","url":"widget.js"} - ] - }, - { - "id": "tilthydro", - "name": "Tilt Hydrometer Display", - "shortName": "Tilt Hydro", - "version": "0.01", - "description": "A display for the [Tilt Hydrometer](https://tilthydrometer.com/) - [more info here](http://www.espruino.com/Tilt+Hydrometer+Display)", - "icon": "app.png", - "tags": "tools,bluetooth", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"tilthydro.app.js","url":"app.js"}, - {"name":"tilthydro.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "supmariodark", - "name": "Super mario clock night mode", - "shortName": "supmariodark", - "version": "0.01", - "description": "Super mario clock in night mode", - "icon": "supmariodark.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"supmariodark.app.js","url":"supmariodark.js"}, - {"name":"supmariodark.img","url":"supmariodark-icon.js","evaluate":true}, - {"name":"supmario30x24.bin","url":"supmario30x24.bin.js"}, - {"name":"supmario30x24.wdt","url":"supmario30x24.wdt.js"}, - {"name":"banner-up.img","url":"banner-up.js","evaluate":true}, - {"name":"banner-down.img","url":"banner-down.js","evaluate":true}, - {"name":"brick2.img","url":"brick2.js","evaluate":true}, - {"name":"enemy.img","url":"enemy.js","evaluate":true}, - {"name":"flower.img","url":"flower.js","evaluate":true}, - {"name":"flower_b.img","url":"flower_b.js","evaluate":true}, - {"name":"mario_wh.img","url":"mario_wh.js","evaluate":true}, - {"name":"pipe.img","url":"pipe.js","evaluate":true} - ] - }, - { - "id": "gmeter", - "name": "G-Meter", - "shortName": "G-Meter", - "version": "0.01", - "description": "Simple G-Meter", - "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"gmeter.app.js","url":"app.js"}, - {"name":"gmeter.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "dtlaunch", - "name": "Desktop Launcher", - "version": "0.07", - "description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.", - "screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}], - "icon": "icon.png", - "type": "launch", - "tags": "tool,system,launcher", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"dtlaunch.app.js","url":"app-b1.js", "supports": ["BANGLEJS"]}, - {"name":"dtlaunch.app.js","url":"app-b2.js", "supports": ["BANGLEJS2"]}, - {"name":"dtlaunch.settings.js","url":"settings-b1.js", "supports": ["BANGLEJS"]}, - {"name":"dtlaunch.settings.js","url":"settings-b2.js", "supports": ["BANGLEJS2"]}, - {"name":"dtlaunch.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"name":"dtlaunch.json"}] - }, - { - "id": "HRV", - "name": "Heart Rate Variability monitor", - "shortName": "HRV monitor", - "version": "0.04", - "description": "Heart Rate Variability monitor, see Readme for more info", - "icon": "hrv.png", - "tags": "", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"HRV.app.js","url":"app.js"}, - {"name":"HRV.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "hardalarm", - "name": "Hard Alarm", - "shortName": "HardAlarm", - "version": "0.02", - "description": "Make sure you wake up! Count to the right number to turn off the alarm", - "icon": "app.png", - "tags": "tool,alarm,widget", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"hardalarm.app.js","url":"app.js"}, - {"name":"hardalarm.boot.js","url":"boot.js"}, - {"name":"hardalarm.js","url":"hardalarm.js"}, - {"name":"hardalarm.img","url":"app-icon.js","evaluate":true}, - {"name":"hardalarm.wid.js","url":"widget.js"} - ], - "data": [{"name":"hardalarm.json"}] - }, - { - "id": "edisonsball", - "name": "Edison's Ball", - "shortName": "Edison's Ball", - "version": "0.01", - "description": "Hypnagogia/Micro-Sleep alarm for experimental use in exploring sleep transition and combating drowsiness", - "icon": "app-icon.png", - "tags": "", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"edisonsball.app.js","url":"app.js"}, - {"name":"edisonsball.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "hrrawexp", - "name": "HRM Data Exporter", - "shortName": "HRM Data Exporter", - "version": "0.01", - "description": "export raw hrm signal data to a csv file", - "icon": "app-icon.png", - "tags": "", - "supports": ["BANGLEJS"], - "readme": "README.md", - "interface": "interface.html", - "storage": [ - {"name":"hrrawexp.app.js","url":"app.js"}, - {"name":"hrrawexp.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "breath", - "name": "Breathing App", - "shortName": "Breathing App", - "version": "0.01", - "description": "app to aid relaxation and train breath syncronicity using haptics and visualisation, also displays HR", - "icon": "app-icon.png", - "tags": "tools,health", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"breath.app.js","url":"app.js"}, - {"name":"breath.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"name":"breath.settings.json","url":"settings.json"}] - }, - { - "id": "lazyclock", - "name": "Lazy Clock", - "version": "0.03", - "description": "Tells the time, roughly", - "icon": "lazyclock.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "readme": "README.md", - "screenshots": [{"url":"bangle1-lazy-clock-screenshot.png"}], - "allow_emulator": true, - "storage": [ - {"name":"lazyclock.app.js","url":"lazyclock-app.js"}, - {"name":"lazyclock.img","url":"lazyclock-icon.js","evaluate":true} - ] - }, - { - "id": "astral", - "name": "Astral Clock", - "version": "0.03", - "description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.", - "icon": "app-icon.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"astral.app.js","url":"app.js"}, - {"name":"astral.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "alpinenav", - "name": "Alpine Nav", - "version": "0.01", - "description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime", - "icon": "app-icon.png", - "tags": "outdoors,gps", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"alpinenav.app.js","url":"app.js"}, - {"name":"alpinenav.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "lifeclk", - "name": "Game of Life Clock", - "shortName": "Conway's Clock", - "version": "0.06", - "description": "Modification and clockification of Conway's Game of Life", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"lifeclk.app.js","url":"app.min.js"}, - {"name":"lifeclk.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "speedalt", - "name": "GPS Adventure Sports", - "shortName": "GPS Adv Sport", - "version": "1.02", - "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", - "icon": "app.png", - "type": "app", - "tags": "tool,outdoors", - "supports": ["BANGLEJS"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"speedalt.app.js","url":"app.js"}, - {"name":"speedalt.img","url":"app-icon.js","evaluate":true}, - {"name":"speedalt.settings.js","url":"settings.js"} - ], - "data": [{"name":"speedalt.json"}] - }, - { - "id": "speedalt2", - "name": "GPS Adventure Sports II", - "shortName":"GPS Adv Sport II", - "version":"1.10", - "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", - "icon": "app.png", - "type": "app", - "tags": "tool,outdoors", - "supports": ["BANGLEJS"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"speedalt2.app.js","url":"app.js"}, - {"name":"speedalt2.img","url":"app-icon.js","evaluate":true}, - {"name":"speedalt2.settings.js","url":"settings.js"} - ], - "data": [{"name":"speedalt2.json"}] - }, - { - "id": "slomoclock", - "name": "SloMo Clock", - "shortName": "SloMo Clock", - "version": "0.10", - "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", - "icon": "watch.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "readme": "README.md", - "allow_emulator": true, - "screenshots": [{"url":"bangle1-slow-mo-clock-screenshot.png"}], - "storage": [ - {"name":"slomoclock.app.js","url":"app.js"}, - {"name":"slomoclock.img","url":"app-icon.js","evaluate":true}, - {"name":"slomoclock.settings.js","url":"settings.js"} - ], - "data": [{"name":"slomoclock.json"}] - }, - { - "id": "de-stress", - "name": "De-Stress", - "shortName": "De-Stress", - "version": "0.02", - "description": "Simple haptic heartbeat", - "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"de-stress.app.js","url":"app.js"}, - {"name":"de-stress.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "mclockplus", - "name": "Morph Clock+", - "shortName": "Morph Clock+", - "version": "0.03", - "description": "Morphing Clock with more readable seconds and date and additional stopwatch", - "icon": "mclockplus.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"mclockplus.app.js","url":"mclockplus.app.js"}, - {"name":"mclockplus.img","url":"mclockplus-icon.js","evaluate":true} - ] - }, - { - "id": "intervals", - "name": "Intervals App", - "shortName": "Intervals", - "version": "0.01", - "description": "Intervals for training. It is possible to configure work time and rest time and number of sets.", - "icon": "intervals.png", - "tags": "", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"intervals.app.js","url":"intervals.app.js"}, - {"name":"intervals.img","url":"intervals-icon.js","evaluate":true} - ] - }, - { - "id": "planetarium", - "name": "Planetarium", - "shortName": "Planetarium", - "version": "0.03", - "description": "Planetarium showing up to 500 stars using the watch location and time", - "icon": "planetarium.png", - "tags": "", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"planetarium.app.js","url":"planetarium.app.js"}, - {"name":"planetarium.data.csv","url":"planetarium.data.csv"}, - {"name":"planetarium.const.csv","url":"planetarium.const.csv"}, - {"name":"planetarium.extra.csv","url":"planetarium.extra.csv"}, - {"name":"planetarium.settings.js","url":"settings.js"}, - {"name":"planetarium.img","url":"planetarium-icon.js","evaluate":true} - ], - "data": [{"name":"planetarium.json"}] - }, - { - "id": "tapelauncher", - "name": "Tape Launcher", - "version": "0.02", - "description": "An App launcher, icons displayed in a horizontal tape, swipe or use buttons", - "icon": "icon.png", - "type": "launch", - "tags": "tool,system,launcher", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"tapelauncher.app.js","url":"app.js"}, - {"name":"tapelauncher.img","url":"icon.js","evaluate":true} - ] - }, - { - "id": "oblique", - "name": "Oblique Strategies", - "version": "0.01", - "description": "Oblique Strategies for creativity. Copied from Brian Eno.", - "icon": "eno.png", - "tags": "tool", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"oblique.app.js","url":"app.js"}, - {"name":"oblique.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "testuserinput", - "name": "Test User Input", - "shortName": "Test User Input", - "version": "0.06", - "description": "App to test the bangle.js input interface. It displays the user action in text, circle buttons or on/off switch UI elements.", - "icon": "app.png", - "tags": "input,interface,buttons,touch,UI", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"testuserinput.app.js","url":"app.js"}, - {"name":"testuserinput.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "gpssetup", - "name": "GPS Setup", - "shortName": "GPS Setup", - "version": "0.02", - "description": "Configure the GPS power options and store them in the GPS nvram", - "icon": "gpssetup.png", - "tags": "gps,tools,outdoors", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"gpssetup","url":"gpssetup.js"}, - {"name":"gpssetup.settings.js","url":"settings.js"}, - {"name":"gpssetup.app.js","url":"app.js"}, - {"name":"gpssetup.img","url":"icon.js","evaluate":true} - ], - "data": [{"name":"gpssetup.settings.json","url":"settings.json"}] - }, - { - "id": "walkersclock", - "name": "Walkers Clock", - "shortName": "Walkers Clock", - "version": "0.04", - "description": "A large font watch, displays steps, can switch GPS on/off, displays grid reference", - "icon": "walkersclock48.png", - "type": "clock", - "tags": "clock,gps,tools,outdoors", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"walkersclock.app.js","url":"app.js"}, - {"name":"walkersclock.img","url":"icon.js","evaluate":true} - ] - }, - { - "id": "widgps", - "name": "GPS Widget", - "version": "0.03", - "description": "Tiny widget to show the power on/off status of the GPS", - "icon": "widget.png", - "type": "widget", - "tags": "widget,gps", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"widgps.wid.js","url":"widget.js"} - ] - }, - { - "id": "widhrt", - "name": "HRM Widget", - "version": "0.03", - "description": "Tiny widget to show the power on/off status of the Heart Rate Monitor", - "icon": "widget.png", - "type": "widget", - "tags": "widget,hrm", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"widhrt.wid.js","url":"widget.js"} - ] - }, - { - "id": "countdowntimer", - "name": "Countdown Timer", - "version": "0.01", - "description": "A simple countdown timer with a focus on usability", - "icon": "countdowntimer.png", - "tags": "timer,tool", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"countdowntimer.app.js","url":"countdowntimer.js"}, - {"name":"countdowntimer.img","url":"countdowntimer-icon.js","evaluate":true} - ] - }, - { - "id": "helloworld", - "name": "hello, world!", - "shortName": "hello world", - "version": "0.02", - "description": "A cross cultural hello world!/hola mundo! app with colors and languages", - "icon": "app.png", - "tags": "input,interface,buttons,touch", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"helloworld.app.js","url":"app.js"}, - {"name":"helloworld.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "widcom", - "name": "Compass Widget", - "version": "0.02", - "description": "Tiny widget to show the power on/off status of the Compass", - "icon": "widget.png", - "type": "widget", - "tags": "widget,compass", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"widcom.wid.js","url":"widget.js"} - ] - }, - { - "id": "arrow", - "name": "Arrow Compass", - "version": "0.05", - "description": "Moving arrow compass that points North, shows heading, with tilt correction. Based on jeffmer's Navigation Compass", - "icon": "arrow.png", - "type": "app", - "tags": "tool,outdoors", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"arrow.app.js","url":"app.js"}, - {"name":"arrow.img","url":"icon.js","evaluate":true} - ] - }, - { - "id": "waypointer", - "name": "Way Pointer", - "version": "0.01", - "description": "Navigate to a waypoint using the GPS for bearing and compass to point way, uses the same waypoint interface as GPS Navigation", - "icon": "waypointer.png", - "tags": "tool,outdoors,gps", - "supports": ["BANGLEJS"], - "readme": "README.md", - "interface": "waypoints.html", - "storage": [ - {"name":"waypointer.app.js","url":"app.js"}, - {"name":"waypointer.img","url":"icon.js","evaluate":true} - ], - "data": [{"name":"waypoints.json","url":"waypoints.json"}] - }, - { - "id": "color_catalog", - "name": "Colors Catalog", - "shortName": "Colors Catalog", - "version": "0.01", - "description": "Displays RGB565 and RGB888 colors, its name and code in screen.", - "icon": "app.png", - "tags": "Color,input,buttons,touch,UI", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"color_catalog.app.js","url":"app.js"}, - {"name":"color_catalog.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "UI4swatch", - "name": "UI 4 swatch", - "shortName": "UI 4 swatch", - "version": "0.01", - "description": "A UI/UX for espruino smartwatches, displays dinamically calc. x,y coordinates.", - "icon": "app.png", - "tags": "Color,input,buttons,touch,UI", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"UI4swatch.app.js","url":"app.js"}, - {"name":"UI4swatch.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "simplest", - "name": "Simplest Clock", - "version": "0.03", - "description": "The simplest working clock, acts as a tutorial piece", - "icon": "simplest.png", - "screenshots": [{"url":"screenshot_simplest.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"simplest.app.js","url":"app.js"}, - {"name":"simplest.img","url":"icon.js","evaluate":true} - ] - }, - { - "id": "stepo", - "name": "Stepometer Clock", - "version": "0.03", - "description": "A large font watch, displays step count in a doughnut guage and warns of low battery, requires one of the steps widgets to be installed", - "icon": "stepo.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"stepo.app.js","url":"app.js"}, - {"name":"stepo.img","url":"icon.js","evaluate":true} - ] - }, - { - "id": "gbmusic", - "name": "Gadgetbridge Music Controls", - "shortName": "Music Controls", - "version": "0.08", - "description": "Control the music on your Gadgetbridge-connected phone", - "icon": "icon.png", - "screenshots": [{"url":"screenshot_v1.png"},{"url":"screenshot_v2.png"}], - "type": "app", - "tags": "tools,bluetooth,gadgetbridge,music", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"gbmusic.app.js","url":"app.js"}, - {"name":"gbmusic.settings.js","url":"settings.js"}, - {"name":"gbmusic.wid.js","url":"widget.js"}, - {"name":"gbmusic.img","url":"icon.js","evaluate":true} - ], - "data": [{"name":"gbmusic.json"},{"name":"gbmusic.load.json"}] - }, - { - "id": "battleship", - "name": "Battleship", - "version": "0.01", - "description": "The classic game of battleship", - "icon": "battleship-icon.png", - "tags": "game", - "supports": ["BANGLEJS"], - "screenshots": [{"url":"bangle1-battle-ship-screenshot.png"}], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"battleship.app.js","url":"battleship.js"}, - {"name":"battleship.img","url":"battleship-icon.js","evaluate":true} - ] - }, - { - "id": "kitchen", - "name": "Kitchen Combo", - "version": "0.13", - "description": "Combination of the Stepo, Walkersclock, Arrow and Waypointer apps into a multiclock format. 'Everything but the kitchen sink'", - "icon": "kitchen.png", - "type": "clock", - "tags": "tool,outdoors,gps", - "supports": ["BANGLEJS"], - "readme": "README.md", - "interface": "waypoints.html", - "storage": [ - {"name":"kitchen.app.js","url":"kitchen.app.js"}, - {"name":"stepo2.kit.js","url":"stepo2.kit.js"}, - {"name":"swatch.kit.js","url":"swatch.kit.js"}, - {"name":"gps.kit.js","url":"gps.kit.js"}, - {"name":"compass.kit.js","url":"compass.kit.js"}, - {"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true} - ], - "data": [{"name":"waypoints.json","url":"waypoints.json"}] - }, - { - "id": "banglebridge", - "name": "BangleBridge", - "shortName": "BangleBridge", - "version": "0.01", - "description": "Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App", - "icon": "widget.png", - "type": "widget", - "tags": "widget", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"banglebridge.wid.js","url":"widget.js"}, - {"name":"banglebridge.watch.img","url":"watch.img"}, - {"name":"banglebridge.heart.img","url":"heart.img"} - ] - }, - { - "id": "qmsched", - "name": "Quiet Mode Schedule and Widget", - "shortName": "Quiet Mode", - "version": "0.06", - "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"}, - {"url":"screenshot_b2_main.png"},{"url":"screenshot_b2_edit.png"},{"url":"screenshot_b2_lcd.png"}], - "tags": "tool,widget", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"qmsched","url":"lib.js"}, - {"name":"qmsched.app.js","url":"app.js"}, - {"name":"qmsched.boot.js","url":"boot.js"}, - {"name":"qmsched.img","url":"icon.js","evaluate":true}, - {"name":"qmsched.wid.js","url":"widget.js"} - ], - "data": [{"name":"qmsched.json"}] - }, - { - "id": "hourstrike", - "name": "Hour Strike", - "shortName": "Hour Strike", - "version": "0.08", - "description": "Strike the clock on the hour. A great tool to remind you an hour has passed!", - "icon": "app-icon.png", - "tags": "tool,alarm", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"hourstrike.app.js","url":"app.js"}, - {"name":"hourstrike.boot.js","url":"boot.js"}, - {"name":"hourstrike.img","url":"app-icon.js","evaluate":true}, - {"name":"hourstrike.json","url":"hourstrike.json"} - ] - }, - { - "id": "whereworld", - "name": "Where in the World?", - "shortName": "Where World", - "version": "0.01", - "description": "Shows your current location on the world map", - "icon": "app.png", - "tags": "gps", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"whereworld.app.js","url":"app.js"}, - {"name":"whereworld.img","url":"app-icon.js","evaluate":true}, - {"name":"whereworld.worldmap","url":"worldmap"} - ] - }, - { - "id": "omnitrix", - "name": "Omnitrix", - "version": "0.01", - "description": "An Omnitrix Showpiece", - "icon": "omnitrix.png", - "screenshots": [{"url":"screenshot.png"}], - "tags": "game", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"omnitrix.app.js","url":"omnitrix.app.js"}, - {"name":"omnitrix.img","url":"omnitrix.icon.js","evaluate":true} - ] - }, - { - "id": "batclock", - "name": "Bat Clock", - "shortName": "Bat Clock", - "version": "0.02", - "description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.", - "icon": "bat-clock.png", - "screenshots": [{"url":"screenshot.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"batclock.app.js","url":"bat-clock.app.js"}, - {"name":"batclock.img","url":"bat-clock.icon.js","evaluate":true} - ] - }, - { - "id": "doztime", - "name": "Dozenal Time", - "shortName": "Dozenal Time", - "version": "0.04", - "description": "A dozenal Holocene calendar and dozenal diurnal clock", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS", "BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"doztime.app.js","url":"app-bangle1.js","supports":["BANGLEJS"]}, - {"name":"doztime.app.js","url":"app-bangle2.js","supports":["BANGLEJS2"]}, - {"name":"doztime.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "gbtwist", - "name": "Gadgetbridge Twist Control", - "shortName": "Twist Control", - "version": "0.01", - "description": "Shake your wrist to control your music app via Gadgetbridge", - "icon": "app.png", - "type": "app", - "tags": "tools,bluetooth,gadgetbridge,music", - "supports": ["BANGLEJS"], - "readme": "README.md", - "allow_emulator": false, - "storage": [ - {"name":"gbtwist.app.js","url":"app.js"}, - {"name":"gbtwist.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "thermom", - "name": "Thermometer", - "version": "0.05", - "description": "Displays the current temperature in degree Celsius/Fahrenheit (depending on locale), updates every 10 seconds with average of last 5 readings.", - "icon": "app.png", - "tags": "tool", - "supports": ["BANGLEJS", "BANGLEJS2"], - "screenshots": [{"url":"screenshot.png"}], - "allow_emulator": true, - "storage": [ - {"name":"thermom.app.js","url":"app.js"}, - {"name":"thermom.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "mysticdock", - "name": "Mystic Dock", - "version": "1.00", - "description": "A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.", - "icon": "mystic-dock.png", - "type": "dock", - "tags": "dock", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"mysticdock.app.js","url":"mystic-dock-app.js"}, - {"name":"mysticdock.boot.js","url":"mystic-dock-boot.js"}, - {"name":"mysticdock.settings.js","url":"mystic-dock-settings.js"}, - {"name":"mysticdock.img","url":"mystic-dock-icon.js","evaluate":true} - ] - }, - { - "id": "mysticclock", - "name": "Mystic Clock", - "version": "1.01", - "description": "A retro-inspired watchface featuring time, date, and an interactive data display line.", - "icon": "mystic-clock.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "screenshots": [{"url":"bangle1-mystic-clock-screenshot.png"}], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"mysticclock.app.js","url":"mystic-clock-app.js"}, - {"name":"mysticclock.settings.js","url":"mystic-clock-settings.js"}, - {"name":"mysticclock.img","url":"mystic-clock-icon.js","evaluate":true} - ] - }, - { - "id": "hcclock", - "name": "Hi-Contrast Clock", - "version": "0.03", - "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.", - "icon": "hcclock-icon.png", - "type": "clock", - "tags": "clock", - "screenshots": [{"url":"bangle1-high-contrast-clock-screenshot.png"}], - "supports": ["BANGLEJS"], - "allow_emulator": true, - "storage": [ - {"name":"hcclock.app.js","url":"hcclock.app.js"}, - {"name":"hcclock.img","url":"hcclock-icon.js","evaluate":true} - ] - }, - { - "id": "thermomF", - "name": "Fahrenheit Temp", - "version": "0.01", - "description": "[NOT RECOMMENDED] A modification of the Thermometer App to display temprature in Fahrenheit. Please use the 'Thermometer App' and install 'Languages' to get the temperature in the correct format for your locale.", - "icon": "thermf.png", - "tags": "tool", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"thermomF.app.js","url":"app.js"}, - {"name":"thermomF.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "nixie", - "name": "Nixie Clock", - "shortName": "Nixie", - "version": "0.01", - "description": "A nixie tube clock for both Bangle 1 and 2.", - "icon": "nixie.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"nixie.app.js","url":"app.js"}, - {"name":"nixie.img","url":"app-icon.js","evaluate":true}, - {"name":"m_vatch.js","url":"m_vatch.js"} - ] - }, - { - "id": "carcrazy", - "name": "Car Crazy", - "shortName": "Car Crazy", - "version": "0.03", - "description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.", - "icon": "carcrash.png", - "tags": "game", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"carcrazy.app.js","url":"app.js"}, - {"name":"carcrazy.img","url":"app-icon.js","evaluate":true}, - {"name":"carcrazy.settings.js","url":"settings.js"} - ], - "data": [{"name":"CarCrazy.csv"}] - }, - { - "id": "shortcuts", - "name": "Shortcuts", - "shortName": "Shortcuts", - "version": "0.01", - "description": "Quickly load your favourite apps from (almost) any watch face.", - "icon": "app.png", - "type": "bootloader", - "tags": "tool", - "supports": ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"shortcuts.boot.js","url":"boot.js"}, - {"name":"shortcuts.settings.js","url":"settings.js"} - ], - "data": [{"name":"shortcuts.json"}] - }, - { - "id": "vectorclock", - "name": "Vector Clock", - "version": "0.03", - "description": "A digital clock that uses the built-in vector font.", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS", "BANGLEJS2"], - "allow_emulator": true, - "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} - ] - }, - { - "id": "fd6fdetect", - "name": "fd6fdetect", - "shortName": "fd6fdetect", - "version": "0.2", - "description": "Allows you to see 0xFD6F beacons near you.", - "icon": "app.png", - "tags": "tool", - "readme": "README.md", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"fd6fdetect.app.js","url":"app.js"}, - {"name":"fd6fdetect.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "choozi", - "name": "Choozi", - "version": "0.01", - "description": "Choose people or things at random using Bangle.js.", - "icon": "app.png", - "tags": "tool", - "supports": ["BANGLEJS"], - "readme": "README.md", - "allow_emulator": true, - "screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}], - "storage": [ - {"name":"choozi.app.js","url":"app.js"}, - {"name":"choozi.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "widclkbttm", - "name": "Digital clock (Bottom) widget", - "shortName": "Digital clock Bottom Widget", - "version": "0.03", - "description": "Displays time in the bottom area.", - "icon": "widclkbttm.png", - "type": "widget", - "tags": "widget", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"} - ] - }, - { - "id": "pastel", - "name": "Pastel Clock", - "shortName": "Pastel", - "version": "0.10", - "description": "A Configurable clock with custom fonts, background and weather display. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times", - "icon": "pastel.png", - "dependencies": {"mylocation":"app", "widpedom":"app","weather":"app"}, - "screenshots": [{"url":"screenshot_pastel.png"}, {"url":"weather_icons.png"}], - "type": "clock", - "tags": "clock, weather, tool", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"f_architect","url":"f_architect.js"}, - {"name":"f_gochihand","url":"f_gochihand.js"}, - {"name":"f_cabin","url":"f_cabin.js"}, - {"name":"f_orbitron","url":"f_orbitron.js"}, - {"name":"f_monoton","url":"f_monoton.js"}, - {"name":"f_elite","url":"f_elite.js"}, - {"name":"f_lato","url":"f_lato.js"}, - {"name":"f_latosmall","url":"f_latosmall.js"}, - {"name":"pastel.app.js","url":"pastel.app.js"}, - {"name":"pastel.img","url":"pastel.icon.js","evaluate":true}, - {"name":"pastel.settings.js","url":"pastel.settings.js"} - ], - "data": [{"name":"pastel.json"}] - }, - { - "id": "antonclk", - "name": "Anton Clock", - "version": "0.03", - "description": "A simple clock using the bold Anton font.", - "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"antonclk.app.js","url":"app.js"}, - {"name":"antonclk.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "waveclk", - "name": "Wave Clock", - "version": "0.02", - "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: Works on any Bangle.js 2, but requires firmware 2v11 or later on Bangle.js 1**", - "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"waveclk.app.js","url":"app.js"}, - {"name":"waveclk.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "floralclk", - "name": "Floral Clock", - "version": "0.01", - "description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: Works on any Bangle.js 2 but requires firmware 2v11 or later on Bangle.js 1**", - "icon": "app.png", - "screenshots": [{"url":"screenshot_floral.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"floralclk.app.js","url":"app.js"}, - {"name":"floralclk.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "score", - "name": "Score Tracker", - "version": "0.01", - "description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.", - "icon": "score.app.png", - "screenshots": [{"url":"screenshot_score.png"}], - "type": "app", - "tags": "", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"score.app.js","url":"score.app.js"}, - {"name":"score.settings.js","url":"score.settings.js"}, - {"name":"score.presets.json","url":"score.presets.json"}, - {"name":"score.img","url":"score.app-icon.js","evaluate":true} - ], - "data": [{"name":"score.json"}] - }, - { - "id": "menusmall", - "name": "Small Menus", - "version": "0.02", - "description": "Replace Bangle.js 2's menus with a version that contains smaller text", - "icon": "app.png", - "type": "boot", - "tags": "system", - "supports": ["BANGLEJS2"], - "storage": [ - {"name":"menusmall.boot.js","url":"boot.js"} - ] - }, - { - "id": "ffcniftya", - "name": "Nifty-A Clock", - "version": "0.01", - "description": "A nifty clock with time and date", - "icon": "app.png", - "screenshots": [{"url":"screenshot_nifty.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"ffcniftya.app.js","url":"app.js"}, - {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "ffcniftyb", - "name": "Nifty-B Clock", - "version": "0.02", - "description": "A nifty clock (series B) with time, date and color configuration", - "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"ffcniftyb.app.js","url":"app.js"}, - {"name":"ffcniftyb.img","url":"app-icon.js","evaluate":true}, - {"name":"ffcniftyb.settings.js","url":"settings.js"} - ], - "data": [{"name":"ffcniftyb.json"}] - }, - { - "id": "stopwatch", - "name": "Stopwatch Touch", - "version": "0.01", - "description": "A touch based stop watch for Bangle JS 2", - "icon": "stopwatch.png", - "screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}], - "tags": "tools,app", - "supports": ["BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"stopwatch.app.js","url":"stopwatch.app.js"}, - {"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true} - ] - }, - { - "id": "vernierrespirate", - "name": "Vernier Go Direct Respiration Belt", - "shortName": "Respiration Belt", - "version": "0.01", - "description": "Connects to a Go Direct Respiration Belt and shows respiration rate", - "icon": "app.png", - "tags": "health,bluetooth", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"vernierrespirate.app.js","url":"app.js"}, - {"name":"vernierrespirate.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"name":"vernierrespirate.json"}] - }, - { - "id": "gpstouch", - "name": "GPS Touch", - "version": "0.02", - "description": "A touch based GPS watch, shows OS map reference", - "icon": "gpstouch.png", - "screenshots": [{"url":"screenshot4.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot1.png"}], - "tags": "tools,app", - "supports": ["BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"geotools","url":"geotools.js"}, - {"name":"gpstouch.app.js","url":"gpstouch.app.js"}, - {"name":"gpstouch.img","url":"gpstouch.icon.js","evaluate":true} - ] - }, - { - "id": "swiperclocklaunch", - "name": "Swiper Clock Launch", - "version": "0.02", - "description": "Navigate between clock and launcher with Swipe action", - "icon": "swiperclocklaunch.png", - "type": "bootloader", - "tags": "tools, system", - "supports": ["BANGLEJS", "BANGLEJS2"], - "storage": [ - {"name":"swiperclocklaunch.boot.js","url":"boot.js"}, - {"name":"swiperclocklaunch.img","url":"icon.js","evaluate":true} - ] - }, - { - "id": "qalarm", - "name": "Q Alarm and Timer", - "shortName": "Q Alarm", - "icon": "app.png", - "version": "0.03", - "description": "Alarm and timer app with days of week and 'hard' option.", - "tags": "tool,alarm,widget", - "supports": ["BANGLEJS", "BANGLEJS2"], - "storage": [ - { "name": "qalarm.app.js", "url": "app.js" }, - { "name": "qalarm.boot.js", "url": "boot.js" }, - { "name": "qalarm.js", "url": "qalarm.js" }, - { "name": "qalarmcheck.js", "url": "qalarmcheck.js" }, - { "name": "qalarm.img", "url": "app-icon.js", "evaluate": true }, - { "name": "qalarm.wid.js", "url": "widget.js" } - ], - "data": [{ "name": "qalarm.json" }] - }, - { - "id": "emojuino", - "name": "Emojuino", - "shortName": "Emojuino", - "version": "0.03", - "description": "Emojis & Espruino: broadcast Unicode emojis via Bluetooth Low Energy.", - "icon": "emojuino.png", - "screenshots": [ - { "url": "screenshot-tx.png" }, - { "url": "screenshot-swipe.png" }, - { "url": "screenshot-welcome.png" } - ], - "type": "app", - "tags": "emoji", - "supports" : [ "BANGLEJS2" ], - "allow_emulator": true, - "readme": "README.md", - "storage": [ - { "name": "emojuino.app.js", "url": "emojuino.js" }, - { "name": "emojuino.img", "url": "emojuino-icon.js", "evaluate": true } - ] - }, - { - "id": "cliclockJS2Enhanced", - "name": "Commandline-Clock JS2 Enhanced", - "shortName": "CLI-Clock JS2", - "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"}], - "type": "clock", - "tags": "clock,cli,command,bash,shell", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"cliclockJS2Enhanced.app.js","url":"app.js"}, - {"name":"cliclockJS2Enhanced.img","url":"app.icon.js","evaluate":true} - ] - }, - { - "id": "wid_a_battery_widget", - "name": "A Battery Widget (with percentage)", - "shortName":"A Battery Widget", - "icon": "widget.png", - "version":"1.02", - "type": "widget", - "supports": ["BANGLEJS", "BANGLEJS2"], - "readme": "README.md", - "description": "Simple and slim battery widget with charge status and percentage", - "tags": "widget,battery", - "storage": [ - {"name":"wid_a_battery_widget.wid.js","url":"widget.js"} - ] - }, - { - "id": "lcars", - "name": "LCARS Clock", - "shortName":"LCARS", - "icon": "lcars.png", - "version":"0.09", - "readme": "README.md", - "supports": ["BANGLEJS2"], - "description": "Library Computer Access Retrieval System (LCARS) clock.", - "type": "clock", - "tags": "clock", - "screenshots": [{"url":"screenshot.png"}], - "storage": [ - {"name":"lcars.app.js","url":"lcars.app.js"}, - {"name":"lcars.img","url":"lcars.icon.js","evaluate":true}, - {"name":"lcars.settings.js","url":"lcars.settings.js"} - ] - }, - { "id": "binwatch", - "name": "Binary Watch", - "shortName":"BinWatch", - "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}], - "version":"0.04", - "supports": ["BANGLEJS2"], - "readme": "README.md", - "allow_emulator":true, - "description": "Famous binary watch", - "tags": "clock", - "type": "clock", - "storage": [ - {"name":"binwatch.app.js","url":"app.js"}, - {"name":"binwatch.bg176.img","url":"Background176_center.img"}, - {"name":"binwatch.bg240.img","url":"Background240_center.img"}, - {"name":"binwatch.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "hidmsicswipe", - "name": "Bluetooth Music Swipe Controls", - "shortName": "Swipe Control", - "version": "0.01", - "description": "Based on the original Bluetooth Music Controls. Swipe up/down for volume, left/right for previous and next, tap for play/pause and btn1 to lock and unlock the controls. Enable HID in settings, pair with your phone, then use this app to control music from your watch!", - "icon": "hidmsicswipe.png", - "tags": "bluetooth", - "supports": ["BANGLEJS2"], - "storage": [ - {"name":"hidmsicswipe.app.js","url":"hidmsicswipe.js"}, - {"name":"hidmsicswipe.img","url":"hidmsicswipe-icon.js","evaluate":true} - ] - }, - { - "id": "authentiwatch", - "name": "2FA Authenticator", - "shortName": "AuthWatch", - "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}], - "version": "0.04", - "description": "Google Authenticator compatible tool.", - "tags": "tool", - "interface": "interface.html", - "supports": ["BANGLEJS", "BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"authentiwatch.app.js","url":"app.js"}, - {"name":"authentiwatch.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"name":"authentiwatch.json"}] - }, - { "id": "schoolCalendar", - "name": "School Calendar", - "shortName":"SCalendar", - "icon": "CalenderLogo.png", - "version": "0.01", - "description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)", - "tags": "tool", - "readme":"README.md", - "custom":"custom.html", - "supports": ["BANGLEJS"], - "screenshots": [{"url":"screenshot_basic.png"},{"url":"screenshot_info.png"}], - "storage": [ - {"name":"schoolCalendar.app.js"}, - {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} - ], - "data": [ - {"name":"calendarItems.csv"} - ] - }, - { "id": "timecal", - "name": "TimeCal", - "shortName":"TimeCal", - "icon": "icon.png", - "version":"0.01", - "description": "TimeCal shows the Time along with a 3 week calendar", - "tags": "clock", - "type": "clock", - "supports":["BANGLEJS2"], - "storage": [ - {"name":"timecal.app.js","url":"timecal.app.js"} - ] - }, - { - "id": "a_clock_timer", - "name": "A Clock with Timer", - "version": "0.01", - "description": "A Clock with Timer, Map and Time Zones", - "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS2"], - "allow_emulator": true, - "readme": "README.md", - "storage": [ - {"name":"a_clock_timer.app.js","url":"app.js"}, - {"name":"a_clock_timer.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id":"intervalTimer", - "name":"Interval Timer", - "shortName":"Interval Timer", - "icon": "app.png", - "version":"0.01", - "description": "Interval Timer for workouts, HIIT, or whatever else.", - "tags": "timer, interval, hiit, workout", - "readme":"README.md", - "supports":["BANGLEJS2"], - "storage": [ - {"name":"intervalTimer.app.js","url":"app.js"}, - {"name":"intervalTimer.img","url":"app-icon.js","evaluate":true} - ] - }, - { "id": "93dub", - "name": "93 Dub", - "shortName":"93 Dub", - "icon": "93dub.png", - "screenshots": [{"url":"screenshot.png"}], - "version":"0.06", - "description": "Fan recreation of orviwan's 91 Dub app for the Pebble smartwatch. Uses assets from his 91-Dub-v2.0 repo", - "tags": "clock", - "type": "clock", - "supports":["BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"93dub.app.js","url":"app.js"}, - {"name":"93dub.img","url":"app-icon.js","evaluate":true} - ] - }, - { "id": "poweroff", - "name": "Poweroff", - "shortName":"Poweroff", - "version":"0.01", - "description": "Simple app to power off your Bangle.js", - "icon": "app.png", - "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} - ] -}, -{ - "id": "sensible", - "name": "SensiBLE", - "shortName": "SensiBLE", - "version": "0.05", - "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" ], - "allow_emulator": true, - "readme": "README.md", - "storage": [ - { "name": "sensible.app.js", "url": "sensible.js" }, - { "name": "sensible.img", "url": "sensible-icon.js", "evaluate": true } - ] -}, - { - "id": "widbars", - "name": "Bars Widget", - "version": "0.01", - "description": "Display several measurements as vertical bars.", - "icon": "icon.png", - "screenshots": [{"url":"screenshot.png"}], - "readme": "README.md", - "type": "widget", - "tags": "widget", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widbars.wid.js","url":"widget.js"} - ] -}, -{ - "id":"a_speech_timer", - "name":"Speech Timer", - "icon": "app.png", - "version":"1.01", - "description": "A timer designed to help keeping your speeches and presentations to time.", - "tags": "tool,timer", - "readme":"README.md", - "supports":["BANGLEJS2"], - "screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}], - "allow_emulator": true, - "storage": [ - {"name":"a_speech_timer.app.js","url":"app.js"}, - {"name":"a_speech_timer.img","url":"app-icon.js","evaluate":true} - ] -}, - { "id": "mylocation", - "name": "My Location", - "shortName":"My Location", - "icon": "mylocation.png", - "type": "app", - "screenshots": [{"url":"screenshot_1.png"}], - "version":"0.02", - "description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README", - "readme": "README.md", - "tags": "tool,utility", - "supports": ["BANGLEJS", "BANGLEJS2"], - "storage": [ - {"name":"mylocation.app.js","url":"mylocation.app.js"}, - {"name":"mylocation.img","url":"mylocation.icon.js","evaluate": true } - ], - "data": [ - {"name":"mylocation.json"} - ] - }, - { - "id": "pebble", - "name": "Pebble Clock", - "shortName": "Pebble", - "version": "0.07", - "description": "A pebble style clock to keep the rebellion going", - "dependencies": {"widpedom":"app"}, - "readme": "README.md", - "icon": "pebble.png", - "screenshots": [{"url":"pebble_screenshot.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS", "BANGLEJS2"], - "storage": [ - {"name":"pebble.app.js","url":"pebble.app.js"}, - {"name":"pebble.settings.js","url":"pebble.settings.js"}, - {"name":"pebble.img","url":"pebble.icon.js","evaluate":true} - ] - }, - { "id": "pooqroman", - "name": "pooq Roman watch face", - "shortName":"pooq Roman", - "version":"0.03", - "description": "A classic watch face with a certain dynamicity. Most amusing in 24h mode. Slide up to show more hands, down for less(!). By design does not support standard widgets, sorry!", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports" : ["BANGLEJS2"], - "allow_emulator":true, - "readme": "README.md", - "storage": [ - {"name":"pooqroman.app.js","url":"app.js"}, - {"name":"pooqroman.img","url":"app-icon.js","evaluate":true} - ], - "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": ["BANGLEJS", "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.05", - "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": "tinydraw", - "name": "TinyDraw", - "shortName":"TinyDraw", - "version":"0.01", - "type": "app", - "description": "Draw stuff in your wrist", - "icon": "app.png", - "allow_emulator": true, - "tags": "tools, keyboard, text, scribble", - "supports" : ["BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"tinydraw.app.js","url":"app.js"}, - {"name":"tinydraw.img","url":"app-icon.js","evaluate":true} - ], - "screenshots":[ - { "url":"screenshot.png" } - ] - }, - { "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.13", - "description": "Directly launch apps from the clock screen with custom patterns.", - "icon": "app.png", - "screenshots": [{"url":"manage_patterns_light.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": "slimehunt", - "name": "Slime Hunt", - "shortName":"SlimeHunt", - "icon": "app.png", - "version":"0.02", - "description": "Fight against slimes in turn based combat, try to get the highscore!", - "tags": "rpg,slime", - "supports" : ["BANGLEJS"], - "readme": "README.md", - "storage": [ - {"name":"slimehunt.app.js","url":"app.js"}, - {"name":"slimehunt.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "rebble", - "name": "Rebble Clock", - "shortName": "Rebble", - "version": "0.04", - "description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion", - "readme": "README.md", - "icon": "rebble.png", - "dependencies": {"mylocation":"app", "widpedom":"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.app.js","url":"app.js"}, - {"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", - "screenshots": [{"url":"screenshot.png"}], - "allow_emulator": true, - "version":"0.03", - "description": "Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awair device.", - "type": "clock", - "tags": "clock,tool,health", - "readme":"README.md", - "supports":["BANGLEJS2"], - "storage": [ - {"name":"awairmonitor.app.js","url":"app.js"}, - {"name":"awairmonitor.img","url":"app-icon.js","evaluate":true} - ] - }, - { "id": "pooqround", - "name": "pooq Round watch face", - "shortName":"pooq Round", - "version":"0.01", - "description": "A 24 hour analogue watchface with high legibility and a novel style.", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports" : ["BANGLEJS2"], - "allow_emulator":true, - "readme": "README.md", - "storage": [ - {"name":"pooqround.app.js","url":"app.js"}, - {"name":"pooqround.img","url":"app-icon.js","evaluate":true} - ], - "data": [ - {"name":"pooqround.json"} - ] - }, - { - "id": "coretemp", - "name": "CoreTemp", - "version": "0.02", - "description": "Display CoreTemp device sensor data", - "icon": "coretemp.png", - "type": "app", - "tags": "health", - "readme": "README.md", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"coretemp.wid.js","url":"widget.js"}, - {"name":"coretemp.app.js","url":"coretemp.js"}, - {"name":"coretemp.settings.js","url":"settings.js"}, - {"name":"coretemp.img","url":"coretemp-icon.js","evaluate":true}, - {"name":"coretemp.boot.js","url":"boot.js"} - ], - "data": [{"name":"coretemp.json","url":"app-settings.json"}], - "screenshots": [{"url":"screenshot.png"}] - }, - { - "id": "showimg", - "name": "simple image viewer", - "shortName":"showImage", - "version":"0.2", - "description": "Displays the image in \"showimg.user.img\". The file has to be uploaded via the espruino IDE. Returns to watch face after 60s or button push. I use it to display my vaccination certificate.", - "icon": "app.png", - "tags": "tool", - "supports" : ["BANGLEJS2"], - "storage": [ - {"name":"showimg.app.js","url":"app.js"}, - {"name":"showimg.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "lapcounter", - "name": "Lap Counter", - "version": "0.01", - "description": "Click button to count laps. Shows count and total time snapshot (like a stopwatch, but laid back).", - "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}], - "type": "app", - "tags": "tool,outdoors", - "readme":"README.md", - "supports": ["BANGLEJS", "BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"lapcounter.app.js","url":"app.js"}, - {"name":"lapcounter.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "pebbled", - "name": "Pebble Clock with distance", - "shortName": "Pebble + distance", - "version": "0.1", - "description": "Fork of Pebble Clock with distance in KM. Both step count and the distance are on the main screen. Default step length = 0.75m (can be changed in settings).", - "readme": "README.md", - "icon": "pebbled.png", - "screenshots": [{"url":"pebble_screenshot.png"}], - "type": "clock", - "tags": "clock,distance", - "supports": ["BANGLEJS2"], - "storage": [ - {"name":"pebbled.app.js","url":"pebbled.app.js"}, - {"name":"pebbled.settings.js","url":"pebbled.settings.js"}, - {"name":"pebbled.img","url":"pebbled.icon.js","evaluate":true} - ] - }, - { "id": "circlesclock", - "name": "Circles clock", - "shortName":"Circles clock", - "version":"0.03", - "description": "A clock with circles for different data at the bottom in a probably familiar style", - "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}], - "dependencies": {"widpedom":"app"}, - "type": "clock", - "tags": "clock", - "supports" : ["BANGLEJS2"], - "allow_emulator":true, - "readme": "README.md", - "storage": [ - {"name":"circlesclock.app.js","url":"app.js"}, - {"name":"circlesclock.img","url":"app-icon.js","evaluate":true}, - {"name":"circlesclock.settings.js","url":"settings.js"} - ], - "data": [ - {"name":"circlesclock.json"} - ] - }, - { "id": "contourclock", - "name": "Contour Clock", - "shortName" : "Contour Clock", - "version":"0.01", - "icon": "app.png", - "description": "A Minimalist clockface with large Digits. Looks best with the dark theme", - "screenshots" : [{"url":"screenshot.png"}], - "tags": "clock", - "allow_emulator":true, - "supports" : ["BANGLEJS2"], - "type": "clock", - "storage": [ - {"name":"contourclock.app.js","url":"app.js"}, - {"name":"contourclock.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "ltherm", - "name": "Localized Thermometer", - "shortName": "Thermometer", - "version": "0.01", - "description": "Displays the current temperature in localized units.", - "icon": "thermf.png", - "tags": "tool", - "supports": ["BANGLEJS2"], - "allow_emulator": true, - "readme": "README.md", - "storage": [ - {"name":"ltherm.app.js","url":"app.js"}, - {"name":"ltherm.img","url":"icon.js","evaluate":true} - ] - }, - { - "id": "presentor", - "name": "Presentor", - "version": "3.0", - "description": "Use your Bangle to present!", - "icon": "app.png", - "type": "app", - "tags": "tool,bluetooth", - "interface": "interface.html", - "readme":"README.md", - "supports": ["BANGLEJS", "BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"presentor.app.js","url":"app.js"}, - {"name":"presentor.img","url":"app-icon.js","evaluate":true}, - {"name":"presentor.json","url":"settings.json"} - ] - }, - { - "id": "slash", - "name": "Slash Watch", - "shortName":"Slash", - "icon": "slash.png", - "screenshots": [{"url":"screenshot.png"}], - "version":"0.01", - "description": "Slash Watch based on Pebble watch face by Nikki.", - "tags": "clock", - "type": "clock", - "supports":["BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "storage": [ - {"name":"slash.app.js","url":"app.js"}, - {"name":"slash.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "promenu", - "name": "Pro Menu", - "version": "0.01", - "description": "Replace Bangle.js 1's built in menu function.", - "icon": "icon.png", - "type": "boot", - "tags": "system", - "supports": ["BANGLEJS"], - "screenshots": [{"url":"pro-menu-screenshot.png"}], - "storage": [ - {"name":"promenu.boot.js","url":"boot.js"}, - {"name":"promenu.img","url":"promenuIcon.js","evaluate":true} - ] - }, - { - "id": "touchtimer", - "name": "Touch Timer", - "shortName": "Touch Timer", - "version": "0.02", - "description": "Quickly and easily create a timer with touch-only input. The time can be easily set with a number pad.", - "icon": "app.png", - "tags": "tools", - "supports": ["BANGLEJS2"], - "readme": "README.md", - "screenshots": [{"url":"0_light_timer_edit.png"},{"url":"1_light_timer_ready.png"},{"url":"2_light_timer_running.png"},{"url":"3_light_timer_finished.png"}], - "storage": [ - { "name": "touchtimer.app.js", "url": "app.js" }, - { "name":"touchtimer.settings.js", "url":"settings.js"}, - { "name": "touchtimer.img", "url": "app-icon.js", "evaluate": true } - ], - "data": [{"name":"touchtimer.data.json"}] - }, - { - "id": "teatimer", - "name": "Tea Timer", - "version": "1.00", - "description": "A simple timer. You can easyly set up the time.", - "icon": "teatimer.png", - "type": "app", - "tags": "tool", - "supports": ["BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"teatimer.app.js","url":"app.js"}, - {"name":"teatimer.img","url":"app-icon.js","evaluate":true} - ], - "screenshots": [ - {"url":"TeatimerStart.jpg"}, - {"url":"TeatimerHelp.jpg"}, - {"url":"TeatimerRun.jpg"}, - {"url":"TeatimerUp.jpg"} - ] - }, - { - "id": "swp2clk", - "name": "Swipe back to the Clock", - "shortName": "Swipe to Clock", - "version": "0.01", - "description": "Let's you swipe from left to right on any app to return back to the clock face. Please configure in the settings app after installing to activate, since its disabled by default.", - "icon": "app.png", - "type": "boot", - "tags": "tools", - "supports": ["BANGLEJS2"], - "readme": "README.md", - "storage": [ - { "name": "swp2clk.boot.js", "url": "boot.js" }, - {"name":"swp2clk.settings.js","url":"settings.js"} - ], - "data": [{"name":"swp2clk.data.json"}] - }, - { - "id":"colorwheel", - "name":"Color Wheel", - "tags":"app,tool", - "version":"0.01", - "description":"a tappable wheel of good-looking colors", - "readme":"README.md", - "supports":["BANGLEJS2"], - "allow_emulator":true, - "icon":"colorwheel.png", - "storage": [ - {"name":"colorwheel.app.js","url":"app.js"}, - {"name":"colorwheel.img","url":"app-icon.js","evaluate":true} - ] - }, - { "id": "minimal_clock", - "name": "Minimal Analog Clock", - "shortName":"Minimal Clock", - "version":"0.03", - "description": "a minimal analog clock - just with some hands and no clock face", - "icon": "app-icon.png", - "type": "clock", - "tags": "clock", - "supports" : ["BANGLEJS2"], - "allow_emulator": true, - "screenshots": [{"url":"app-screenshot.png"}], - "readme": "README.md", - "storage": [ - {"name":"minimal_clock.app.js","url":"app.js"}, - {"name":"minimal_clock.img","url":"app-icon.js","evaluate":true} - ] - }, - { "id": "simple_clock", - "name": "Simple Analog Clock", - "shortName":"Simple Clock", - "version":"0.02", - "description": "a simple, yet stylish, analog clock", - "icon": "app-icon.png", - "type": "clock", - "tags": "clock", - "supports" : ["BANGLEJS2"], - "allow_emulator": true, - "screenshots": [{"url":"app-screenshot.png"}], - "readme": "README.md", - "storage": [ - {"name":"simple_clock.app.js","url":"app.js"}, - {"name":"simple_clock.img","url":"app-icon.js","evaluate":true} - ] - }, - { "id": "colorful_clock", - "name": "Colorful Analog Clock", - "shortName":"Colorful Clock", - "version":"0.02", - "description": "a colorful analog clock", - "icon": "app-icon.png", - "type": "clock", - "tags": "clock", - "supports" : ["BANGLEJS2"], - "allow_emulator": true, - "screenshots": [{"url":"app-screenshot.png"}], - "readme": "README.md", - "storage": [ - {"name":"colorful_clock.app.js","url":"app.js"}, - {"name":"colorful_clock.img","url":"app-icon.js","evaluate":true} - ] - }, - { "id": "themesetter", - "name": "Theme Setter", - "shortName":"Theme Setter", - "version":"0.04", - "description": "a comfortable way to configure theme colors", - "icon": "app-icon.png", - "type": "app", - "tags": "tool", - "supports" : ["BANGLEJS2"], - "allow_emulator": true, - "screenshots": [{"url":"app-screenshot.png"}], - "readme": "README.md", - "storage": [ - {"name":"themesetter.app.js","url":"app.js"}, - {"name":"themesetter.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "widviztime", - "name": "Widget Autohide Widget", - "shortName": "Viz Time Widget", - "version": "0.01", - "description": "The widgets will be shown for four seconds after the device is unlocked.", - "icon": "eye.png", - "type": "widget", - "tags": "widget", - "readme":"README.md", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widviztime.wid.js","url":"widget.js"} - ] - }, - { - "id": "supf", - "name": "Simple Clock with Date", - "shortName": "supf Clock", - "version": "0.01", - "description": "Simple Clock with seconds and date in custom language. Install 'Languages' to get localized names.", - "icon": "icon.png", - "screenshots": [{"url":"screenshot_supf.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS2"], - "allow_emulator": true, - "readme": "README.md", - "storage": [ - {"name":"supf.app.js","url":"app.js"}, - {"name":"supf.img","url":"icon.js","evaluate":true} - ] - }, - { "id": "andark", - "name": "Analog Dark", - "shortName":"AnDark", - "version":"0.04", - "description": "analog clock face without disturbing widgets", - "icon": "andark_icon.png", - "type": "clock", - "tags": "clock", - "supports" : ["BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"andark.app.js","url":"app.js"}, - {"name":"andark.img","url":"app_icon.js","evaluate":true} - ] - }, - { - "id": "diract", - "name": "DirAct", - "shortName": "DirAct", - "version": "0.01", - "description": "Proximity interaction detection.", - "icon": "diract.png", - "type": "app", - "tags": "tool,sensors", - "supports" : [ "BANGLEJS2" ], - "allow_emulator": false, - "readme": "README.md", - "storage": [ - { "name": "diract.app.js", "url": "diract.js" }, - { "name": "diract.img", "url": "diract-icon.js", "evaluate": true } - ] - }, - { - "id": "sonicclk", - "name": "Sonic Clock", - "version": "1.11", - "description": "A classic sonic clock featuring run, stop and wait animations.", - "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS2"], - "allow_emulator": true, - "readme": "README.md", - "storage": [ - {"name":"sonicclk.app.js","url":"app.js"}, - {"name":"sonicclk.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "touchmenu", - "name": "TouchMenu", - "version": "0.01", - "description": "Redesigned menu that uses the full touchscreen on the Bangle.js 2", - "screenshots": [{"url":"touchmenu.gif"}], - "icon": "touchmenu.png", - "type": "bootloader", - "tags": "tool", - "supports": ["BANGLEJS2"], - "storage": [ - {"name":"touchmenu.boot.js","url":"touchmenu.boot.js"} - ] - }, - { - "id": "puzzle15", - "name": "15 puzzle", - "version": "0.05", - "description": "A 15 puzzle game with drag gesture interface", - "readme":"README.md", - "icon": "puzzle15.app.png", - "screenshots": [{"url":"screenshot.png"}], - "type": "app", - "tags": "game", - "supports": ["BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"puzzle15.app.js","url":"puzzle15.app.js"}, - {"name":"puzzle15.settings.js","url":"puzzle15.settings.js"}, - {"name":"puzzle15.img","url":"puzzle15.app-icon.js","evaluate":true} - ], - "data": [{"name":"puzzle15.json"}] - } + +{%- include_relative {{ apps.first }} -%} + +{%- for app in apps offset:1 -%} +,{%- include_relative {{ app }} -%} +{%- endfor -%} + ] diff --git a/apps/1button/metadata.json b/apps/1button/metadata.json new file mode 100644 index 000000000..6cfcb9310 --- /dev/null +++ b/apps/1button/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "1button", + "name": "One-Button-Tracker", + "version": "0.01", + "description": "A widget that turns BTN1 into a tracker, records time of button press/release.", + "icon": "widget.png", + "type": "widget", + "tags": "tool,quantifiedself,widget", + "supports": ["BANGLEJS"], + "readme": "README.md", + "interface": "interface.html", + "storage": [ + {"name":"1button.wid.js","url":"widget.js"} + ], + "data": [{"name":"one_button_presses.csv","storageFile":true}] +} diff --git a/apps/93dub/metadata.json b/apps/93dub/metadata.json new file mode 100644 index 000000000..524780792 --- /dev/null +++ b/apps/93dub/metadata.json @@ -0,0 +1,17 @@ +{ "id": "93dub", + "name": "93 Dub", + "shortName":"93 Dub", + "icon": "93dub.png", + "screenshots": [{"url":"screenshot.png"}], + "version":"0.06", + "description": "Fan recreation of orviwan's 91 Dub app for the Pebble smartwatch. Uses assets from his 91-Dub-v2.0 repo", + "tags": "clock", + "type": "clock", + "supports":["BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"93dub.app.js","url":"app.js"}, + {"name":"93dub.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/BLEcontroller/metadata.json b/apps/BLEcontroller/metadata.json new file mode 100644 index 000000000..bb28b2360 --- /dev/null +++ b/apps/BLEcontroller/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "BLEcontroller", + "name": "BLE Customisable Controller with Joystick", + "shortName": "BLE Controller", + "version": "0.01", + "description": "A configurable controller for BLE devices and robots, with a basic four direction joystick. Designed to be easy to customise so you can add your own menus.", + "icon": "BLEcontroller.png", + "tags": "tool,bluetooth", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": false, + "storage": [ + {"name":"BLEcontroller.app.js","url":"app.js"}, + {"name":"BLEcontroller.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/HRV/metadata.json b/apps/HRV/metadata.json new file mode 100644 index 000000000..9e0aed176 --- /dev/null +++ b/apps/HRV/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "HRV", + "name": "Heart Rate Variability monitor", + "shortName": "HRV monitor", + "version": "0.04", + "description": "Heart Rate Variability monitor, see Readme for more info", + "icon": "hrv.png", + "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"HRV.app.js","url":"app.js"}, + {"name":"HRV.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/UI4swatch/metadata.json b/apps/UI4swatch/metadata.json new file mode 100644 index 000000000..379d173c3 --- /dev/null +++ b/apps/UI4swatch/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "UI4swatch", + "name": "UI 4 swatch", + "shortName": "UI 4 swatch", + "version": "0.01", + "description": "A UI/UX for espruino smartwatches, displays dinamically calc. x,y coordinates.", + "icon": "app.png", + "tags": "Color,input,buttons,touch,UI", + "supports": ["BANGLEJS"], + "readme": "README.md", + "screenshots": [{"url":"UI4swatch_icon.png"},{"url":"UI4swatch_s1.png"}], + "storage": [ + {"name":"UI4swatch.app.js","url":"app.js"}, + {"name":"UI4swatch.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/_example_app/add_to_apps.json b/apps/_example_app/metadata.json similarity index 89% rename from apps/_example_app/add_to_apps.json rename to apps/_example_app/metadata.json index cc28e1e93..e0d664338 100644 --- a/apps/_example_app/add_to_apps.json +++ b/apps/_example_app/metadata.json @@ -1,4 +1,3 @@ -// Create an entry in apps.json as follows: { "id": "7chname", "name": "My app's human readable name", "shortName":"Short Name", diff --git a/apps/_example_widget/add_to_apps.json b/apps/_example_widget/metadata.json similarity index 89% rename from apps/_example_widget/add_to_apps.json rename to apps/_example_widget/metadata.json index b55adce9d..ad4b7537d 100644 --- a/apps/_example_widget/add_to_apps.json +++ b/apps/_example_widget/metadata.json @@ -1,4 +1,3 @@ -// Create an entry in apps.json as follows: { "id": "7chname", "name": "My widget's human readable name", "shortName":"Short Name", diff --git a/apps/a_clock_timer/metadata.json b/apps/a_clock_timer/metadata.json new file mode 100644 index 000000000..cc61fc57b --- /dev/null +++ b/apps/a_clock_timer/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "a_clock_timer", + "name": "A Clock with Timer", + "version": "0.01", + "description": "A Clock with Timer, Map and Time Zones", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "readme": "README.md", + "storage": [ + {"name":"a_clock_timer.app.js","url":"app.js"}, + {"name":"a_clock_timer.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/a_speech_timer/metadata.json b/apps/a_speech_timer/metadata.json new file mode 100644 index 000000000..6255a6b92 --- /dev/null +++ b/apps/a_speech_timer/metadata.json @@ -0,0 +1,16 @@ +{ +"id":"a_speech_timer", +"name":"Speech Timer", +"icon": "app.png", +"version":"1.01", +"description": "A timer designed to help keeping your speeches and presentations to time.", +"tags": "tool,timer", +"readme":"README.md", +"supports":["BANGLEJS2"], +"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}], +"allow_emulator": true, +"storage": [ + {"name":"a_speech_timer.app.js","url":"app.js"}, + {"name":"a_speech_timer.img","url":"app-icon.js","evaluate":true} +] +} diff --git a/apps/about/metadata.json b/apps/about/metadata.json new file mode 100644 index 000000000..6c22bdc56 --- /dev/null +++ b/apps/about/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "about", + "name": "About", + "version": "0.12", + "description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers", + "icon": "app.png", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "screenshots": [{"url":"bangle1-about-screenshot.png"}], + "allow_emulator": true, + "storage": [ + {"name":"about.app.js","url":"app-bangle1.js","supports": ["BANGLEJS"]}, + {"name":"about.app.js","url":"app-bangle2.js","supports": ["BANGLEJS2"]}, + {"name":"about.img","url":"app-icon.js","evaluate":true} + ], + "sortorder": -4 +} diff --git a/apps/ac_ac/Customizer.html b/apps/ac_ac/Customizer.html new file mode 100644 index 000000000..f2aa79920 --- /dev/null +++ b/apps/ac_ac/Customizer.html @@ -0,0 +1,890 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Please customize your analog clock for the Bangle.js 2 according to your needs. + When finished, click on "Upload" at the bottom of this form. +

+ (Pressing "Upload" will also backup your current configuration so that you + won't have to enter the same settings over and over again when you come back + to this page later) +

+ +

Clock Size Calculation

+ +

+ Click on the desired clock size calculator (if you installed some widgets + on your Bangle.js 2, the smart one may produce larger clock faces than the + simple one): +

+ + + + + + + + +
+
+ simple +
+
+ smart +
+
+ (custom) +
+

+ If you prefer a "custom" clock size calculator, please enter the URL + of its JavaScript module below: +

+ custom URL: +

+ +

Clock Face

+ +

+ Click on the desired clock face: +

+ + + + + + + + + + + + +
+
+ (none) +
+
+ four-numbered +
+
+ twelve-numbered +
+
+ "rainbow"
colored +
+
+ (custom) +
+

+ If you prefer a "custom" clock face, please enter the URL + of its JavaScript module below: +

+ custom URL: +

+ Clock faces are drawn in the configured foreground and background colors + (you may select them at the end of this form) +

+ "Four-numbered" clock faces may draw indian-arabic or roman numerals. Which do you prefer? +

+ indian-arabic (3, 6, 9, 12)
+ roman (III, VI, IX, XII) +

+ The "twelve-numbered" and "rainbow"-colored faces may be drawn with or without + dots marking the position of every minute. Which variant do you prefer? +

+ without dots
+ with dots +

+ +

Clock Hands

+ +

+ Click on the desired clock hands: +

+ + + + + + + + + + +
+
+ simple +
+
+ rounded +
+
+ hollow +
+
+ (custom) +
+

+ If you prefer "custom" clock hands, please enter the URL + of their JavaScript module below: +

+ custom URL: +

+ Clock hands are drawn in the configured foreground and background colors + (you may select them at the end of this form) +

+ Hollow clock hands may optionally be filled with a given color. If you have + chosen hollow hands, please specify the desired fill mode and color below: +

+ Hollow Hand Fill Color: +

+ + + + + + + + + + +

+ Additionally, all clock hands may be drawn with or without second hands. + If you want them to be drawn, please click on their desired color below + (or choose "themed" to use your Bangle's configured theme) - if not, just + select "none": +

+ Second Hand Color: +

+ + + + + + + + + + +

+ +

Complications

+ +

+ Complications are small displays for additional information. If you want + one or multiple complications to be added to your clock, you'll have to + specify which one to be loaded and where it should be placed. +

+ Up to 6 possible positions exist (top-left, top-right, left, right, + bottom-left and bottom-right). Alternatively, the positions "top-left" and + "top-right" may be traded for a slightly larger complication at position + "top" or "bottom-left" and "bottom-right" for one at the "bottom": +

+ + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
top-left:
  Complication: + +
custom URL:
top:
  Complication: + +
custom URL:
top-right:
  Complication: + +
custom URL:
left:
  Complication: + +
custom URL:
right:
  Complication: + +
custom URL:
bottom-left:
  Complication: + +
custom URL:
bottom:
  Complication: + +
custom URL:
bottom-right:
  Complication: + +
custom URL:
+

+ +

Settings

+ +

+ Color faces, hands and complications are often drawn using configurable + foreground and background colors. +

+ Here you may specify these colors. Click on a color to select it - or on + "themed" if you want the clock to use the currently configured theme on + your Bangle.js 2: +

+ Background Color: +

+ + + + + + + + + +

+ Foreground Color: +

+ + + + + + + + + +

+ When you are satisfied with your configuration, just click on "Upload" in + order to generate the specified clock and upload it to your Bangle.js 2: +

+ + + +

+ This application is based on the author's + Analog Clock Construction Kit (ACCK). + If you need a different "clockwork", clock size calculation or clock face, + or specific clock hands or complications, just follow the link to learn how to + implement your own clock parts. +

+ + + diff --git a/apps/ac_ac/README.md b/apps/ac_ac/README.md new file mode 100644 index 000000000..05e5f4798 --- /dev/null +++ b/apps/ac_ac/README.md @@ -0,0 +1,34 @@ +# AC-AC - A Configurable Analog Clock # + +This app implements an analog clock with various faces, hands and complications +to choose from before uploading to a Bangle.js 2. + +It is based on the [Analog Clock Construction Kit (ACCK)](https://github.com/rozek/banglejs-2-analog-clock-construction-kit) +and makes most of the currently implemented parts available with a few mouse +clicks - just click on "Upload" and you will be directed to a web form where +you compose your very own, personal analog clock. + +You currently have the choice between + +* 2 different clock sizes, +* 4 different clock faces, +* 3 different clock hands and +* 4 different complications + +Alternatively, you may specify the GitHub URL of ACCK compatible modules for +external clock sizes, faces, hands or complications. + +Additionally, you may use the currently configured global theme or configure +your own colors for clock fore- and background and second hands. + +Consequently, even without external modules you already have the choice between +102144 combinations! + + + +## License ## + +[MIT License](LICENSE) diff --git a/apps/ac_ac/RainbowClockFace.png b/apps/ac_ac/RainbowClockFace.png new file mode 100644 index 000000000..2defa759b Binary files /dev/null and b/apps/ac_ac/RainbowClockFace.png differ diff --git a/apps/ac_ac/app-icon.js b/apps/ac_ac/app-icon.js new file mode 100644 index 000000000..20caf2c8e --- /dev/null +++ b/apps/ac_ac/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgn/ABH+AQPvBpIAI/n8/3f5/PCp/v9oHF7w1CABffGxAYMH4f9z/514YDCxW/O4gFBxwHD/ZEL7/9GgX8GwQLCBQQXH/uP/Hf/2N44IBAgIXJ7oaD/3v/3uAYIIB9wQGAA2+/iRG5oSIM4f+1nrPYgAB3aHIAC77QYYRoCAAP676ICABXYFIntDoPf3+PC5f+BoPOX4vPNBn7IogEB/eu3QXC9wNEAAeKBIP+dgbSCDYMwgEApQVEygPCeRH8iAWBAAMHPwXDgoRGAonACwYABgN5uMAC4q8GC4U0DQsAggRF9gXFgggB/2hC4kdVAQCBVAX7xwXCVAnGCwUadAeeDYfr7IhEAAf93e+A4gpB9yRB/mqcgndRgQAHzqRE1gEC/KoCjLZEsgCB9evO4gOC/RyEgqdC2KnFO4S/KgFYsC/Ga5EBs1AX5bXHgx1C2YXEnp7GCARgB4AfE64WCnawFCgf9VAK/G/3M7zWDz4PF/maXJIAD7D8EVAP85QXN3OP/42DfoQXN/wvE/ySGABa8FAC37AgepVwQ9E1SfBAAJIEAAnrBQ39xgwJ7pRHFQX+3QECCAbyG9bPDzwXC9QMBdgQXIAAf41wEC5pLCJJBcF9fZQ5IAGYYn81q7RJQwWC/wXM9/tA4veCxooDIAPv55PEABwpB97rDAAw")) \ No newline at end of file diff --git a/apps/ac_ac/app-icon.png b/apps/ac_ac/app-icon.png new file mode 100644 index 000000000..b83541133 Binary files /dev/null and b/apps/ac_ac/app-icon.png differ diff --git a/apps/ac_ac/app-screenshot.png b/apps/ac_ac/app-screenshot.png new file mode 100644 index 000000000..0aef3fa38 Binary files /dev/null and b/apps/ac_ac/app-screenshot.png differ diff --git a/apps/ac_ac/app.js b/apps/ac_ac/app.js new file mode 100644 index 000000000..1d9b2e3c6 --- /dev/null +++ b/apps/ac_ac/app.js @@ -0,0 +1,2 @@ +let Clockwork = require('https://raw.githubusercontent.com/rozek/banglejs-2-simple-clockwork/main/Clockwork.js'); +Clockwork.windUp(); \ No newline at end of file diff --git a/apps/ac_ac/custom.png b/apps/ac_ac/custom.png new file mode 100644 index 000000000..14d797ba3 Binary files /dev/null and b/apps/ac_ac/custom.png differ diff --git a/apps/ac_ac/fournumberedClockFace.png b/apps/ac_ac/fournumberedClockFace.png new file mode 100644 index 000000000..391303b31 Binary files /dev/null and b/apps/ac_ac/fournumberedClockFace.png differ diff --git a/apps/ac_ac/hollowClockHands.png b/apps/ac_ac/hollowClockHands.png new file mode 100644 index 000000000..2dce42ef5 Binary files /dev/null and b/apps/ac_ac/hollowClockHands.png differ diff --git a/apps/ac_ac/largePlaceholders.png b/apps/ac_ac/largePlaceholders.png new file mode 100644 index 000000000..b7272e57c Binary files /dev/null and b/apps/ac_ac/largePlaceholders.png differ diff --git a/apps/ac_ac/metadata.json b/apps/ac_ac/metadata.json new file mode 100644 index 000000000..a4f3de0ac --- /dev/null +++ b/apps/ac_ac/metadata.json @@ -0,0 +1,18 @@ +{ "id": "ac_ac", + "name": "A Configurable Analog Clock", + "shortName":"Configurable Clock", + "version":"0.03", + "description": "AC-AC, a highly customizable analog clock with several clock faces, hands and complications to choose from", + "icon": "app-icon.png", + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "allow_emulator": false, + "screenshots": [{"url":"app-screenshot.png"}], + "readme": "README.md", + "custom": "Customizer.html", + "storage": [ + {"name":"ac_ac.app.js","url":"app.js"}, + {"name":"ac_ac.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/ac_ac/none.png b/apps/ac_ac/none.png new file mode 100644 index 000000000..6f8d8ae14 Binary files /dev/null and b/apps/ac_ac/none.png differ diff --git a/apps/ac_ac/roundedClockHands.png b/apps/ac_ac/roundedClockHands.png new file mode 100644 index 000000000..cbd48e856 Binary files /dev/null and b/apps/ac_ac/roundedClockHands.png differ diff --git a/apps/ac_ac/simpleClockHands.png b/apps/ac_ac/simpleClockHands.png new file mode 100644 index 000000000..820606f27 Binary files /dev/null and b/apps/ac_ac/simpleClockHands.png differ diff --git a/apps/ac_ac/simpleClockSize.png b/apps/ac_ac/simpleClockSize.png new file mode 100644 index 000000000..49650586e Binary files /dev/null and b/apps/ac_ac/simpleClockSize.png differ diff --git a/apps/ac_ac/smallPlaceholders.png b/apps/ac_ac/smallPlaceholders.png new file mode 100644 index 000000000..43569e56d Binary files /dev/null and b/apps/ac_ac/smallPlaceholders.png differ diff --git a/apps/ac_ac/smartClockSize.png b/apps/ac_ac/smartClockSize.png new file mode 100644 index 000000000..6891acc89 Binary files /dev/null and b/apps/ac_ac/smartClockSize.png differ diff --git a/apps/ac_ac/twelvenumberedClockFace.png b/apps/ac_ac/twelvenumberedClockFace.png new file mode 100644 index 000000000..fc04d865e Binary files /dev/null and b/apps/ac_ac/twelvenumberedClockFace.png differ diff --git a/apps/accelgraph/ChangeLog b/apps/accelgraph/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/accelgraph/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/accelgraph/app-icon.js b/apps/accelgraph/app-icon.js new file mode 100644 index 000000000..d45b8cc63 --- /dev/null +++ b/apps/accelgraph/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA/4AB304ief85L/ABNVAAwKCgILHoALBgoLHqALOrVVr4BEBZIFBBYiaCAAPq2oLQEYlqF5VrBZWnBZWvBZNWz4LGBoQLHJ4O///6v/1BZHa/4LFLYOlr9pR49r1ILJ09qr4ZBBY2vrWdBY5PBq2uyoLIquqBY5bBKoZTFLYILJJ4STDBY77IJ4QLUJ4QLU1QAE0oLPqoAGBZ0BBY9ABYMABY4KCAH4AGA=")) diff --git a/apps/accelgraph/app.js b/apps/accelgraph/app.js new file mode 100644 index 000000000..a59d636d2 --- /dev/null +++ b/apps/accelgraph/app.js @@ -0,0 +1,24 @@ +Bangle.loadWidgets(); +g.clear(1); +Bangle.drawWidgets(); +var R = Bangle.appRect; + +var x = 0; +var last; + +function getY(v) { + return (R.y+R.y2 + v*R.h/2)/2; +} +Bangle.on('accel', a => { + g.reset(); + if (last) { + g.setColor("#f00").drawLine(x-1,getY(last.x),x,getY(a.x)); + g.setColor("#0f0").drawLine(x-1,getY(last.y),x,getY(a.y)); + g.setColor("#00f").drawLine(x-1,getY(last.z),x,getY(a.z)); + } + last = a;x++; + if (x>=g.getWidth()) { + x = 1; + g.clearRect(R); + } +}); diff --git a/apps/accelgraph/app.png b/apps/accelgraph/app.png new file mode 100644 index 000000000..b0ba00ee7 Binary files /dev/null and b/apps/accelgraph/app.png differ diff --git a/apps/accelgraph/metadata.json b/apps/accelgraph/metadata.json new file mode 100644 index 000000000..e4c1ae0a5 --- /dev/null +++ b/apps/accelgraph/metadata.json @@ -0,0 +1,14 @@ +{ "id": "accelgraph", + "name": "Accelerometer Graph", + "shortName":"Accel Graph", + "version":"0.01", + "description": "A simple app to draw a graph of data from the accelerometer on the screen", + "icon": "app.png", + "tags": "tool,debug", + "supports" : ["BANGLEJS","BANGLEJS2"], + "screenshots": [{"url":"screenshot.png"}], + "storage": [ + {"name":"accelgraph.app.js","url":"app.js"}, + {"name":"accelgraph.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/accelgraph/screenshot.png b/apps/accelgraph/screenshot.png new file mode 100644 index 000000000..404243d85 Binary files /dev/null and b/apps/accelgraph/screenshot.png differ diff --git a/apps/accellog/metadata.json b/apps/accellog/metadata.json new file mode 100644 index 000000000..a30c9a6fc --- /dev/null +++ b/apps/accellog/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "accellog", + "name": "Acceleration Logger", + "shortName": "Accel Log", + "version": "0.03", + "description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC", + "icon": "app.png", + "tags": "outdoor", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "interface": "interface.html", + "storage": [ + {"name":"accellog.app.js","url":"app.js"}, + {"name":"accellog.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"wildcard":"accellog.?.csv"}] +} diff --git a/apps/accelrec/metadata.json b/apps/accelrec/metadata.json new file mode 100644 index 000000000..8b082c8bc --- /dev/null +++ b/apps/accelrec/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "accelrec", + "name": "Acceleration Recorder", + "shortName": "Accel Rec", + "version": "0.02", + "description": "This app puts the Bangle's accelerometer into 100Hz mode and reads 2 seconds worth of data after movement starts. The data can then be exported back to the PC.", + "icon": "app.png", + "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", + "interface": "interface.html", + "storage": [ + {"name":"accelrec.app.js","url":"app.js"}, + {"name":"accelrec.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"wildcard":"accelrec.?.csv"}] +} diff --git a/apps/aclock/metadata.json b/apps/aclock/metadata.json new file mode 100644 index 000000000..c483a4e8c --- /dev/null +++ b/apps/aclock/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "aclock", + "name": "Analog Clock", + "version": "0.15", + "description": "An Analog Clock", + "icon": "clock-analog.png", + "screenshots": [{"url":"screenshot_analog.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"aclock.app.js","url":"clock-analog.js"}, + {"name":"aclock.img","url":"clock-analog-icon.js","evaluate":true} + ] +} diff --git a/apps/acmaze/ChangeLog b/apps/acmaze/ChangeLog new file mode 100644 index 000000000..b8c1ec0b5 --- /dev/null +++ b/apps/acmaze/ChangeLog @@ -0,0 +1,3 @@ +0.01: New App! +0.02: Faster maze generation +0.03: Avoid clearing bottom widgets diff --git a/apps/acmaze/README.md b/apps/acmaze/README.md new file mode 100644 index 000000000..4724eea3e --- /dev/null +++ b/apps/acmaze/README.md @@ -0,0 +1,17 @@ +# AccelaMaze + +Tilt the watch to roll a ball through a maze. + +![Screenshot](screenshot.png) + +## Usage + +* Use the menu to select difficulty level (or exit). +* Wait until the maze gets generated and a red ball appears. +* Tilt the watch to get the ball into the green cell. + +At any time you can click the button to return to the menu. + +## Creator + +[Nimrod Kerrett](https://zzzen.com) diff --git a/apps/acmaze/app-icon.js b/apps/acmaze/app-icon.js new file mode 100644 index 000000000..8bd043b8b --- /dev/null +++ b/apps/acmaze/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwggaXh3M53/AA3yl4IHn//+EM5nMAoIX/C4RfCC4szmcxC4QFBAAUxC4UPAwIOB+YCCiMRkAFCkIGBAAQfBC4IUEAQhHIAAQX/C5EDmcyCgUTAoYXDR4kzC4UBPoKVB+YFFAQSPBiAKBiCnDGoZECABDUCa4YX/C5qPBQwoXGkczmC/FQYSSCVQSSCEwQOCC4hKFX4QXCd5YX/C4qMEmQXITAinDPoIADTwSPFkKMBX47RGI47XIC/4XCgZ9DQYYABmKYBmIXFkczmEBRIK/CQYQIBkECSoiSCA4MQa5pEFd6IX/RgMyC6H/QASVCRIS/EAQrXFJQoX/C6kDRQIXCiYFD+QFBmIUCkYFD+CJBiSPCRwIFFSoQFCiF3u9wI4gAO+wXW+IXygAAW")) diff --git a/apps/acmaze/app.js b/apps/acmaze/app.js new file mode 100644 index 000000000..16a1ce561 --- /dev/null +++ b/apps/acmaze/app.js @@ -0,0 +1,287 @@ +const MARGIN = 25; +const WALL_RIGHT = 1, WALL_DOWN = 2; +const STATUS_GENERATING = 0, STATUS_PLAYING = 1, + STATUS_SOLVED = 2, STATUS_ABORTED = -1; + +function Maze(n) { + this.n = n; + this.status = STATUS_GENERATING; + this.wall_length = Math.floor((g.getHeight()-2*MARGIN)/n); + this.total_length = this.wall_length*n; + this.margin = Math.floor((g.getHeight()-this.total_length)/2); + this.ball_x = 0; + this.ball_y = 0; + // This voodoo is needed because otherwise + // bottom line widgets (like digital clock) + // disappear during maze generation + Bangle.drawWidgets(); + g.setColor(g.theme.fg); + for (let i=0; i<=n; i++) { + g.drawRect( + this.margin, this.margin+i*this.wall_length, + g.getWidth()-this.margin, this.margin+i*this.wall_length + ); + g.drawRect( + this.margin+i*this.wall_length, this.margin, + this.margin+i*this.wall_length, g.getHeight() - this.margin + ); + } + this.walls = new Uint8Array(n*n); + this.groups = new Uint8Array(n*n); + for (let cell = 0; cell0 && !(this.walls[n*(ball_r-1)+ball_c]&WALL_DOWN)) { + next_y--; + } else if (dy>0 && ball_r<(this.n-1) && !(this.walls[n*ball_r+ball_c]&WALL_DOWN)) { + next_y++; + } else if (dx<0 && ball_c>0 && !(this.walls[n*ball_r+ball_c-1]&WALL_RIGHT)) { + next_x--; + } else if (dx>0 && ball_c<(this.n-1) && !(this.walls[n*ball_r+ball_c]&WALL_RIGHT)) { + next_x++; + } else { + return false; + } + } + this.clearCell(ball_r, ball_c); + if (this.ball_x%this.wall_length) { + this.clearCell(ball_r, ball_c+1); + } + if (this.ball_y%this.wall_length) { + this.clearCell(ball_r+1, ball_c); + } + this.ball_x = next_x; + this.ball_y = next_y; + this.drawBall(this.ball_x, this.ball_y); + if (this.ball_x==(n-1)*this.wall_length && this.ball_y==(n-1)*this.wall_length) { + this.status = STATUS_SOLVED; + } + return true; + }; + this.try_move_horizontally = function(accel_x) { + if (accel_x>0.15) { + return this.move(-1, 0); + } else if (accel_x<-0.15) { + return this.move(1, 0); + } + return false; + }; + this.try_move_vertically = function(accel_y) { + if (accel_y<-0.15) { + return this.move(0,1); + } else if (accel_y>0.15) { + return this.move(0,-1); + } + return false; + }; + this.tick = function() { + accel = Bangle.getAccel(); + if (this.ball_x%this.wall_length) { + this.try_move_horizontally(accel.x); + } else if (this.ball_y%this.wall_length) { + this.try_move_vertically(accel.y); + } else { + if (Math.abs(accel.x)>Math.abs(accel.y)) { // prefer horizontally + if (!this.try_move_horizontally(accel.x)) { + this.try_move_vertically(accel.y); + } + } else { // prefer vertically + if (!this.try_move_vertically(accel.y)) { + this.try_move_horizontally(accel.x); + } + } + } + }; + this.clearCell(0,0); + this.clearCell(n-1,n-1); + this.drawBall(0,0); + this.status = STATUS_PLAYING; +} + +function timeToText(t) { // Courtesy of stopwatch app + let hrs = Math.floor(t/3600000); + let mins = Math.floor(t/60000)%60; + let secs = Math.floor(t/1000)%60; + let tnth = Math.floor(t/100)%10; + let text; + + if (hrs === 0) + text = ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth; + else + text = ("0"+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2); + return text; +} + +let aborting = false; +let start_time = 0; +let duration = 0; +let maze=null; +let mazeMenu = { + "": { "title": "Maze size", "selected": 1 }, + "Easy (8x8)": function() { E.showMenu(); maze = new Maze(8); }, + "Medium (10x10)": function() { E.showMenu(); maze = new Maze(10); }, + "Hard (14x14)": function() { E.showMenu(); maze = new Maze(14); }, + "< Exit": function() { setTimeout(load, 100); } // timeout voodoo prevents deadlock +}; + +g.reset(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +Bangle.setLocked(false); +Bangle.setLCDTimeout(0); +E.showMenu(mazeMenu); +let maze_interval = setInterval( + function() { + if (maze) { + if (digitalRead(BTN1) || maze.status==STATUS_ABORTED) { + maze = null; + start_time = duration = 0; + aborting = false; + setTimeout(function() {E.showMenu(mazeMenu); }, 100); + return; + } + if (!start_time) { + start_time = Date.now(); + } + if (maze.status==STATUS_PLAYING) { + maze.tick(); + } + if (maze.status==STATUS_SOLVED && !duration) { + duration = Date.now()-start_time; + g.setFontAlign(0,0).setColor(g.theme.fg); + g.setFont("Vector",18); + g.drawString(`Solved ${maze.n}X${maze.n} in\n ${timeToText(duration)} \nBtn1 to play again`, g.getWidth()/2, g.getHeight()/2, true); + } + } + }, 25); diff --git a/apps/acmaze/app.png b/apps/acmaze/app.png new file mode 100644 index 000000000..0d96448b1 Binary files /dev/null and b/apps/acmaze/app.png differ diff --git a/apps/acmaze/metadata.json b/apps/acmaze/metadata.json new file mode 100644 index 000000000..d8ab8fa62 --- /dev/null +++ b/apps/acmaze/metadata.json @@ -0,0 +1,15 @@ +{ "id": "acmaze", + "name": "AccelaMaze", + "shortName":"AccelaMaze", + "version":"0.03", + "description": "Tilt the watch to roll a ball through a maze.", + "icon": "app.png", + "tags": "game", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "screenshots": [{"url":"screenshot.png"}], + "storage": [ + {"name":"acmaze.app.js","url":"app.js"}, + {"name":"acmaze.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/acmaze/screenshot.png b/apps/acmaze/screenshot.png new file mode 100644 index 000000000..4b7217b97 Binary files /dev/null and b/apps/acmaze/screenshot.png differ diff --git a/apps/activepedom/metadata.json b/apps/activepedom/metadata.json new file mode 100644 index 000000000..4deb7006d --- /dev/null +++ b/apps/activepedom/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "activepedom", + "name": "Active Pedometer", + "shortName": "Active Pedometer", + "version": "0.09", + "description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.", + "icon": "app.png", + "tags": "outdoors,widget", + "supports": ["BANGLEJS"], + "readme": "README.md", + "screenshots": [{"url":"600.png"},{"url":"10600.png"},{"url":"1600.png"}], + "storage": [ + {"name":"activepedom.wid.js","url":"widget.js"}, + {"name":"activepedom.settings.js","url":"settings.js"}, + {"name":"activepedom.img","url":"app-icon.js","evaluate":true}, + {"name":"activepedom.app.js","url":"app.js"} + ] +} diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json new file mode 100644 index 000000000..3e109bda9 --- /dev/null +++ b/apps/alarm/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "alarm", + "name": "Default Alarm & Timer", + "shortName": "Alarms", + "version": "0.14", + "description": "Set and respond to alarms and timers", + "icon": "app.png", + "tags": "tool,alarm,widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"alarm.app.js","url":"app.js"}, + {"name":"alarm.boot.js","url":"boot.js"}, + {"name":"alarm.js","url":"alarm.js"}, + {"name":"alarm.img","url":"app-icon.js","evaluate":true}, + {"name":"alarm.wid.js","url":"widget.js"} + ], + "data": [{"name":"alarm.json"}] +} diff --git a/apps/alpinenav/metadata.json b/apps/alpinenav/metadata.json new file mode 100644 index 000000000..dcb56e912 --- /dev/null +++ b/apps/alpinenav/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "alpinenav", + "name": "Alpine Nav", + "version": "0.01", + "description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime", + "icon": "app-icon.png", + "tags": "outdoors,gps", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"alpinenav.app.js","url":"app.js"}, + {"name":"alpinenav.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/analogimgclk/metadata.json b/apps/analogimgclk/metadata.json new file mode 100644 index 000000000..c33ac3a46 --- /dev/null +++ b/apps/analogimgclk/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "analogimgclk", + "name": "Analog Clock (Image background)", + "shortName": "Analog Clock", + "version": "0.03", + "description": "An analog clock with an image background", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"analogimgclk.app.js","url":"app.js"}, + {"name":"analogimgclk.bg.img","url":"bg.img"}, + {"name":"analogimgclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/andark/metadata.json b/apps/andark/metadata.json new file mode 100644 index 000000000..3e2b3116e --- /dev/null +++ b/apps/andark/metadata.json @@ -0,0 +1,15 @@ +{ "id": "andark", + "name": "Analog Dark", + "shortName":"AnDark", + "version":"0.04", + "description": "analog clock face without disturbing widgets", + "icon": "andark_icon.png", + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"andark.app.js","url":"app.js"}, + {"name":"andark.img","url":"app_icon.js","evaluate":true} + ] +} diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index c2c4ea6be..0d837fe43 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -4,3 +4,4 @@ 0.03: Handling of message actions (ok/clear) 0.04: Android icon now goes to settings page with 'find phone' 0.05: Fix handling of message actions +0.06: Option to keep messages after a disconnect (default false) (fix #1186) diff --git a/apps/android/README.md b/apps/android/README.md new file mode 100644 index 000000000..c10718aac --- /dev/null +++ b/apps/android/README.md @@ -0,0 +1,48 @@ +# Android Integration + +This app allows your Bangle.js to receive notifications [from the Gadgetbridge app on Android](http://www.espruino.com/Gadgetbridge) + +See [this link](http://www.espruino.com/Gadgetbridge) for notes on how to install +the Android app (and how it works). + +It requires the `Messages` app on Bangle.js (which should be automatically installed) to +display any notifications that are received. + +## Settings + +You can access the settings menu either from the `Android` icon in the launcher, +or from `App Settings` in the `Settings` menu. + +It contains: + +* `Connected` - shows whether there is an active Bluetooth connection or not +* `Find Phone` - opens a submenu where you can activate the `Find Phone` functionality +of Gadgetbridge - making your phone make noise so you can find it. +* `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js +keep any messages it has received, or should it delete them? +* `Messages` - launches the messages app, showing a list of messages + +## How it works + +Gadgetbridge on Android connects to Bangle.js, and sends commands over the +BLE UART connection. These take the form of `GB({ ... JSON ... })\n` - so they +call a global function called `GB` which then interprets the JSON. + +Responses are sent back to Gadgetbridge simply as one line of JSON. + +More info on message formats on http://www.espruino.com/Gadgetbridge + +## Testing + +Bangle.js can only hold one connection open at a time, so it's hard to see +if there are any errors when handling Gadgetbridge messages. + +However you can: + +* Use the `Gadgetbridge Debug` app on Bangle.js to display/log the messages received from Gadgetbridge +* Connect with the Web IDE and manually enter the Gadgetbridge messages on the left-hand side to +execute them as if they came from Gadgetbridge, for instance: + +``` +GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"}) +``` diff --git a/apps/android/boot.js b/apps/android/boot.js index 59ffe006d..fff9ad444 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -4,6 +4,7 @@ Bluetooth.println(JSON.stringify(message)); } + var settings = require("Storage").readJSON("android.settings.json",1)||{}; var _GB = global.GB; global.GB = (event) => { // feed a copy to other handlers if there were any @@ -51,7 +52,8 @@ // Battery monitor function sendBattery() { gbSend({ t: "status", bat: E.getBattery() }); } NRF.on("connect", () => setTimeout(sendBattery, 2000)); - NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect + if (!settings.keep) + NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect setInterval(sendBattery, 10*60*1000); // Health tracking Bangle.on('health', health=>{ @@ -68,4 +70,6 @@ if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id }); // error/warn here? }; + // remove settings object so it's not taking up RAM + delete settings; })(); diff --git a/apps/android/metadata.json b/apps/android/metadata.json new file mode 100644 index 000000000..6b780ff55 --- /dev/null +++ b/apps/android/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "android", + "name": "Android Integration", + "shortName": "Android", + "version": "0.06", + "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", + "icon": "app.png", + "tags": "tool,system,messages,notifications,gadgetbridge", + "dependencies": {"messages":"app"}, + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"android.app.js","url":"app.js"}, + {"name":"android.settings.js","url":"settings.js"}, + {"name":"android.img","url":"app-icon.js","evaluate":true}, + {"name":"android.boot.js","url":"boot.js"} + ], + "data": [{"name":"android.settings.json"}], + "sortorder": -8 +} diff --git a/apps/android/settings.js b/apps/android/settings.js index d241397a4..7c46a1fc0 100644 --- a/apps/android/settings.js +++ b/apps/android/settings.js @@ -2,17 +2,29 @@ function gb(j) { Bluetooth.println(JSON.stringify(j)); } + var settings = require("Storage").readJSON("android.settings.json",1)||{}; + function updateSettings() { + require("Storage").writeJSON("android.settings.json", settings); + } var mainmenu = { "" : { "title" : "Android" }, "< Back" : back, - "Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" }, + /*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" }, "Find Phone" : () => E.showMenu({ "" : { "title" : "Find Phone" }, "< Back" : ()=>E.showMenu(mainmenu), - "On" : _=>gb({t:"findPhone",n:true}), - "Off" : _=>gb({t:"findPhone",n:false}), + /*LANG*/"On" : _=>gb({t:"findPhone",n:true}), + /*LANG*/"Off" : _=>gb({t:"findPhone",n:false}), }), - "Messages" : ()=>load("messages.app.js") + /*LANG*/"Keep Msgs" : { + value : !!settings.keep, + format : v=>v?/*LANG*/"Yes":/*LANG*/"No", + onchange: v => { + settings.keep = v; + updateSettings(); + } + }, + /*LANG*/"Messages" : ()=>load("messages.app.js") }; E.showMenu(mainmenu); }) diff --git a/apps/animals/metadata.json b/apps/animals/metadata.json new file mode 100644 index 000000000..773f0fd0a --- /dev/null +++ b/apps/animals/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "animals", + "name": "Animals Game", + "version": "0.01", + "description": "Simple toddler's game - displays a different number of animals each time the screen is pressed", + "icon": "animals.png", + "tags": "game", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"animals.app.js","url":"animals.js"}, + {"name":"animals.img","url":"animals-icon.js","evaluate":true}, + {"name":"animals-snake.img","url":"animals-snake.js","evaluate":true}, + {"name":"animals-duck.img","url":"animals-duck.js","evaluate":true}, + {"name":"animals-swan.img","url":"animals-swan.js","evaluate":true}, + {"name":"animals-fox.img","url":"animals-fox.js","evaluate":true}, + {"name":"animals-camel.img","url":"animals-camel.js","evaluate":true}, + {"name":"animals-pig.img","url":"animals-pig.js","evaluate":true}, + {"name":"animals-sheep.img","url":"animals-sheep.js","evaluate":true}, + {"name":"animals-mouse.img","url":"animals-mouse.js","evaluate":true} + ] +} diff --git a/apps/animclk/metadata.json b/apps/animclk/metadata.json new file mode 100644 index 000000000..31dfe453f --- /dev/null +++ b/apps/animclk/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "animclk", + "name": "Animated Clock", + "shortName": "Anim Clock", + "version": "0.03", + "description": "An animated clock face using Mark Ferrari's amazing 8 bit game art and palette cycling: http://www.markferrari.com/art/8bit-game-art", + "icon": "app.png", + "type": "clock", + "tags": "clock,animated", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"animclk.app.js","url":"app.js"}, + {"name":"animclk.pixels1","url":"animclk.pixels1"}, + {"name":"animclk.pixels2","url":"animclk.pixels2"}, + {"name":"animclk.pal","url":"animclk.pal"}, + {"name":"animclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/antonclk/ChangeLog b/apps/antonclk/ChangeLog index f88276a90..4dca8053e 100644 --- a/apps/antonclk/ChangeLog +++ b/apps/antonclk/ChangeLog @@ -1,3 +1,10 @@ 0.01: New App! 0.02: Load widgets after setUI so widclk knows when to hide 0.03: Clock now shows day of week under date. +0.04: Clock can optionally show seconds, date optionally in ISO-8601 format, weekdays and uppercase configurable, too. +0.05: Clock can optionally show ISO-8601 calendar weeknumber (default: Off) + when weekday name "Off": week #: + when weekday name "On": weekday name is cut at 6th position and .# is added +0.06: fixes #1271 - wrong settings name + when weekday name and calendar weeknumber are on then display is # + week is buffered until date or timezone changes \ No newline at end of file diff --git a/apps/antonclk/README.md b/apps/antonclk/README.md new file mode 100644 index 000000000..28a38f5fd --- /dev/null +++ b/apps/antonclk/README.md @@ -0,0 +1,79 @@ +# Anton Clock - Large font digital watch with seconds and date + +Anton clock uses the "Anton" bold font to show the time in a clear, easily readable manner. On the Bangle.js 2, the time can be read easily even if the screen is locked and unlit. + +## Features + +The basic time representation only shows hours and minutes of the current time. However, Anton clock can show additional information: + +* Seconds can be shown, either always or only if the screen is unlocked. +* To help easy recognition, the seconds can be coloured in blue on the Bangle.js 2. +* Date can be shown in three different formats: + * ISO-8601: 2021-12-19 + * short local format: 19/12/2021, 19.12.2021 + * long local format: DEC 19 2021 +* Weekday can be shown (on seconds screen only instead of year) + +## Usage + +Install Anton clock through the Bangle.js app loader. +Configure it through the default Bangle.js configuration mechanism +(Settings app, "Apps" menu, "Anton clock" submenu). +If you like it, make it your default watch face +(Settings app, "System" menu, "Clock" submenu, select "Anton clock"). + +## Configuration + +Anton clock is configured by the standard settings mechanism of Bangle.js's operating system: +Open the "Settings" app, then the "Apps" submenu and below it the "Anton clock" menu. +You configure Anton clock through several "on/off" switches in two menus. + +### The main menu + +The main menu contains several settings covering Anton clock in general. + +* **Seconds...** - Opens the submenu for configuring the presentation of the current time's seconds. +* **Date** - Format of the date representation. Possible values are + * **Long** - "Long" date format in the current locale. Usually with the month as name, not number. + * **Short** - "Short" date format in the current locale. Usually with the month as number. + * **ISO8601** - Show the date in ISO-8601 format (YYYY-MM-DD), irrespective of the current locale. +* **Show Weekday** - Weekday is shown in the time presentation without seconds. +Weekday name depends on the current locale. +If seconds are shown, the weekday is never shown as there is not enough space on the watch face. +* **Show CalWeek** - Week-number (ISO-8601) is shown. (default: Off) +If "Show Weekday" is "Off" displays the week-number as "week #". +If "Show Weekday" is "On" displays "weekday name short" with " #" . +If seconds are shown, the week number is never shown as there is not enough space on the watch face. +* **Vector font** - Use the built-in vector font for dates and weekday. +This can improve readability. +Otherwise, a scaled version of the built-in 6x8 pixels font is used. + +### The "Seconds" submenu + +The "Seconds" submenu configures how (and if) seconds are shown on the "Anton" watch face. + +* **Show** - Configure when the seconds should be shown at all: + * **Never** - Seconds are never shown. +In this case, hour and minute are a bit more centered on the screen and the clock will always only update every minute. +This saves battery power. + * **Unlocked** - Seconds are shown if the display is unlocked. +On locked displays, only hour, minutes, date and optionally the weekday are shown. +_This option is highly recommended on the Bangle.js 2!_ + * **Always** - Seconds are _always_ shown, irrespective of the display's unlock state. +_Enabling this option increases power consumption as the watch face will update once per second instead of once per minute._ +* **With ":"** - If enabled, a colon ":" is prepended to the seconds. +This resembles the usual time representation "hh:mm:ss", even though the seconds are printed on a separate line. +* **Color** - If enabled, seconds are shown in blue instead of black. +If the date is shown on the seconds screen, it is colored read instead of black. +This make the visual orientation much easier on the watch face. +* **Date** - It is possible to show the date together with the seconds: + * **No** - Date is _not_ shown in the seconds screen. +In this case, the seconds are centered below hour and minute. + * **Year** - Date is shown with day, month, and year. If "Date" in the main settings is configured to _ISO8601_, this is used here, too. Otherwise, the short local format is used. + * **Weekday** - Date is shown with day, month, and weekday. + +The date is coloured in red if the "Coloured" option is chosen. + +## Compatibility + +Anton clock makes use of core Bangle.js 2 features (coloured display, display lock state). It also runs on the Bangle.js 1 but these features are not available there due to hardware restrictions. diff --git a/apps/antonclk/app.js b/apps/antonclk/app.js index 7912dfc0f..7b40d8eb5 100644 --- a/apps/antonclk/app.js +++ b/apps/antonclk/app.js @@ -1,61 +1,230 @@ +// Clock with large digits using the "Anton" bold font + +const SETTINGSFILE = "antonclk.json"; + Graphics.prototype.setFontAnton = function(scale) { -// Actual height 69 (68 - 0) - g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAA/gAAAAAAAAAAP/gAAAAAAAAAH//gAAAAAAAAB///gAAAAAAAAf///gAAAAAAAP////gAAAAAAD/////gAAAAAA//////gAAAAAP//////gAAAAH///////gAAAB////////gAAAf////////gAAP/////////gAD//////////AA//////////gAA/////////4AAA////////+AAAA////////gAAAA///////wAAAAA//////8AAAAAA//////AAAAAAA/////gAAAAAAA////4AAAAAAAA///+AAAAAAAAA///gAAAAAAAAA//wAAAAAAAAAA/8AAAAAAAAAAA/AAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////AAAAAB///////8AAAAH////////AAAAf////////wAAA/////////4AAB/////////8AAD/////////+AAH//////////AAP//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wA//8AAAAAB//4A//wAAAAAAf/4A//gAAAAAAP/4A//gAAAAAAP/4A//gAAAAAAP/4A//wAAAAAAf/4A///////////4Af//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH//////////AAD/////////+AAB/////////8AAA/////////4AAAP////////gAAAD///////+AAAAAf//////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/gAAAAAAAAAAP/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/AAAAAAAAAAA//AAAAAAAAAAA/+AAAAAAAAAAB/8AAAAAAAAAAD//////////gAH//////////gAP//////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAAB/gAAD//4AAAAf/gAAP//4AAAB//gAA///4AAAH//gAB///4AAAf//gAD///4AAA///gAH///4AAD///gAP///4AAH///gAP///4AAP///gAf///4AAf///gAf///4AB////gAf///4AD////gA////4AH////gA////4Af////gA////4A/////gA//wAAB/////gA//gAAH/////gA//gAAP/////gA//gAA///8//gA//gAD///w//gA//wA////g//gA////////A//gA///////8A//gA///////4A//gAf//////wA//gAf//////gA//gAf/////+AA//gAP/////8AA//gAP/////4AA//gAH/////gAA//gAD/////AAA//gAB////8AAA//gAA////wAAA//gAAP///AAAA//gAAD//8AAAA//gAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/+AAAAAD/wAAB//8AAAAP/wAAB///AAAA//wAAB///wAAB//wAAB///4AAD//wAAB///8AAH//wAAB///+AAP//wAAB///+AAP//wAAB////AAf//wAAB////AAf//wAAB////gAf//wAAB////gA///wAAB////gA///wAAB////gA///w//AAf//wA//4A//AAA//wA//gA//AAAf/wA//gB//gAAf/wA//gB//gAAf/wA//gD//wAA//wA//wH//8AB//wA///////////gA///////////gA///////////gA///////////gAf//////////AAf//////////AAP//////////AAP/////////+AAH/////////8AAH///+/////4AAD///+f////wAAA///8P////gAAAf//4H///+AAAAH//gB///wAAAAAP4AAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAAAAAA//wAAAAAAAAAP//wAAAAAAAAB///wAAAAAAAAf///wAAAAAAAH////wAAAAAAA/////wAAAAAAP/////wAAAAAB//////wAAAAAf//////wAAAAH///////wAAAA////////wAAAP////////wAAA///////H/wAAA//////wH/wAAA/////8AH/wAAA/////AAH/wAAA////gAAH/wAAA///4AAAH/wAAA//+AAAAH/wAAA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAH/4AAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//8AAA/////+B///AAA/////+B///wAA/////+B///4AA/////+B///8AA/////+B///8AA/////+B///+AA/////+B////AA/////+B////AA/////+B////AA/////+B////gA/////+B////gA/////+B////gA/////+A////gA//gP/gAAB//wA//gf/AAAA//wA//gf/AAAAf/wA//g//AAAAf/wA//g//AAAA//wA//g//gAAA//wA//g//+AAP//wA//g////////gA//g////////gA//g////////gA//g////////gA//g////////AA//gf///////AA//gf//////+AA//gP//////+AA//gH//////8AA//gD//////4AA//gB//////wAA//gA//////AAAAAAAH////8AAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////gAAAAB///////+AAAAH////////gAAAf////////4AAB/////////8AAD/////////+AAH//////////AAH//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wAf//////////4A//wAD/4AAf/4A//gAH/wAAP/4A//gAH/wAAP/4A//gAP/wAAP/4A//gAP/4AAf/4A//wAP/+AD//4A///wP//////4Af//4P//////wAf//4P//////wAf//4P//////wAf//4P//////wAP//4P//////gAP//4H//////gAH//4H//////AAH//4D/////+AAD//4D/////8AAB//4B/////4AAA//4A/////wAAAP/4AP////AAAAB/4AD///4AAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAADgA//gAAAAAAP/gA//gAAAAAH//gA//gAAAAB///gA//gAAAAP///gA//gAAAD////gA//gAAAf////gA//gAAB/////gA//gAAP/////gA//gAB//////gA//gAH//////gA//gA///////gA//gD///////gA//gf///////gA//h////////gA//n////////gA//////////gAA/////////AAAA////////wAAAA///////4AAAAA///////AAAAAA//////4AAAAAA//////AAAAAAA/////4AAAAAAA/////AAAAAAAA////8AAAAAAAA////gAAAAAAAA///+AAAAAAAAA///4AAAAAAAAA///AAAAAAAAAA//4AAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//gB///wAAAAP//4H///+AAAA///8P////gAAB///+f////4AAD///+/////8AAH/////////+AAH//////////AAP//////////gAP//////////gAf//////////gAf//////////wAf//////////wAf//////////wA///////////wA//4D//wAB//4A//wB//gAA//4A//gA//gAAf/4A//gA//AAAf/4A//gA//gAAf/4A//wB//gAA//4A///P//8AH//4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////gAP//////////gAP//////////AAH//////////AAD/////////+AAD///+/////8AAB///8f////wAAAf//4P////AAAAH//wD///8AAAAA/+AAf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAB///+AA/+AAAAP////gA//wAAAf////wA//4AAB/////4A//8AAD/////8A//+AAD/////+A///AAH/////+A///AAP//////A///gAP//////A///gAf//////A///wAf//////A///wAf//////A///wAf//////A///wA///////AB//4A//4AD//AAP/4A//gAB//AAP/4A//gAA//AAP/4A//gAA/+AAP/4A//gAB/8AAP/4A//wAB/8AAf/4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH/////////+AAD/////////8AAB/////////4AAAf////////wAAAP////////AAAAB///////4AAAAAD/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAB/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("EiAnGicnJycnJycnEw=="), 78+(scale<<8)+(1<<16)); + // Actual height 69 (68 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAA/gAAAAAAAAAAP/gAAAAAAAAAH//gAAAAAAAAB///gAAAAAAAAf///gAAAAAAAP////gAAAAAAD/////gAAAAAA//////gAAAAAP//////gAAAAH///////gAAAB////////gAAAf////////gAAP/////////gAD//////////AA//////////gAA/////////4AAA////////+AAAA////////gAAAA///////wAAAAA//////8AAAAAA//////AAAAAAA/////gAAAAAAA////4AAAAAAAA///+AAAAAAAAA///gAAAAAAAAA//wAAAAAAAAAA/8AAAAAAAAAAA/AAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////AAAAAB///////8AAAAH////////AAAAf////////wAAA/////////4AAB/////////8AAD/////////+AAH//////////AAP//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wA//8AAAAAB//4A//wAAAAAAf/4A//gAAAAAAP/4A//gAAAAAAP/4A//gAAAAAAP/4A//wAAAAAAf/4A///////////4Af//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH//////////AAD/////////+AAB/////////8AAA/////////4AAAP////////gAAAD///////+AAAAAf//////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/gAAAAAAAAAAP/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/AAAAAAAAAAA//AAAAAAAAAAA/+AAAAAAAAAAB/8AAAAAAAAAAD//////////gAH//////////gAP//////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAAB/gAAD//4AAAAf/gAAP//4AAAB//gAA///4AAAH//gAB///4AAAf//gAD///4AAA///gAH///4AAD///gAP///4AAH///gAP///4AAP///gAf///4AAf///gAf///4AB////gAf///4AD////gA////4AH////gA////4Af////gA////4A/////gA//wAAB/////gA//gAAH/////gA//gAAP/////gA//gAA///8//gA//gAD///w//gA//wA////g//gA////////A//gA///////8A//gA///////4A//gAf//////wA//gAf//////gA//gAf/////+AA//gAP/////8AA//gAP/////4AA//gAH/////gAA//gAD/////AAA//gAB////8AAA//gAA////wAAA//gAAP///AAAA//gAAD//8AAAA//gAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/+AAAAAD/wAAB//8AAAAP/wAAB///AAAA//wAAB///wAAB//wAAB///4AAD//wAAB///8AAH//wAAB///+AAP//wAAB///+AAP//wAAB////AAf//wAAB////AAf//wAAB////gAf//wAAB////gA///wAAB////gA///wAAB////gA///w//AAf//wA//4A//AAA//wA//gA//AAAf/wA//gB//gAAf/wA//gB//gAAf/wA//gD//wAA//wA//wH//8AB//wA///////////gA///////////gA///////////gA///////////gAf//////////AAf//////////AAP//////////AAP/////////+AAH/////////8AAH///+/////4AAD///+f////wAAA///8P////gAAAf//4H///+AAAAH//gB///wAAAAAP4AAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAAAAAA//wAAAAAAAAAP//wAAAAAAAAB///wAAAAAAAAf///wAAAAAAAH////wAAAAAAA/////wAAAAAAP/////wAAAAAB//////wAAAAAf//////wAAAAH///////wAAAA////////wAAAP////////wAAA///////H/wAAA//////wH/wAAA/////8AH/wAAA/////AAH/wAAA////gAAH/wAAA///4AAAH/wAAA//+AAAAH/wAAA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAH/4AAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//8AAA/////+B///AAA/////+B///wAA/////+B///4AA/////+B///8AA/////+B///8AA/////+B///+AA/////+B////AA/////+B////AA/////+B////AA/////+B////gA/////+B////gA/////+B////gA/////+A////gA//gP/gAAB//wA//gf/AAAA//wA//gf/AAAAf/wA//g//AAAAf/wA//g//AAAA//wA//g//gAAA//wA//g//+AAP//wA//g////////gA//g////////gA//g////////gA//g////////gA//g////////AA//gf///////AA//gf//////+AA//gP//////+AA//gH//////8AA//gD//////4AA//gB//////wAA//gA//////AAAAAAAH////8AAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////gAAAAB///////+AAAAH////////gAAAf////////4AAB/////////8AAD/////////+AAH//////////AAH//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wAf//////////4A//wAD/4AAf/4A//gAH/wAAP/4A//gAH/wAAP/4A//gAP/wAAP/4A//gAP/4AAf/4A//wAP/+AD//4A///wP//////4Af//4P//////wAf//4P//////wAf//4P//////wAf//4P//////wAP//4P//////gAP//4H//////gAH//4H//////AAH//4D/////+AAD//4D/////8AAB//4B/////4AAA//4A/////wAAAP/4AP////AAAAB/4AD///4AAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAADgA//gAAAAAAP/gA//gAAAAAH//gA//gAAAAB///gA//gAAAAP///gA//gAAAD////gA//gAAAf////gA//gAAB/////gA//gAAP/////gA//gAB//////gA//gAH//////gA//gA///////gA//gD///////gA//gf///////gA//h////////gA//n////////gA//////////gAA/////////AAAA////////wAAAA///////4AAAAA///////AAAAAA//////4AAAAAA//////AAAAAAA/////4AAAAAAA/////AAAAAAAA////8AAAAAAAA////gAAAAAAAA///+AAAAAAAAA///4AAAAAAAAA///AAAAAAAAAA//4AAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//gB///wAAAAP//4H///+AAAA///8P////gAAB///+f////4AAD///+/////8AAH/////////+AAH//////////AAP//////////gAP//////////gAf//////////gAf//////////wAf//////////wAf//////////wA///////////wA//4D//wAB//4A//wB//gAA//4A//gA//gAAf/4A//gA//AAAf/4A//gA//gAAf/4A//wB//gAA//4A///P//8AH//4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////gAP//////////gAP//////////AAH//////////AAD/////////+AAD///+/////8AAB///8f////wAAAf//4P////AAAAH//wD///8AAAAA/+AAf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAB///+AA/+AAAAP////gA//wAAAf////wA//4AAB/////4A//8AAD/////8A//+AAD/////+A///AAH/////+A///AAP//////A///gAP//////A///gAf//////A///wAf//////A///wAf//////A///wAf//////A///wA///////AB//4A//4AD//AAP/4A//gAB//AAP/4A//gAA//AAP/4A//gAA/+AAP/4A//gAB/8AAP/4A//wAB/8AAf/4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH/////////+AAD/////////8AAB/////////4AAAf////////wAAAP////////AAAAB///////4AAAAAD/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAB/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("EiAnGicnJycnJycnEw=="), 78 + (scale << 8) + (1 << 16)); +}; + +Graphics.prototype.setFontAntonSmall = function(scale) { + // Actual height 53 (52 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAAAAAAAAAAAAAMAAAAAAAAD8AAAAAAAA/8AAAAAAAf/8AAAAAAH//8AAAAAB///8AAAAA////8AAAAP////8AAAD/////8AAB//////8AAf//////8AH///////4A///////+AA///////AAA//////wAAA/////8AAAA////+AAAAA////gAAAAA///4AAAAAA//8AAAAAAA//AAAAAAAA/wAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/////wAAA//////8AAB//////+AAH///////gAH///////gAP///////wAf///////4Af///////4A////////8A////////8A////////8A//AAAAD/8A/8AAAAA/8A/8AAAAA/8A/8AAAAA/8A/+AAAAB/8A////////8A////////8A////////8Af///////4Af///////4AP///////wAP///////wAH///////gAD///////AAA//////8AAAP/////wAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAA/4AAAAAAAA/4AAAAAAAB/wAAAAAAAB/wAAAAAAAD/wAAAAAAAD/gAAAAAAAH///////8AP///////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAP8AA//4AAA/8AB//4AAH/8AH//4AAP/8AP//4AA//8AP//4AB//8Af//4AD//8Af//4AP//8A///4Af//8A///4A///8A///4D///8A//AAH///8A/8AAP///8A/8AA//+/8A/8AD//8/8A/+Af//w/8A//////g/8A/////+A/8A/////8A/8Af////4A/8Af////wA/8AP////AA/8AP///+AA/8AH///8AA/8AD///wAA/8AA///AAA/8AAP/4AAA/8AAAAAAAAAAAAAAAAAAAAAAH4AAf/gAAA/4AAf/8AAD/4AAf//AAH/4AAf//gAP/4AAf//wAP/4AAf//wAf/4AAf//4Af/4AAf//4A//4AAf//8A//4AAf//8A//4AAP//8A//A/8AB/8A/8A/8AA/8A/8B/8AA/8A/8B/8AA/8A/+D//AB/8A////////8A////////8A////////8Af///////4Af///////4Af///////wAP///////gAH//9////gAD//4///+AAB//wf//4AAAP/AH//gAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAB//wAAAAAAP//wAAAAAD///wAAAAA////wAAAAH////wAAAB/////wAAAf/////wAAD//////wAA///////wAA/////h/wAA////wB/wAA///8AB/wAA///AAB/wAA//gAAB/wAA////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8AAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAAAAAAAAAAAAAAAAAAAAAP/4AA////4P/+AA////4P//AA////4P//gA////4P//wA////4P//wA////4P//4A////4P//4A////4P//8A////4P//8A////4P//8A/8H/AAB/8A/8H+AAA/8A/8P+AAA/8A/8P+AAA/8A/8P/gAD/8A/8P/////8A/8P/////8A/8P/////8A/8P/////4A/8H/////4A/8H/////wA/8D/////wA/8B/////gA/8A////+AA/8AP///4AAAAAB///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////wAAAf/////8AAB///////AAH///////gAP///////wAP///////wAf///////4Af///////4A////////8A////////8A////////8A/+AH/AB/8A/8AP+AA/8A/4Af+AA/8A/8Af+AA/8A/8Af/gH/8A//4f////8A//4f////8A//4f////8Af/4f////4Af/4f////4AP/4P////wAP/4P////gAH/4H////AAD/4D///+AAB/4B///4AAAP4AP//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAA/8AAAAAAAA/8AAAAAB8A/8AAAAB/8A/8AAAAf/8A/8AAAH//8A/8AAA///8A/8AAH///8A/8AA////8A/8AD////8A/8Af////8A/8B/////8A/8P/////8A/8//////8A////////AA///////AAA//////gAAA/////4AAAA/////AAAAA////4AAAAA////AAAAAA///8AAAAAA///gAAAAAA//+AAAAAAA//wAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/gD//gAAA//4P//8AAD//8f///AAH//+////gAH///////wAP///////4AP///////8Af///////8Af///////+Af///////+A////////+A//B//AB/+A/+A/+AA/+A/8Af+AA/+A/+Af+AA/+A//A//AB/+A////////+Af///////+Af///////+Af///////8Af///////8AP///////4AH///////4AH//+////wAD//+////AAA//4P//+AAAP/gH//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAfgAAA///8A/8AAB///+A//AAH////A//gAH////g//wAP////g//wAf////w//4Af////w//4A/////w//8A/////w//8A/////w//8A//gP/wA/8A/8AD/wA/8A/8AD/wAf8A/8AD/gA/8A/+AH/AB/8A////////8A////////8A////////8Af///////4Af///////4Af///////wAP///////wAH///////gAD//////+AAA//////4AAAP/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DhgeFB4eHh4eHh4eDw=="), 60 + (scale << 8) + (1 << 16)); +}; + +// variables defined from settings +var secondsMode; +var secondsColoured; +var secondsWithColon; +var dateOnMain; +var dateOnSecs; +var weekDay; +var calWeek; +var upperCase; +var vectorFont; + +// dynamic variables +var drawTimeout; +var queueMillis = 1000; +var secondsScreen = true; + +var isBangle1 = (process.env.HWVERSION == 1); + +//For development purposes +/* +require('Storage').writeJSON(SETTINGSFILE, { + secondsMode: "Unlocked", // "Never", "Unlocked", "Always" + secondsColoured: true, + secondsWithColon: true, + dateOnMain: "Long", // "Short", "Long", "ISO8601" + dateOnSecs: "Year", // "No", "Year", "Weekday", LEGACY: true/false + weekDay: true, + calWeek: true, + upperCase: true, + vectorFont: true, +}); +*/ + +// OR (also for development purposes) +/* +require('Storage').erase(SETTINGSFILE); +*/ + +// Load settings +function loadSettings() { + // Helper function default setting + function def (value, def) {return value !== undefined ? value : def;} + + var settings = require('Storage').readJSON(SETTINGSFILE, true) || {}; + secondsMode = def(settings.secondsMode, "Never"); + secondsColoured = def(settings.secondsColoured, true); + secondsWithColon = def(settings.secondsWithColon, true); + dateOnMain = def(settings.dateOnMain, "Long"); + dateOnSecs = def(settings.dateOnSecs, "Year"); + weekDay = def(settings.weekDay, true); + calWeek = def(settings.calWeek, false); + upperCase = def(settings.upperCase, true); + vectorFont = def(settings.vectorFont, false); + + // Legacy + if (dateOnSecs === true) + dateOnSecs = "Year"; + if (dateOnSecs === false) + dateOnSecs = "No"; } -// timeout used to update every minute -var drawTimeout; - -// schedule a draw for the next minute +// schedule a draw for the next second or minute function queueDraw() { if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = setTimeout(function() { drawTimeout = undefined; draw(); - }, 60000 - (Date.now() % 60000)); + }, queueMillis - (Date.now() % queueMillis)); } - -function draw() { - var x = g.getWidth()/2; - var y = g.getHeight()/2; - g.reset(); - var date = new Date(); - var timeStr = require("locale").time(date,1); - var dateStr = require("locale").date(date).toUpperCase(); - var dowStr = require("locale").dow(date).toUpperCase(); - // draw time - g.setFontAlign(0,0).setFont("Anton"); - g.clearRect(0,y-40,g.getWidth(),y+35); // clear the background - g.drawString(timeStr,x,y); - // draw date - y += 40; - g.setFontAlign(0,0).setFont("6x8",2); - g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background - g.drawString(dateStr,x,y); - //draw day of week - y += 16; - g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background - g.drawString(dowStr,x,y); - // queue draw in one minute - queueDraw(); -} - -// Clear the screen once, at startup -g.clear(); -// draw immediately at first, queue update -draw(); -// Stop updates when LCD is off, restart when on -Bangle.on('lcdPower',on=>{ - if (on) { +function updateState() { + if (Bangle.isLCDOn()) { + if ((secondsMode === "Unlocked" && !Bangle.isLocked()) || secondsMode === "Always") { + secondsScreen = true; + queueMillis = 1000; + } else { + secondsScreen = false; + queueMillis = 60000; + } draw(); // draw immediately, queue redraw } else { // stop draw timer if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; } +} + +function isoStr(date) { + return date.getFullYear() + "-" + ("0" + (date.getMonth() + 1)).substr(-2) + "-" + ("0" + date.getDate()).substr(-2); +} + +var calWeekBuffer = [false,false,false]; //buffer tz, date, week no (once calculated until other tz or date is requested) +function ISO8601calWeek(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 + dateNoTime = date; dateNoTime.setHours(0,0,0,0); + if (calWeekBuffer[0] === date.getTimezoneOffset() && calWeekBuffer[1] === dateNoTime) return calWeekBuffer[2]; + calWeekBuffer[0] = date.getTimezoneOffset(); + calWeekBuffer[1] = dateNoTime; + var tdt = new Date(date.valueOf()); + var dayn = (date.getDay() + 6) % 7; + tdt.setDate(tdt.getDate() - dayn + 3); + var firstThursday = tdt.valueOf(); + tdt.setMonth(0, 1); + if (tdt.getDay() !== 4) { + tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7); + } + calWeekBuffer[2] = 1 + Math.ceil((firstThursday - tdt) / 604800000); + return calWeekBuffer[2]; +} + +function doColor() { + return !isBangle1 && !Bangle.isLocked() && secondsColoured; +} + +// Actually draw the watch face +function draw() { + var x = g.getWidth() / 2; + var y = g.getHeight() / 2 - (secondsMode !== "Never" ? 24 : (vectorFont ? 12 : 0)); + g.reset(); + /* This is to mark the widget areas during development. + g.setColor("#888") + .fillRect(0, 0, g.getWidth(), 23) + .fillRect(0, g.getHeight() - 23, g.getWidth(), g.getHeight()).reset(); + /* */ + g.clearRect(0, 24, g.getWidth(), g.getHeight() - 24); // clear whole background (w/o widgets) + var date = new Date(); // Actually the current date, this one is shown + var timeStr = require("locale").time(date, 1); // Hour and minute + g.setFontAlign(0, 0).setFont("Anton").drawString(timeStr, x, y); // draw time + if (secondsScreen) { + y += 65; + var secStr = (secondsWithColon ? ":" : "") + ("0" + date.getSeconds()).substr(-2); + if (doColor()) + g.setColor(0, 0, 1); + g.setFont("AntonSmall"); + if (dateOnSecs !== "No") { // A bit of a complex drawing with seconds on the right and date on the left + g.setFontAlign(1, 0).drawString(secStr, g.getWidth() - (isBangle1 ? 32 : 2), y); // seconds + y -= (vectorFont ? 15 : 13); + x = g.getWidth() / 4 + (isBangle1 ? 12 : 4) + (secondsWithColon ? 0 : g.stringWidth(":") / 2); + var dateStr2 = (dateOnMain === "ISO8601" ? isoStr(date) : require("locale").date(date, 1)); + var year; + var md; + var yearfirst; + if (dateStr2.match(/\d\d\d\d$/)) { // formatted date ends with year + year = (dateOnSecs === "Year" ? dateStr2.slice(-4) : require("locale").dow(date, 1)); + md = dateStr2.slice(0, -4); + if (!md.endsWith(".")) // keep separator before the year only if it is a dot (31.12. but 31/12) + md = md.slice(0, -1); + yearfirst = false; + } else { // formatted date begins with year + if (!dateStr2.match(/^\d\d\d\d/)) // if year position cannot be detected... + dateStr2 = isoStr(date); // ...use ISO date format instead + year = (dateOnSecs === "Year" ? dateStr2.slice(0, 4) : require("locale").dow(date, 1)); + md = dateStr2.slice(5); // never keep separator directly after year + yearfirst = true; + } + if (dateOnSecs === "Weekday" && upperCase) + year = year.toUpperCase(); + g.setFontAlign(0, 0); + if (vectorFont) + g.setFont("Vector", 24); + else + g.setFont("6x8", 2); + if (doColor()) + g.setColor(1, 0, 0); + g.drawString(md, x, (yearfirst ? y + (vectorFont ? 26 : 16) : y)); + g.drawString(year, x, (yearfirst ? y : y + (vectorFont ? 26 : 16))); + } else { + g.setFontAlign(0, 0).drawString(secStr, x, y); // Just the seconds centered + } + } else { // No seconds screen: Show date and optionally day of week + y += (vectorFont ? 50 : (secondsMode !== "Never") ? 52 : 40); + var dateStr = (dateOnMain === "ISO8601" ? isoStr(date) : require("locale").date(date, (dateOnMain === "Long" ? 0 : 1))); + if (upperCase) + dateStr = dateStr.toUpperCase(); + g.setFontAlign(0, 0); + if (vectorFont) + g.setFont("Vector", 24); + else + g.setFont("6x8", 2); + g.drawString(dateStr, x, y); + if (calWeek || weekDay) { + var dowcwStr = ""; + if (calWeek) + dowcwStr = " #" + ("0" + ISO8601calWeek(date)).substring(-2); + if (weekDay) + dowcwStr = require("locale").dow(date, calWeek ? 1 : 0) + dowcwStr; //weekDay e.g. Monday or weekDayShort # e.g. Mon #01 + else //week #01 + dowcwStr = /*LANG*/"week" + dowcwStr; + if (upperCase) + dowcwStr = dowcwStr.toUpperCase(); + g.drawString(dowcwStr, x, y + (vectorFont ? 26 : 16)); + } + } + + // queue next draw + queueDraw(); +} + +// Init the settings of the app +loadSettings(); +// Clear the screen once, at startup +g.clear(); +// Set dynamic state and perform initial drawing +updateState(); +// Register hooks for LCD on/off event and screen lock on/off event +Bangle.on('lcdPower', on => { + updateState(); +}); +Bangle.on('lock', on => { + updateState(); }); // Show launcher when middle button pressed Bangle.setUI("clock"); // Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); + +// end of file \ No newline at end of file diff --git a/apps/antonclk/app.png b/apps/antonclk/app.png index d96f17758..a38093c5f 100644 Binary files a/apps/antonclk/app.png and b/apps/antonclk/app.png differ diff --git a/apps/antonclk/metadata.json b/apps/antonclk/metadata.json new file mode 100644 index 000000000..def5d3b48 --- /dev/null +++ b/apps/antonclk/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "antonclk", + "name": "Anton Clock", + "version": "0.06", + "description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.", + "readme":"README.md", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"antonclk.app.js","url":"app.js"}, + {"name":"antonclk.settings.js","url":"settings.js"}, + {"name":"antonclk.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"antonclk.json"}] +} diff --git a/apps/antonclk/screenshot.png b/apps/antonclk/screenshot.png index c66f8bdd8..e949b8a24 100644 Binary files a/apps/antonclk/screenshot.png and b/apps/antonclk/screenshot.png differ diff --git a/apps/antonclk/settings.js b/apps/antonclk/settings.js new file mode 100644 index 000000000..e452b02c7 --- /dev/null +++ b/apps/antonclk/settings.js @@ -0,0 +1,107 @@ +// Settings menu for the enhanced Anton clock + +(function(back) { + var FILE = "antonclk.json"; + // Load settings + var settings = Object.assign({ + secondsOnUnlock: false, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Helper method which uses int-based menu item for set of string values + function stringItems(startvalue, writer, values) { + return { + value: (startvalue === undefined ? 0 : values.indexOf(startvalue)), + format: v => values[v], + min: 0, + max: values.length - 1, + wrap: true, + step: 1, + onchange: v => { + writer(values[v]); + writeSettings(); + } + }; + } + + // Helper method which breaks string set settings down to local settings object + function stringInSettings(name, values) { + return stringItems(settings[name], v => settings[name] = v, values); + } + + var mainmenu = { + "": { + "title": "Anton clock" + }, + "< Back": () => back(), + "Seconds...": () => E.showMenu(secmenu), + "Date": stringInSettings("dateOnMain", ["Short", "Long", "ISO8601"]), + "Show Weekday": { + value: (settings.weekDay !== undefined ? settings.weekDay : true), + format: v => v ? "On" : "Off", + onchange: v => { + settings.weekDay = v; + writeSettings(); + } + }, + "Show CalWeek": { + value: (settings.calWeek !== undefined ? settings.calWeek : false), + format: v => v ? "On" : "Off", + onchange: v => { + settings.calWeek = v; + writeSettings(); + } + }, + "Uppercase": { + value: (settings.upperCase !== undefined ? settings.upperCase : false), + format: v => v ? "On" : "Off", + onchange: v => { + settings.upperCase = v; + writeSettings(); + } + }, + "Vector font": { + value: (settings.vectorFont !== undefined ? settings.vectorFont : false), + format: v => v ? "On" : "Off", + onchange: v => { + settings.vectorFont = v; + writeSettings(); + } + }, + }; + + // Submenu + var secmenu = { + "": { + "title": "Show seconds..." + }, + "< Back": () => E.showMenu(mainmenu), + "Show": stringInSettings("secondsMode", ["Never", "Unlocked", "Always"]), + "With \":\"": { + value: (settings.secondsWithColon !== undefined ? settings.secondsWithColon : false), + format: v => v ? "On" : "Off", + onchange: v => { + settings.secondsWithColon = v; + writeSettings(); + } + }, + "Color": { + value: (settings.secondsColoured !== undefined ? settings.secondsColoured : false), + format: v => v ? "On" : "Off", + onchange: v => { + settings.secondsColoured = v; + writeSettings(); + } + }, + "Date": stringInSettings("dateOnSecs", ["No", "Year", "Weekday"]) + }; + + // Actually display the menu + E.showMenu(mainmenu); + +}); + +// end of file diff --git a/apps/arrow/metadata.json b/apps/arrow/metadata.json new file mode 100644 index 000000000..bf462e33b --- /dev/null +++ b/apps/arrow/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "arrow", + "name": "Arrow Compass", + "version": "0.05", + "description": "Moving arrow compass that points North, shows heading, with tilt correction. Based on jeffmer's Navigation Compass", + "icon": "arrow.png", + "type": "app", + "tags": "tool,outdoors", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"arrow.app.js","url":"app.js"}, + {"name":"arrow.img","url":"icon.js","evaluate":true} + ] +} diff --git a/apps/assistedgps/ChangeLog b/apps/assistedgps/ChangeLog index 5560f00bc..739ccf915 100644 --- a/apps/assistedgps/ChangeLog +++ b/apps/assistedgps/ChangeLog @@ -1 +1,3 @@ 0.01: New App! +0.02: Update to work with Bangle.js 2 +0.03: Select GNSS systems to use for Bangle.js 2 diff --git a/apps/assistedgps/custom.html b/apps/assistedgps/custom.html index 139c232af..80d68a71f 100644 --- a/apps/assistedgps/custom.html +++ b/apps/assistedgps/custom.html @@ -8,34 +8,72 @@

GPS can take a long time (~5 minutes) to get an accurate position the first time it is used. AGPS uploads a few hints to the GPS receiver about satellite positions that allow it to get a faster, more accurate fix - however they are only valid for a short period of time.

-

You can upload data that covers a longer period of time, but the upload will take longer.

-
- - - - - + -

Click

+ + diff --git a/apps/assistedgps/metadata.json b/apps/assistedgps/metadata.json new file mode 100644 index 000000000..1dbc42c87 --- /dev/null +++ b/apps/assistedgps/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "assistedgps", + "name": "Assisted GPS Update (AGPS)", + "version": "0.03", + "description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 or 2 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.", + "icon": "app.png", + "type": "RAM", + "tags": "tool,outdoors,agps", + "supports": ["BANGLEJS","BANGLEJS2"], + "custom": "custom.html", + "customConnect": true, + "storage": [] +} diff --git a/apps/astral/metadata.json b/apps/astral/metadata.json new file mode 100644 index 000000000..3317092db --- /dev/null +++ b/apps/astral/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "astral", + "name": "Astral Clock", + "version": "0.03", + "description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.", + "icon": "app-icon.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"astral.app.js","url":"app.js"}, + {"name":"astral.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/astrocalc/metadata.json b/apps/astrocalc/metadata.json new file mode 100644 index 000000000..384c7fa1e --- /dev/null +++ b/apps/astrocalc/metadata.json @@ -0,0 +1,23 @@ +{ + "id": "astrocalc", + "name": "Astrocalc", + "version": "0.02", + "description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.", + "icon": "astrocalc.png", + "tags": "app,sun,moon,cycles,tool,outdoors", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "storage": [ + {"name":"astrocalc.app.js","url":"astrocalc-app.js"}, + {"name":"suncalc.js","url":"suncalc.js"}, + {"name":"astrocalc.img","url":"astrocalc-icon.js","evaluate":true}, + {"name":"first-quarter.img","url":"first-quarter-icon.js","evaluate":true}, + {"name":"last-quarter.img","url":"last-quarter-icon.js","evaluate":true}, + {"name":"waning-crescent.img","url":"waning-crescent-icon.js","evaluate":true}, + {"name":"waning-gibbous.img","url":"waning-gibbous-icon.js","evaluate":true}, + {"name":"full.img","url":"full-icon.js","evaluate":true}, + {"name":"new.img","url":"new-icon.js","evaluate":true}, + {"name":"waxing-gibbous.img","url":"waxing-gibbous-icon.js","evaluate":true}, + {"name":"waxing-crescent.img","url":"waxing-crescent-icon.js","evaluate":true} + ] +} diff --git a/apps/astroid/metadata.json b/apps/astroid/metadata.json new file mode 100644 index 000000000..abb3681ff --- /dev/null +++ b/apps/astroid/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "astroid", + "name": "Asteroids!", + "version": "0.03", + "description": "Retro asteroids game", + "icon": "asteroids.png", + "screenshots": [{"url":"screenshot_asteroids.png"}], + "tags": "game", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"astroid.app.js","url":"asteroids.js"}, + {"name":"astroid.img","url":"asteroids-icon.js","evaluate":true} + ] +} diff --git a/apps/authentiwatch/metadata.json b/apps/authentiwatch/metadata.json new file mode 100644 index 000000000..7a0138d24 --- /dev/null +++ b/apps/authentiwatch/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "authentiwatch", + "name": "2FA Authenticator", + "shortName": "AuthWatch", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "version": "0.04", + "description": "Google Authenticator compatible tool.", + "tags": "tool", + "interface": "interface.html", + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"authentiwatch.app.js","url":"app.js"}, + {"name":"authentiwatch.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"authentiwatch.json"}] +} diff --git a/apps/awairmonitor/metadata.json b/apps/awairmonitor/metadata.json new file mode 100644 index 000000000..a58175b1b --- /dev/null +++ b/apps/awairmonitor/metadata.json @@ -0,0 +1,17 @@ +{ + "id":"awairmonitor", + "name":"Awair Monitor", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "allow_emulator": true, + "version":"0.03", + "description": "Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awair device.", + "type": "clock", + "tags": "clock,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/ballmaze/app.js b/apps/ballmaze/app.js index 862e6fc6c..2d55887f0 100644 --- a/apps/ballmaze/app.js +++ b/apps/ballmaze/app.js @@ -1,4 +1,5 @@ -(() => { +(() => { + BANGLEJS2 = process.env.HWVERSION==2; Bangle.setLCDTimeout(0); let intervalID; let settings = require("Storage").readJSON("ballmaze.json",true) || {}; @@ -6,7 +7,9 @@ // density, elasticity of bounces, "drag coefficient" const rho = 100, e = 0.3, C = 0.01; // screen width & height in pixels - const sW = 240, sH = 160; + const sW = g.getWidth(); + const sH = g.getHeight()*2/3; + const bgColour ="#f00"; // only for Bangle.js 2 // gravity constant (lowercase was already taken) const G = 9.80665; @@ -17,14 +20,16 @@ // The play area is 240x160, sizes are the ball radius, so we can use common // denominators of 120x80 to get square rooms // Reverse the order to show the easiest on top of the menu - const sizes = [1, 2, 4, 5, 8, 10, 16, 20, 40].reverse(), - // even size 1 actually works, but larger mazes take forever to generate - minSize = 4, defaultSize = 10; const sizeNames = { 1: "Insane", 2: "Gigantic", 4: "Enormous", 5: "Huge", 8: "Large", 10: "Medium", 16: "Small", 20: "Tiny", 40: "Trivial", }; - + // even size 1 actually works, but larger mazes take forever to generate + if (!BANGLEJS2) { + const sizes = [1, 2, 4, 5, 8, 10, 16, 20, 40].reverse(), minSize = 4, defaultSize = 10; + } else { + const sizes = [1, 2, 4, 5, 8, 10, 16, 20 ].reverse(), minSize = 4, defaultSize = 10; + } /** * Draw something to all screen buffers * @param draw {function} Callback which performs the drawing @@ -45,17 +50,17 @@ // use unbuffered graphics for UI stuff function showMessage(message, title) { - Bangle.setLCDMode(); + if (!BANGLEJS2) Bangle.setLCDMode(); return E.showMessage(message, title); } function showPrompt(prompt, options) { - Bangle.setLCDMode(); + if (!BANGLEJS2) Bangle.setLCDMode(); return E.showPrompt(prompt, options); } function showMenu(menu) { - Bangle.setLCDMode(); + if (!BANGLEJS2) Bangle.setLCDMode(); return E.showMenu(menu); } @@ -105,7 +110,7 @@ generateMaze(); // this shows unbuffered progress messages if (settings.cheat && r>1) findRoute(); // not enough memory for r==1 :-( - Bangle.setLCDMode("doublebuffered"); + if (!BANGLEJS2) Bangle.setLCDMode("doublebuffered"); clearAll(); drawAll(drawMaze); intervalID = setInterval(tick, 100); @@ -307,6 +312,7 @@ const range = {top: 0, left: 0, bottom: rows, right: cols}; const w = sW/cols, h = sH/rows; g.clear(); + if (BANGLEJS2) g.setBgColor(bgColour); g.setColor(0.76, 0.60, 0.42); for(let row = range.top; row<=range.bottom; row++) { for(let col = range.left; col<=range.right; col++) { diff --git a/apps/ballmaze/metadata.json b/apps/ballmaze/metadata.json new file mode 100644 index 000000000..3223789d4 --- /dev/null +++ b/apps/ballmaze/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "ballmaze", + "name": "Ball Maze", + "version": "0.02", + "description": "Navigate a ball through a maze by tilting your watch.", + "icon": "icon.png", + "type": "app", + "tags": "game", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"ballmaze.app.js","url":"app.js"}, + {"name":"ballmaze.img","url":"icon.js","evaluate":true} + ], + "data": [{"name":"ballmaze.json"}] +} diff --git a/apps/balltastic/ChangeLog b/apps/balltastic/ChangeLog index de6afabe8..6ed48b5df 100644 --- a/apps/balltastic/ChangeLog +++ b/apps/balltastic/ChangeLog @@ -1,2 +1,3 @@ 0.01: Initial version of Balltastic released! Happy! -0.02: Set LCD timeout for Espruino 2v10 compatibility \ No newline at end of file +0.02: Set LCD timeout for Espruino 2v10 compatibility +0.03: Now also works on Bangle.js 2 diff --git a/apps/balltastic/app.js b/apps/balltastic/app.js index ed5207e5f..d0262c3cb 100644 --- a/apps/balltastic/app.js +++ b/apps/balltastic/app.js @@ -1,11 +1,12 @@ +BANGLEJS2 = process.env.HWVERSION==2; Bangle.setLCDBrightness(1); -Bangle.setLCDMode("doublebuffered"); +if (!BANGLEJS2) Bangle.setLCDMode("doublebuffered"); Bangle.setLCDTimeout(0); let points = 0; let level = 1; let levelSpeedStart = 0.8; -let nextLevelPoints = 20; +let nextLevelPoints = 10; let levelSpeedFactor = 0.2; let counterWidth = 10; let gWidth = g.getWidth() - counterWidth; @@ -81,12 +82,23 @@ function drawLevelText() { g.setColor("#26b6c7"); g.setFontAlign(0, 0); g.setFont("4x6", 5); - g.drawString("Level " + level, 120, 80); + g.drawString("Level " + level, g.getWidth()/2, g.getHeight()/2); +} + +function drawPointsText() { + g.setColor("#26b6c7"); + g.setFontAlign(0, 0); + g.setFont("4x6", 2); + g.drawString("Points " + points, g.getWidth()/2, g.getHeight()-20); } function draw() { //bg - g.setColor("#71c6cf"); + if (!BANGLEJS2) { + g.setColor("#71c6cf"); + } else { + g.setColor("#002000"); + } g.fillRect(0, 0, g.getWidth(), g.getHeight()); //counter @@ -94,6 +106,7 @@ function draw() { //draw level drawLevelText(); + drawPointsText(); //dot g.setColor("#ff0000"); @@ -152,7 +165,7 @@ function count() { if (counter <= 0) { running = false; clearInterval(drawInterval); - setTimeout(function(){ E.showMessage("Press Button 1\nto restart.", "Gameover!");},50); + setTimeout(function(){ E.showMessage("Press Button 1\nto restart.", "Game over!");},50); } } diff --git a/apps/balltastic/bangle2-balltastic-screenshot.png b/apps/balltastic/bangle2-balltastic-screenshot.png new file mode 100644 index 000000000..c9e904614 Binary files /dev/null and b/apps/balltastic/bangle2-balltastic-screenshot.png differ diff --git a/apps/balltastic/metadata.json b/apps/balltastic/metadata.json new file mode 100644 index 000000000..09e265829 --- /dev/null +++ b/apps/balltastic/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "balltastic", + "name": "Balltastic", + "version": "0.03", + "description": "Simple but fun ball eats dots game.", + "icon": "app.png", + "screenshots": [{"url":"bangle2-balltastic-screenshot.png"}], + "type": "app", + "tags": "game,fun", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"balltastic.app.js","url":"app.js"}, + {"name":"balltastic.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/banglebridge/metadata.json b/apps/banglebridge/metadata.json new file mode 100644 index 000000000..8a9eaa6e4 --- /dev/null +++ b/apps/banglebridge/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "banglebridge", + "name": "BangleBridge", + "shortName": "BangleBridge", + "version": "0.01", + "description": "Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App", + "icon": "widget.png", + "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"banglebridge.wid.js","url":"widget.js"}, + {"name":"banglebridge.watch.img","url":"watch.img"}, + {"name":"banglebridge.heart.img","url":"heart.img"} + ] +} diff --git a/apps/banglerun/metadata.json b/apps/banglerun/metadata.json new file mode 100644 index 000000000..d66441c8d --- /dev/null +++ b/apps/banglerun/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "banglerun", + "name": "BangleRun", + "shortName": "BangleRun", + "version": "0.10", + "description": "An app for running sessions. Displays info and logs your run for later viewing.", + "icon": "banglerun.png", + "tags": "run,running,fitness,outdoors", + "supports": ["BANGLEJS"], + "interface": "interface.html", + "allow_emulator": false, + "storage": [ + {"name":"banglerun.app.js","url":"app.js"}, + {"name":"banglerun.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/banglexercise/ChangeLog b/apps/banglexercise/ChangeLog new file mode 100644 index 000000000..5f1d3bd7d --- /dev/null +++ b/apps/banglexercise/ChangeLog @@ -0,0 +1,4 @@ +0.01: New App! +0.02: Add sit ups + Add more feedback to the user about the exercises + Clean up code diff --git a/apps/banglexercise/README.md b/apps/banglexercise/README.md new file mode 100644 index 000000000..c9f9ec38a --- /dev/null +++ b/apps/banglexercise/README.md @@ -0,0 +1,40 @@ +# BanglExercise + +Can automatically track exercises while wearing the Bangle.js watch. + +Currently only push ups, curls and sit ups are supported. + +## Disclaimer + +This app is experimental but it seems to work quiet reliable for me. +It could be and is likely that the threshold values for detecting exercises do not work for everyone. +Therefore it would be great if we could improve this app together :-) + + +## Usage + +Select the exercise type you want to practice and go for it! +Press stop to end your exercise. + + +## Screenshots +![](screenshot.png) + +## TODO +* Add other exercise types: + * Rope jumps + * Star jumps + * ... +* Save exercise summaries to file system +* Configure daily goal for exercises +* Find a nicer icon + + +## Contribute +Feel free to send in improvements and remarks. + +## Creator +Marco ([myxor](https://github.com/myxor)) + +## Icons +Icons taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0 diff --git a/apps/banglexercise/app-icon.js b/apps/banglexercise/app-icon.js new file mode 100644 index 000000000..e1923bf54 --- /dev/null +++ b/apps/banglexercise/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwIbYh/8AYM/+EP/wFBv4FB/4FB/4FHAwIEBAv4FPAgIGCAosHAofggYFD4EABgXgOgIFLDAQWBAo0BAoOAVIV/UYQABj/4AocDCwQFTg46CEY4vFAopBBApIAVA==")) diff --git a/apps/banglexercise/app.js b/apps/banglexercise/app.js new file mode 100644 index 000000000..bc6e35f07 --- /dev/null +++ b/apps/banglexercise/app.js @@ -0,0 +1,384 @@ +const Layout = require("Layout"); +const heatshrink = require('heatshrink'); +const storage = require('Storage'); + +let tStart; +let historyY = []; +let historyZ = []; +let historyAvgY = []; +let historyAvgZ = []; +let historySlopeY = []; +let historySlopeZ = []; + +let lastZeroPassCameFromPositive; +let lastZeroPassTime = 0; + +let lastExerciseCompletionTime = 0; +let lastExerciseHalfCompletionTime = 0; + +let exerciseType = { + "id": "", + "name": "" +}; + +// add new exercises here: +const exerciseTypes = [{ + "id": "pushup", + "name": "push ups", + "useYaxis": true, + "useZaxis": false, + "threshold": 2500, + "thresholdMinTime": 800, // mininmal time between two push ups in ms + "thresholdMaxTime": 5000, // maximal time between two push ups in ms + "thresholdMinDurationTime": 600, // mininmal duration of half a push up in ms + }, + { + "id": "curl", + "name": "curls", + "useYaxis": true, + "useZaxis": false, + "threshold": 2500, + "thresholdMinTime": 800, // mininmal time between two curls in ms + "thresholdMaxTime": 5000, // maximal time between two curls in ms + "thresholdMinDurationTime": 500, // mininmal duration of half a curl in ms + }, + { + "id": "situp", + "name": "sit ups", + "useYaxis": false, + "useZaxis": true, + "threshold": 3500, + "thresholdMinTime": 800, // mininmal time between two sit ups in ms + "thresholdMaxTime": 5000, // maximal time between two sit ups in ms + "thresholdMinDurationTime": 500, // mininmal duration of half a sit up in ms + } +]; +let exerciseCounter = 0; + +let layout; +let recordActive = false; + +// Size of average window for data analysis +const avgSize = 6; + +let hrtValue; + +let settings = storage.readJSON("banglexercise.json", 1) || { + 'buzz': true +}; + +function showMainMenu() { + let menu; + menu = { + "": { + title: "BanglExercise" + } + }; + + exerciseTypes.forEach(function(et) { + menu[et.name] = function() { + exerciseType = et; + E.showMenu(); + startTraining(); + }; + }); + + if (exerciseCounter > 0) { + menu["--------"] = { + value: "" + }; + menu["Last:"] = { + value: exerciseCounter + " " + exerciseType.name + }; + } + menu.exit = function() { + load(); + }; + + E.showMenu(menu); +} + +function accelHandler(accel) { + if (!exerciseType) return; + const t = Math.round(new Date().getTime()); // time in ms + const y = exerciseType.useYaxis ? accel.y * 8192 : 0; + const z = exerciseType.useZaxis ? accel.z * 8192 : 0; + //console.log(t, y, z); + + if (exerciseType.useYaxis) { + while (historyY.length > avgSize) + historyY.shift(); + + historyY.push(y); + + if (historyY.length > avgSize / 2) { + const avgY = E.sum(historyY) / historyY.length; + historyAvgY.push([t, avgY]); + while (historyAvgY.length > avgSize) + historyAvgY.shift(); + } + } + + if (exerciseType.useZaxis) { + while (historyZ.length > avgSize) + historyZ.shift(); + + historyZ.push(z); + + if (historyZ.length > avgSize / 2) { + const avgZ = E.sum(historyZ) / historyZ.length; + historyAvgZ.push([t, avgZ]); + while (historyAvgZ.length > avgSize) + historyAvgZ.shift(); + } + } + + // slope for Y + if (exerciseType.useYaxis) { + let l = historyAvgY.length; + if (l > 1) { + const p1 = historyAvgY[l - 2]; + const p2 = historyAvgY[l - 1]; + const slopeY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000); + // we use this data for exercises which can be detected by using Y axis data + isValidExercise(slopeY, t); + } + } + + // slope for Z + if (exerciseType.useZaxis) { + l = historyAvgZ.length; + if (l > 1) { + const p1 = historyAvgZ[l - 2]; + const p2 = historyAvgZ[l - 1]; + const slopeZ = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000); + // we use this data for some exercises which can be detected by using Z axis data + isValidExercise(slopeZ, t); + } + } +} + +/* + * Check if slope value of Y-axis or Z-axis data (depending on exercise type) looks like an exercise + * + * In detail we look for slop values which are bigger than the configured threshold for the current exercise type + * Then we look for two consecutive slope values of which one is above 0 and the other is below zero. + * If we find one pair of these values this could be part of one exercise. + * Then we look for a pair of values which cross the zero from the otherwise direction + */ +function isValidExercise(slope, t) { + if (!exerciseType) return; + + const threshold = exerciseType.threshold; + const historySlopeValues = exerciseType.useYaxis ? historySlopeY : historySlopeZ; + const thresholdMinTime = exerciseType.thresholdMinTime; + const thresholdMaxTime = exerciseType.thresholdMaxTime; + const thresholdMinDurationTime = exerciseType.thresholdMinDurationTime; + const exerciseName = exerciseType.name; + + + if (Math.abs(slope) >= threshold) { + historySlopeValues.push([t, slope]); + //console.log(t, Math.abs(slope)); + + const lSlopeHistory = historySlopeValues.length; + if (lSlopeHistory > 1) { + const p1 = historySlopeValues[lSlopeHistory - 1][1]; + const p2 = historySlopeValues[lSlopeHistory - 2][1]; + if (p1 > 0 && p2 < 0) { + if (lastZeroPassCameFromPositive == false) { + lastExerciseHalfCompletionTime = t; + console.log(t, exerciseName + " half complete..."); + + layout.progress.label = "½"; + layout.recording.label = "TRAINING"; + g.clear(); + layout.render(); + } + + lastZeroPassCameFromPositive = true; + lastZeroPassTime = t; + } + if (p2 > 0 && p1 < 0) { + if (lastZeroPassCameFromPositive == true) { + const tDiffLastExercise = t - lastExerciseCompletionTime; + const tDiffStart = t - tStart; + console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart)); + + // check minimal time between exercises: + if ((lastExerciseCompletionTime <= 0 && tDiffStart >= thresholdMinTime) || tDiffLastExercise >= thresholdMinTime) { + + // check maximal time between exercises: + if (lastExerciseCompletionTime <= 0 || tDiffLastExercise <= thresholdMaxTime) { + + // check minimal duration of exercise: + const tDiffExerciseHalfCompletion = t - lastExerciseHalfCompletionTime; + if (tDiffExerciseHalfCompletion > thresholdMinDurationTime) { + //console.log(t, exerciseName + " complete!!!"); + + lastExerciseCompletionTime = t; + exerciseCounter++; + + layout.count.label = exerciseCounter; + layout.progress.label = ""; + layout.recording.label = "Good!"; + + g.clear(); + layout.render(); + + if (settings.buzz) + Bangle.buzz(200, 0.5); + } else { + console.log(t, exerciseName + " too quick for duration time threshold!"); // thresholdMinDurationTime + lastExerciseCompletionTime = t; + + layout.recording.label = "Go slower!"; + g.clear(); + layout.render(); + } + } else { + console.log(t, exerciseName + " top slow for time threshold!"); // thresholdMaxTime + lastExerciseCompletionTime = t; + + layout.recording.label = "Go faster!"; + g.clear(); + layout.render(); + } + } else { + console.log(t, exerciseName + " too quick for time threshold!"); // thresholdMinTime + lastExerciseCompletionTime = t; + + layout.recording.label = "Go slower!"; + g.clear(); + layout.render(); + } + } + + lastZeroPassCameFromPositive = false; + lastZeroPassTime = t; + } + } + } +} + + +function reset() { + historyY = []; + historyZ = []; + historyAvgY = []; + historyAvgZ = []; + historySlopeY = []; + historySlopeZ = []; + + lastZeroPassCameFromPositive = undefined; + lastZeroPassTime = 0; + lastExerciseHalfCompletionTime = 0; + lastExerciseCompletionTime = 0; + exerciseCounter = 0; + tStart = 0; +} + + +function startTraining() { + if (recordActive) return; + g.clear(1); + reset(); + Bangle.setLCDTimeout(0); // force LCD on + Bangle.setHRMPower(1, "banglexercise"); + if (!hrtValue) hrtValue = "..."; + + layout = new Layout({ + type: "v", + c: [{ + type: "txt", + id: "type", + font: "6x8:2", + label: exerciseType.name, + pad: 5 + }, + { + type: "h", + c: [{ + type: "txt", + id: "count", + font: exerciseCounter < 100 ? "6x8:9" : "6x8:8", + label: exerciseCounter, + pad: 5 + }, + { + type: "txt", + id: "progress", + font: "6x8:2", + label: "", + pad: 5 + }, + ] + }, + { + type: "h", + c: [{ + type: "img", + pad: 4, + src: function() { + return heatshrink.decompress(atob("h0OwYOLkmQhMkgACByVJgESpIFBpEEBAIFBCgIFCCgsABwcAgQOCAAMSpAwDyBNM")); + } + }, + { + type: "txt", + id: "hrtRate", + font: "6x8:2", + label: hrtValue, + pad: 5 + }, + ] + }, + { + type: "txt", + id: "recording", + font: "6x8:2", + label: "TRAINING", + bgCol: "#f00", + pad: 5, + fillx: 1 + }, + ] + }, { + btns: [{ + label: "STOP", + cb: () => { + stopTraining(); + } + }], + lazy: false + }); + layout.render(); + + Bangle.setPollInterval(80); // 12.5 Hz + + tStart = new Date().getTime(); + recordActive = true; + if (settings.buzz) + Bangle.buzz(200, 1); + + // delay start a little bit + setTimeout(() => { + Bangle.on('accel', accelHandler); + }, 1000); +} + +function stopTraining() { + if (!recordActive) return; + + g.clear(1); + Bangle.removeListener('accel', accelHandler); + Bangle.setHRMPower(0, "banglexercise"); + showMainMenu(); + recordActive = false; +} + +Bangle.on('HRM', function(hrm) { + hrtValue = hrm.bpm; +}); + +g.clear(1); +showMainMenu(); diff --git a/apps/banglexercise/app.png b/apps/banglexercise/app.png new file mode 100644 index 000000000..ee7332063 Binary files /dev/null and b/apps/banglexercise/app.png differ diff --git a/apps/banglexercise/metadata.json b/apps/banglexercise/metadata.json new file mode 100644 index 000000000..9bb93f112 --- /dev/null +++ b/apps/banglexercise/metadata.json @@ -0,0 +1,21 @@ +{ "id": "banglexercise", + "name": "BanglExercise", + "shortName":"BanglExercise", + "version":"0.02", + "description": "Can automatically track exercises while wearing the Bangle.js watch.", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "app", + "tags": "sport", + "supports" : ["BANGLEJS2"], + "allow_emulator":true, + "readme": "README.md", + "storage": [ + {"name":"banglexercise.app.js","url":"app.js"}, + {"name":"banglexercise.img","url":"app-icon.js","evaluate":true}, + {"name":"banglexercise.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"banglexercise.json"} + ] +} diff --git a/apps/banglexercise/screenshot.png b/apps/banglexercise/screenshot.png new file mode 100644 index 000000000..417be685b Binary files /dev/null and b/apps/banglexercise/screenshot.png differ diff --git a/apps/banglexercise/settings.js b/apps/banglexercise/settings.js new file mode 100644 index 000000000..3208c6eca --- /dev/null +++ b/apps/banglexercise/settings.js @@ -0,0 +1,21 @@ +(function(back) { + const SETTINGS_FILE = "banglexercise.json"; + const storage = require('Storage'); + let settings = storage.readJSON(SETTINGS_FILE, 1) || {}; + function save(key, value) { + settings[key] = value; + storage.write(SETTINGS_FILE, settings); + } + E.showMenu({ + '': { 'title': 'BanglExercise' }, + '< Back': back, + 'Buzz': { + value: "buzz" in settings ? settings.buzz : false, + format: () => (settings.buzz ? 'Yes' : 'No'), + onchange: () => { + settings.buzz = !settings.buzz; + save('buzz', settings.buzz); + } + } + }); +}); diff --git a/apps/barclock/metadata.json b/apps/barclock/metadata.json new file mode 100644 index 000000000..2b7be355f --- /dev/null +++ b/apps/barclock/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "barclock", + "name": "Bar Clock", + "version": "0.09", + "description": "A simple digital clock showing seconds as a bar", + "icon": "clock-bar.png", + "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"barclock.app.js","url":"clock-bar.js"}, + {"name":"barclock.img","url":"clock-bar-icon.js","evaluate":true} + ] +} diff --git a/apps/batchart/metadata.json b/apps/batchart/metadata.json new file mode 100644 index 000000000..f2ed9f0b6 --- /dev/null +++ b/apps/batchart/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "batchart", + "name": "Battery Chart", + "shortName": "Battery Chart", + "version": "0.10", + "description": "A widget and an app for recording and visualizing battery percentage over time.", + "icon": "app.png", + "tags": "app,widget,battery,time,record,chart,tool", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"batchart.wid.js","url":"widget.js"}, + {"name":"batchart.app.js","url":"app.js"}, + {"name":"batchart.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/batclock/metadata.json b/apps/batclock/metadata.json new file mode 100644 index 000000000..8aa115780 --- /dev/null +++ b/apps/batclock/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "batclock", + "name": "Bat Clock", + "shortName": "Bat Clock", + "version": "0.02", + "description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.", + "icon": "bat-clock.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"batclock.app.js","url":"bat-clock.app.js"}, + {"name":"batclock.img","url":"bat-clock.icon.js","evaluate":true} + ] +} diff --git a/apps/battleship/metadata.json b/apps/battleship/metadata.json new file mode 100644 index 000000000..12e92c1d7 --- /dev/null +++ b/apps/battleship/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "battleship", + "name": "Battleship", + "version": "0.01", + "description": "The classic game of battleship", + "icon": "battleship-icon.png", + "tags": "game", + "supports": ["BANGLEJS"], + "screenshots": [{"url":"bangle1-battle-ship-screenshot.png"}], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"battleship.app.js","url":"battleship.js"}, + {"name":"battleship.img","url":"battleship-icon.js","evaluate":true} + ] +} diff --git a/apps/bclock/metadata.json b/apps/bclock/metadata.json new file mode 100644 index 000000000..94219a30b --- /dev/null +++ b/apps/bclock/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "bclock", + "name": "Binary Clock", + "version": "0.03", + "description": "A simple binary clock watch face", + "icon": "clock-binary.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "screenshots": [{"url":"bangle1-binary-clock-screenshot.png"}], + "storage": [ + {"name":"bclock.app.js","url":"clock-binary.js"}, + {"name":"bclock.img","url":"clock-binary-icon.js","evaluate":true} + ] +} diff --git a/apps/beebclock/metadata.json b/apps/beebclock/metadata.json new file mode 100644 index 000000000..31316a80c --- /dev/null +++ b/apps/beebclock/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "beebclock", + "name": "Beeb Clock", + "version": "0.05", + "description": "Clock face that may be coincidentally familiar to BBC viewers", + "icon": "beebclock.png", + "type": "clock", + "tags": "clock", + "screenshots": [{"url":"bangle1-beeb-clock-screenshot.png"}], + "supports": ["BANGLEJS"], + "allow_emulator": true, + "storage": [ + {"name":"beebclock.app.js","url":"beebclock.js"}, + {"name":"beebclock.img","url":"beebclock-icon.js","evaluate":true} + ] +} diff --git a/apps/beer/metadata.json b/apps/beer/metadata.json new file mode 100644 index 000000000..cf69aee90 --- /dev/null +++ b/apps/beer/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "beer", + "name": "Beer Compass", + "version": "0.01", + "description": "Uploads all the pubs in an area onto your watch, so it can always point you at the nearest one", + "icon": "app.png", + "tags": "", + "supports": ["BANGLEJS"], + "custom": "custom.html", + "storage": [ + {"name":"beer.app.js"}, + {"name":"beer.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/berlinc/metadata.json b/apps/berlinc/metadata.json new file mode 100644 index 000000000..49601cbd3 --- /dev/null +++ b/apps/berlinc/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "berlinc", + "name": "Berlin Clock", + "version": "0.05", + "description": "Berlin Clock (see https://en.wikipedia.org/wiki/Mengenlehreuhr)", + "icon": "berlin-clock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "screenshots": [{"url":"berlin-clock-screenshot.png"}], + "storage": [ + {"name":"berlinc.app.js","url":"berlin-clock.js"}, + {"name":"berlinc.img","url":"berlin-clock-icon.js","evaluate":true} + ] +} diff --git a/apps/binclock/metadata.json b/apps/binclock/metadata.json new file mode 100644 index 000000000..d17045868 --- /dev/null +++ b/apps/binclock/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "binclock", + "name": "Binary Clock", + "shortName": "Binary Clock", + "version": "0.03", + "description": "A binary clock with hours and minutes. BTN1 toggles a digital clock.", + "icon": "app.png", + "type": "clock", + "tags": "clock,binary", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"binclock.app.js","url":"app.js"}, + {"name":"binclock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/binwatch/metadata.json b/apps/binwatch/metadata.json new file mode 100644 index 000000000..0b5fb2c72 --- /dev/null +++ b/apps/binwatch/metadata.json @@ -0,0 +1,19 @@ +{ "id": "binwatch", + "name": "Binary Watch", + "shortName":"BinWatch", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "version":"0.04", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "allow_emulator":true, + "description": "Famous binary watch", + "tags": "clock", + "type": "clock", + "storage": [ + {"name":"binwatch.app.js","url":"app.js"}, + {"name":"binwatch.bg176.img","url":"Background176_center.img"}, + {"name":"binwatch.bg240.img","url":"Background240_center.img"}, + {"name":"binwatch.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/blackjack/metadata.json b/apps/blackjack/metadata.json new file mode 100644 index 000000000..331c64040 --- /dev/null +++ b/apps/blackjack/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "blackjack", + "name": "Black Jack game", + "shortName": "Black Jack game", + "version": "0.02", + "description": "Simple implementation of card game Black Jack", + "icon": "blackjack.png", + "tags": "game", + "supports": ["BANGLEJS"], + "screenshots": [{"url":"bangle1-black-jack-game-screenshot.png"}], + "allow_emulator": true, + "storage": [ + {"name":"blackjack.app.js","url":"blackjack.app.js"}, + {"name":"blackjack.img","url":"blackjack-icon.js","evaluate":true} + ] +} diff --git a/apps/bledetect/metadata.json b/apps/bledetect/metadata.json new file mode 100644 index 000000000..f5e0ffb19 --- /dev/null +++ b/apps/bledetect/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "bledetect", + "name": "BLE Detector", + "shortName": "BLE Detector", + "version": "0.03", + "description": "Detect BLE devices and show some informations.", + "icon": "bledetect.png", + "tags": "app,bluetooth,tool", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"bledetect.app.js","url":"bledetect.js"}, + {"name":"bledetect.img","url":"bledetect-icon.js","evaluate":true} + ] +} diff --git a/apps/blescan/metadata.json b/apps/blescan/metadata.json new file mode 100644 index 000000000..420b45cb6 --- /dev/null +++ b/apps/blescan/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "blescan", + "name": "BLE Scanner", + "version": "0.01", + "description": "Scan for advertising BLE devices", + "icon": "blescan.png", + "tags": "bluetooth", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"blescan.app.js","url":"blescan.js"}, + {"name":"blescan.img","url":"blescan-icon.js","evaluate":true} + ] +} diff --git a/apps/blobclk/metadata.json b/apps/blobclk/metadata.json new file mode 100644 index 000000000..85d7deabe --- /dev/null +++ b/apps/blobclk/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "blobclk", + "name": "Large Digit Blob Clock", + "shortName": "Blob Clock", + "version": "0.06", + "description": "A clock with big digits", + "icon": "clock-blob.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "screenshots": [{"url":"bangle2-large-digit-blob-clock-screenshot.png"},{"url":"bangle1-large-digit-blob-clock-screenshot.png"}], + "storage": [ + {"name":"blobclk.app.js","url":"clock-blob.js"}, + {"name":"blobclk.img","url":"clock-blob-icon.js","evaluate":true} + ] +} diff --git a/apps/bluetoothdock/metadata.json b/apps/bluetoothdock/metadata.json new file mode 100644 index 000000000..3c12b4c15 --- /dev/null +++ b/apps/bluetoothdock/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "bluetoothdock", + "name": "Bluetooth Dock", + "shortName": "Dock", + "version": "0.01", + "description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen", + "icon": "app.png", + "tags": "bluetooth", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"bluetoothdock.app.js","url":"app.js"}, + {"name":"bluetoothdock.boot.js","url":"boot.js"}, + {"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/boldclk/metadata.json b/apps/boldclk/metadata.json new file mode 100644 index 000000000..7e3941cb3 --- /dev/null +++ b/apps/boldclk/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "boldclk", + "name": "Bold Clock", + "version": "0.05", + "description": "Simple, readable and practical clock", + "icon": "bold_clock.png", + "screenshots": [{"url":"screenshot_bold.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"boldclk.app.js","url":"bold_clock.js"}, + {"name":"boldclk.img","url":"bold_clock-icon.js","evaluate":true} + ] +} diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index d6619822b..702a8091e 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -44,3 +44,4 @@ 0.38: Option to log to file if settings.log==2 0.39: Fix passkey support (fix https://github.com/espruino/Espruino/issues/2035) 0.40: Bootloader now rebuilds for new firmware versions +0.41: Add Keyboard and Mouse Bluetooth HID option diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 664d64ee7..1b826de5a 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -18,6 +18,7 @@ boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`; if (s.ble!==false) { if (s.HID) { // Human interface device if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`; + else if (s.HID=="com") boot += `Bangle.HID = E.toUint8Array(atob("BQEJAqEBhQEJAaEABQkZASkFFQAlAZUFdQGBApUBdQOBAwUBCTAJMQk4FYElf3UIlQOBBgUMCjgCFYElf3UIlQGBBsDABQEJBqEBhQIFBxngKecVACUBdQGVCIECdQiVAYEBGQApcxUAJXOVBXUIgQDA"));` else if (s.HID=="kb") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBBQcZ4CnnFQAlAXUBlQiBApUBdQiBAZUFdQEFCBkBKQWRApUBdQORAZUGdQgVACVzBQcZAClzgQAJBRUAJv8AdQiVArECwA=="));` else /*kbmedia*/boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA=="));`; boot += `bleServiceOptions.hid=Bangle.HID;\n`; diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json new file mode 100644 index 000000000..ebbf762c0 --- /dev/null +++ b/apps/boot/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "boot", + "name": "Bootloader", + "version": "0.41", + "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", + "icon": "bootloader.png", + "type": "bootloader", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":".boot0","url":"boot0.js"}, + {"name":".bootcde","url":"bootloader.js"}, + {"name":"bootupdate.js","url":"bootupdate.js"} + ], + "sortorder": -10 +} diff --git a/apps/bootgattbat/metadata.json b/apps/bootgattbat/metadata.json new file mode 100644 index 000000000..95a521f47 --- /dev/null +++ b/apps/bootgattbat/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "bootgattbat", + "name": "BLE GATT Battery Service", + "shortName": "BLE Battery Service", + "version": "0.01", + "description": "Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth.\n", + "icon": "bluetooth.png", + "type": "bootloader", + "tags": "battery,ble,bluetooth,gatt", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"gattbat.boot.js","url":"boot.js"} + ] +} diff --git a/apps/breath/metadata.json b/apps/breath/metadata.json new file mode 100644 index 000000000..070a9a79a --- /dev/null +++ b/apps/breath/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "breath", + "name": "Breathing App", + "shortName": "Breathing App", + "version": "0.01", + "description": "app to aid relaxation and train breath syncronicity using haptics and visualisation, also displays HR", + "icon": "app-icon.png", + "tags": "tools,health", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"breath.app.js","url":"app.js"}, + {"name":"breath.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"breath.settings.json","url":"settings.json"}] +} diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog index 5560f00bc..c035dde79 100644 --- a/apps/bthrm/ChangeLog +++ b/apps/bthrm/ChangeLog @@ -1 +1,9 @@ 0.01: New App! +0.02: Make overriding the HRM event optional + Emit BTHRM event for external sensor + Add recorder app plugin +0.03: Prevent readings from internal sensor mixing into BT values + Mark events with src property + Show actual source of event in app +0.04: Automatically reconnect BT sensor + App buzzes if no BTHRM events for more than 3 seconds diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js index 88e574480..e651515d5 100644 --- a/apps/bthrm/boot.js +++ b/apps/bthrm/boot.js @@ -1,79 +1,218 @@ (function() { - var log = function() {};//print - var gatt; - var status; - - Bangle.isHRMOn = function() { - return (status=="searching" || status=="connecting") || (gatt!==undefined); + //var sf = require("Storage").open("bthrm.log","a"); + var log = function(text, param){ + /*var logline = Date.now().toFixed(3) + " - " + text; + if (param){ + logline += " " + JSON.stringify(param); + } + sf.write(logline + "\n"); + print(logline);*/ } - Bangle.setHRMPower = function(isOn, app) { + + log("Start"); + + var blockInit = false; + var gatt; + var currentRetryTimeout; + var initialRetryTime = 40; + var maxRetryTime = 60000; + var retryTime = initialRetryTime; + + var origIsHRMOn = Bangle.isHRMOn; + + Bangle.isBTHRMOn = function(){ + return (gatt!==undefined && gatt.connected); + }; + + Bangle.isHRMOn = function() { + var settings = require('Storage').readJSON("bthrm.json", true) || {}; + + if (settings.enabled && !settings.replace){ + return origIsHRMOn(); + } else if (settings.enabled && settings.replace){ + return Bangle.isBTHRMOn(); + } + return origIsHRMOn() || Bangle.isBTHRMOn(); + }; + + var serviceFilters = [{ + services: [ + "180d" + ] + }]; + + function retry(){ + log("Retry with time " + retryTime); + if (currentRetryTimeout){ + log("Clearing timeout " + currentRetryTimeout); + clearTimeout(currentRetryTimeout); + currentRetryTimeout = undefined; + } + + var clampedTime = retryTime < 200 ? 200 : initialRetryTime; + currentRetryTimeout = setTimeout(() => { + log("Set timeout for retry as " + clampedTime); + initBt(); + }, clampedTime); + + retryTime = Math.pow(retryTime, 1.1); + if (retryTime > maxRetryTime){ + retryTime = maxRetryTime; + } + } + + function onDisconnect(reason) { + log("Disconnect: " + reason); + log("Gatt: ", gatt); + retry(); + } + + function onCharacteristic(event) { + var settings = require('Storage').readJSON("bthrm.json", true) || {}; + var dv = event.target.value; + var flags = dv.getUint8(0); + // 0 = 8 or 16 bit + // 1,2 = sensor contact + // 3 = energy expended shown + // 4 = RR interval + var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit + /* var idx = 2 + (flags&1); // index of next field + if (flags&8) idx += 2; // energy expended + if (flags&16) { + var interval = dv.getUint16(idx,1); // in milliseconds + }*/ + + Bangle.emit(settings.replace ? "HRM" : "BTHRM", { + bpm: bpm, + confidence: bpm == 0 ? 0 : 100, + src: settings.replace ? "bthrm" : undefined + }); + } + + var reUseCounter=0; + + function initBt() { + log("initBt with blockInit: " + blockInit); + if (blockInit){ + retry(); + return; + } + + blockInit = true; + + var connectionPromise; + + if (reUseCounter > 3){ + log("Reuse counter to high") + if (gatt.connected == true){ + try { + log("Force disconnect with gatt: ", gatt); + gatt.disconnect(); + } catch(e) { + log("Error during force disconnect", e); + } + } + gatt=undefined; + reUseCounter = 0; + } + + if (!gatt){ + var requestPromise = NRF.requestDevice({ filters: serviceFilters }); + connectionPromise = requestPromise.then(function(device) { + gatt = device.gatt; + log("Gatt after request:", gatt); + gatt.device.on('gattserverdisconnected', onDisconnect); + }); + } else { + reUseCounter++; + log("Reusing gatt:", gatt); + connectionPromise = gatt.connect(); + } + + + var servicePromise = connectionPromise.then(function() { + return gatt.getPrimaryService(0x180d); + }); + + var characteristicPromise = servicePromise.then(function(service) { + log("Got service:", service); + return service.getCharacteristic(0x2A37); + }); + + var notificationPromise = characteristicPromise.then(function(c) { + log("Got characteristic:", c); + c.on('characteristicvaluechanged', onCharacteristic); + return c.startNotifications(); + }); + notificationPromise.then(()=>{ + log("Wait for notifications"); + retryTime = initialRetryTime; + blockInit=false; + }); + notificationPromise.catch((e) => { + log("Error:", e); + blockInit = false; + retry(); + }); + } + + + Bangle.setBTHRMPower = function(isOn, app) { + var settings = require('Storage').readJSON("bthrm.json", true) || {}; + // Do app power handling if (!app) app="?"; - log("setHRMPower ->", isOn, app); if (Bangle._PWR===undefined) Bangle._PWR={}; - if (Bangle._PWR.HRM===undefined) Bangle._PWR.HRM=[]; - if (isOn && !Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM.push(app); - if (!isOn && Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM = Bangle._PWR.HRM.filter(a=>a!=app); - isOn = Bangle._PWR.HRM.length; + if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[]; + if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app); + if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!=app); + isOn = Bangle._PWR.BTHRM.length; // so now we know if we're really on if (isOn) { - log("setHRMPower on", app); - if (!Bangle.isHRMOn()) { - log("HRM not already on"); - status = "searching"; - NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) { - log("Found device "+device.id); - status = "connecting"; - device.on('gattserverdisconnected', function(reason) { - gatt = undefined; - }); - return device.gatt.connect(); - }).then(function(g) { - log("Connected"); - gatt = g; - return gatt.getPrimaryService(0x180D); - }).then(function(service) { - return service.getCharacteristic(0x2A37); - }).then(function(characteristic) { - log("Got characteristic"); - characteristic.on('characteristicvaluechanged', function(event) { - var dv = event.target.value; - var flags = dv.getUint8(0); - // 0 = 8 or 16 bit - // 1,2 = sensor contact - // 3 = energy expended shown - // 4 = RR interval - var bpm = (flags&1) ? (dv.getUint16(1)/100/* ? */) : dv.getUint8(1); // 8 or 16 bit - /* var idx = 2 + (flags&1); // index of next field - if (flags&8) idx += 2; // energy expended - if (flags&16) { - var interval = dv.getUint16(idx,1); // in milliseconds - }*/ - Bangle.emit('HRM',{ - bpm:bpm, - confidence:100 - }); - }); - return characteristic.startNotifications(); - }).then(function() { - log("Ready"); - status = "ok"; - }).catch(function(err) { - log("Error",err); - gatt = undefined; - status = "error"; - }); + if (!Bangle.isBTHRMOn()) { + initBt(); } } else { // not on - log("setHRMPower off", app); + log("Power off for " + app); if (gatt) { - log("HRM connected - disconnecting"); - status = undefined; - try {gatt.disconnect();}catch(e) { - log("HRM disconnect error", e); + try { + log("Disconnect with gatt: ", gatt); + gatt.disconnect(); + } catch(e) { + log("Error during disconnect", e); } + blockInit = false; gatt = undefined; } } }; + + var origSetHRMPower = Bangle.setHRMPower; + + Bangle.setHRMPower = function(isOn, app) { + log("setHRMPower for " + app + ":" + (isOn?"on":"off")); + var settings = require('Storage').readJSON("bthrm.json", true) || {}; + if (settings.enabled || !isOn){ + log("Enable BTHRM power"); + Bangle.setBTHRMPower(isOn, app); + } + if ((settings.enabled && !settings.replace) || !settings.enabled || !isOn){ + log("Enable HRM power"); + origSetHRMPower(isOn, app); + } + } + + var settings = require('Storage').readJSON("bthrm.json", true) || {}; + if (settings.enabled && settings.replace){ + log("Replace HRM event"); + if (!(Bangle._PWR===undefined) && !(Bangle._PWR.HRM===undefined)){ + for (var i = 0; i < Bangle._PWR.HRM.length; i++){ + var app = Bangle._PWR.HRM[i]; + log("Moving app " + app); + origSetHRMPower(0, app); + Bangle.setBTHRMPower(1, app); + if (Bangle._PWR.HRM===undefined) break; + } + } + } })(); diff --git a/apps/bthrm/bthrm.js b/apps/bthrm/bthrm.js new file mode 100644 index 000000000..de769d085 --- /dev/null +++ b/apps/bthrm/bthrm.js @@ -0,0 +1,69 @@ +var btm = g.getHeight()-1; +var eventInt = null; +var eventBt = null; +var counterInt = 0; +var counterBt = 0; + + +function draw(y, event, type, counter) { + var px = g.getWidth()/2; + g.reset(); + g.setFontAlign(0,0); + g.clearRect(0,y,g.getWidth(),y+75); + if (type == null || event == null || counter == 0){ + return; + } + var str = event.bpm + ""; + g.setFontVector(40).drawString(str,px,y+20); + str = "Confidence: " + event.confidence; + g.setFontVector(12).drawString(str,px,y+50); + str = "Event: " + type; + if (type == "HRM") str += " Source: " + (event.src ? event.src : "internal"); + g.setFontVector(12).drawString(str,px,y+60); +} + +function onBtHrm(e) { + //print("Event for BT " + JSON.stringify(e)); + if (e.bpm == 0){ + Bangle.buzz(100,0.2); + } + if (counterBt == 0){ + Bangle.buzz(200,0.5); + } + counterBt += 3; + eventBt = e; +} + +function onHrm(e) { + //print("Event for Int " + JSON.stringify(e)); + counterInt += 3; + eventInt = e; +} + +Bangle.on('BTHRM', onBtHrm); +Bangle.on('HRM', onHrm); + +Bangle.setHRMPower(1,'bthrm'); + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +g.reset().setFont("6x8",2).setFontAlign(0,0); +g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16); + +function drawInt(){ + counterInt--; + if (counterInt < 0) counterInt = 0; + if (counterInt > 3) counterInt = 3; + draw(24, eventInt, "HRM", counterInt); +} +function drawBt(){ + counterBt--; + if (counterBt < 0) counterBt = 0; + if (counterBt > 3) counterBt = 3; + draw(100, eventBt, "BTHRM", counterBt); +} + +var interval = setInterval(drawInt, 1000); +var interval = setInterval(drawBt, 1000); diff --git a/apps/bthrm/metadata.json b/apps/bthrm/metadata.json new file mode 100644 index 000000000..68734aafe --- /dev/null +++ b/apps/bthrm/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "bthrm", + "name": "Bluetooth Heart Rate Monitor", + "shortName": "BT HRM", + "version": "0.04", + "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", + "icon": "app.png", + "type": "app", + "tags": "health,bluetooth", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"bthrm.app.js","url":"bthrm.js"}, + {"name":"bthrm.recorder.js","url":"recorder.js"}, + {"name":"bthrm.boot.js","url":"boot.js"}, + {"name":"bthrm.img","url":"app-icon.js","evaluate":true}, + {"name":"bthrm.settings.js","url":"settings.js"} + ] +} diff --git a/apps/bthrm/recorder.js b/apps/bthrm/recorder.js new file mode 100644 index 000000000..b1c27660d --- /dev/null +++ b/apps/bthrm/recorder.js @@ -0,0 +1,27 @@ +(function(recorders) { + recorders.bthrm = function() { + var bpm = ""; + function onHRM(h) { + bpm = h.bpm; + } + return { + name : "BTHR", + fields : ["BT Heartrate"], + getValues : () => { + result = [bpm]; + bpm = ""; + return result; + }, + start : () => { + Bangle.on('BTHRM', onHRM); + Bangle.setBTHRMPower(1,"recorder"); + }, + stop : () => { + Bangle.removeListener('BTHRM', onHRM); + Bangle.setBTHRMPower(0,"recorder"); + }, + draw : (x,y) => g.setColor(Bangle.isBTHRMOn()?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y) + }; + } +}) + diff --git a/apps/bthrm/settings.js b/apps/bthrm/settings.js new file mode 100644 index 000000000..8cb00614e --- /dev/null +++ b/apps/bthrm/settings.js @@ -0,0 +1,33 @@ +(function(back) { + var FILE = "bthrm.json"; + + var settings = Object.assign({ + enabled: true, + replace: true, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + E.showMenu({ + '': { 'title': 'Bluetooth HRM' }, + '< Back': back, + 'Use BT HRM': { + value: !!settings.enabled, + format: v => settings.enabled ? "On" : "Off", + onchange: v => { + settings.enabled = v; + writeSettings(); + } + }, + 'Use HRM event': { + value: !!settings.replace, + format: v => settings.replace ? "On" : "Off", + onchange: v => { + settings.replace = v; + writeSettings(); + } + } + }); +}) diff --git a/apps/buffgym/metadata.json b/apps/buffgym/metadata.json new file mode 100644 index 000000000..2f8cc5c69 --- /dev/null +++ b/apps/buffgym/metadata.json @@ -0,0 +1,23 @@ +{ + "id": "buffgym", + "name": "BuffGym", + "version": "0.02", + "description": "BuffGym is the famous 5x5 workout program for the BangleJS", + "icon": "buffgym.png", + "type": "app", + "tags": "tool,outdoors,gym,exercise", + "supports": ["BANGLEJS"], + "readme": "README.md", + "interface": "buffgym.html", + "allow_emulator": false, + "storage": [ + {"name":"buffgym.app.js","url":"buffgym.app.js"}, + {"name":"buffgym-set.js","url":"buffgym-set.js"}, + {"name":"buffgym-exercise.js","url":"buffgym-exercise.js"}, + {"name":"buffgym-workout.js","url":"buffgym-workout.js"}, + {"name":"buffgym-workout-a.json","url":"buffgym-workout-a.json"}, + {"name":"buffgym-workout-b.json","url":"buffgym-workout-b.json"}, + {"name":"buffgym-workout-index.json","url":"buffgym-workout-index.json"}, + {"name":"buffgym.img","url":"buffgym-icon.js","evaluate":true} + ] +} diff --git a/apps/calculator/metadata.json b/apps/calculator/metadata.json new file mode 100644 index 000000000..3d1310859 --- /dev/null +++ b/apps/calculator/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "calculator", + "name": "Calculator", + "shortName": "Calculator", + "version": "0.05", + "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.", + "icon": "calculator.png", + "screenshots": [{"url":"screenshot_calculator.png"}], + "tags": "app,tool", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"calculator.app.js","url":"app.js"}, + {"name":"calculator.img","url":"calculator-icon.js","evaluate":true} + ] +} diff --git a/apps/calendar/metadata.json b/apps/calendar/metadata.json new file mode 100644 index 000000000..5531c03c3 --- /dev/null +++ b/apps/calendar/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "calendar", + "name": "Calendar", + "version": "0.06", + "description": "Simple calendar", + "icon": "calendar.png", + "screenshots": [{"url":"screenshot_calendar.png"}], + "tags": "calendar", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "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"}] +} diff --git a/apps/carcrazy/metadata.json b/apps/carcrazy/metadata.json new file mode 100644 index 000000000..3898de962 --- /dev/null +++ b/apps/carcrazy/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "carcrazy", + "name": "Car Crazy", + "shortName": "Car Crazy", + "version": "0.03", + "description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.", + "icon": "carcrash.png", + "tags": "game", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"carcrazy.app.js","url":"app.js"}, + {"name":"carcrazy.img","url":"app-icon.js","evaluate":true}, + {"name":"carcrazy.settings.js","url":"settings.js"} + ], + "data": [{"name":"CarCrazy.csv"}] +} diff --git a/apps/chargeanim/metadata.json b/apps/chargeanim/metadata.json new file mode 100644 index 000000000..05d894e00 --- /dev/null +++ b/apps/chargeanim/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "chargeanim", + "name": "Charge Animation", + "version": "0.02", + "description": "When charging, show a sideways charging animation and keep the screen on. When removed from the charger load the clock again.", + "icon": "icon.png", + "tags": "battery", + "supports": ["BANGLEJS", "BANGLEJS2"], + "allow_emulator": true, + "screenshots": [{"url":"bangle2-charge-animation-screenshot.png"},{"url":"bangle-charge-animation-screenshot.png"}], + "storage": [ + {"name":"chargeanim.app.js","url":"app.js"}, + {"name":"chargeanim.boot.js","url":"boot.js"}, + {"name":"chargeanim.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/choozi/metadata.json b/apps/choozi/metadata.json new file mode 100644 index 000000000..b75ef062a --- /dev/null +++ b/apps/choozi/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "choozi", + "name": "Choozi", + "version": "0.01", + "description": "Choose people or things at random using Bangle.js.", + "icon": "app.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}], + "storage": [ + {"name":"choozi.app.js","url":"app.js"}, + {"name":"choozi.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/chrono/metadata.json b/apps/chrono/metadata.json new file mode 100644 index 000000000..59fc1dbeb --- /dev/null +++ b/apps/chrono/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "chrono", + "name": "Chrono", + "shortName": "Chrono", + "version": "0.01", + "description": "Single click BTN1 to add 5 minutes. Single click BTN2 to add 30 seconds. Single click BTN3 to add 5 seconds. Tap to pause or play to timer. Double click BTN1 to reset. When timer finishes the watch vibrates.", + "icon": "chrono.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"chrono.app.js","url":"chrono.js"}, + {"name":"chrono.img","url":"chrono-icon.js","evaluate":true} + ] +} diff --git a/apps/chronowid/metadata.json b/apps/chronowid/metadata.json new file mode 100644 index 000000000..7cb32709f --- /dev/null +++ b/apps/chronowid/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "chronowid", + "name": "Chrono Widget", + "shortName": "Chrono Widget", + "version": "0.05", + "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"}, + {"name":"chronowid.app.js","url":"app.js"}, + {"name":"chronowid.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/circlesclock/ChangeLog b/apps/circlesclock/ChangeLog index c0aa4e2f8..4a23c944f 100644 --- a/apps/circlesclock/ChangeLog +++ b/apps/circlesclock/ChangeLog @@ -1,3 +1,15 @@ 0.01: New clock 0.02: Fix icon & add battery warn functionality 0.03: Theming support & minor fixes +0.04: Make configurable what to show in each circle + Add step distance and weather + Allow switching visibility of widgets + Make circles and text slightly bigger +0.05: Show correct percentage values in circles + Show humidity as weather circle data +0.06: Allow settings empty circles + Support to choose between humidity and wind speed for weather circle progress + Support to show time and progress until next sunrise or sunset + Load daily steps from Bangle health if available +0.07: Allow configuration of minimal heart rate confidence +0.08: Allow configuration of up to 4 circles in a row diff --git a/apps/circlesclock/README.md b/apps/circlesclock/README.md index 66d9afe08..242adcf4b 100644 --- a/apps/circlesclock/README.md +++ b/apps/circlesclock/README.md @@ -1,20 +1,25 @@ # Circles clock -A clock with circles for different data at the bottom in a probably familiar style +A clock with three or four circles for different data at the bottom in a probably familiar style -It shows besides time, date and day of week the following information: - * Steps (requires [pedometer widget](https://banglejs.com/apps/#pedometer)) - * Heart rate (when screen is on and unlocked) - * Battery (including charging and battery low) +By default the time, date and day of week is shown. -## Screenshot +It can show the following information (this can be configured): + * Steps + * Steps distance + * Heart rate (automatically updates when screen is on and unlocked) + * Battery (including charging status and battery low warning) + * Weather (requires [weather app](https://banglejs.com/apps/#weather)) + * Humidity or wind speed as circle progress + * Temperature inside circle + * Condition as icon below circle + * Time and progress until next sunrise or sunset (requires [my location app](https://banglejs.com/apps/#mylocation)) -![Screenshot](screenshot.png) - -## TODO -* Show weather information -* Configure which information to show in each circle -* Configure visibility of widgets +## Screenshots +![Screenshot dark theme](screenshot-dark.png) +![Screenshot light theme](screenshot-light.png) +![Screenshot dark theme with four circles](screenshot-dark-4.png) +![Screenshot light theme with four circles](screenshot-light-4.png) ## Creator Marco ([myxor](https://github.com/myxor)) diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js index 026b47cc6..5b7569d63 100644 --- a/apps/circlesclock/app.js +++ b/apps/circlesclock/app.js @@ -1,189 +1,576 @@ const locale = require("locale"); const heatshrink = require("heatshrink"); +const storage = require("Storage"); +const SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); const shoesIcon = heatshrink.decompress(atob("h0OwYJGgmAAgUBkgECgVJB4cSoAUDyEBkARDpADBhMAyQRBgVAkgmDhIUDAAuQAgY1DAAYA=")); +const shoesIconGreen = heatshrink.decompress(atob("h0OwYJGhIEDgVIAgUEyQKDkmACgcggVACIeQAYMSgIRCgmApIbDiQUDAAkBkAFDGoYAD")); const heartIcon = heatshrink.decompress(atob("h0OwYOLkmQhMkgACByVJgESpIFBpEEBAIFBCgIFCCgsABwcAgQOCAAMSpAwDyBNM")); const powerIcon = heatshrink.decompress(atob("h0OwYQNsAED7AEDmwEDtu2AgUbtuABwXbBIUN23AAoYOCgEDFIgODABI")); const powerIconGreen = heatshrink.decompress(atob("h0OwYQNkAEDpAEDiQEDkmSAgUJkmABwVJBIUEyVAAoYOCgEBFIgODABI")); const powerIconRed = heatshrink.decompress(atob("h0OwYQNoAEDyAEDkgEDpIFDiVJBweSAgUJkmAAoYZDgQpEBwYAJA")); -let settings; +const weatherCloudy = heatshrink.decompress(atob("iEQwYWTgP//+AAoMPAoPwAoN/AocfAgP//0AAgQAB/AFEABgdDAAMDDohMRA")); +const weatherSunny = heatshrink.decompress(atob("iEQwYLIg3AAgVgAQMMAo8Am3YAgUB23bAoUNAoIUBjYFCsOwBYoFDDpFgHYI1JI4gFGAAYA=")); +const weatherMoon = heatshrink.decompress(atob("iEQwIFCgOAh/wj/4n/8AId//wBBBIoRBCoIZBDoI")); +const weatherPartlyCloudy = heatshrink.decompress(atob("iEQwYQNv0AjgGDn4EDh///gFChwREC4MfxwIBv0//+AC4X4j4FCv/AgfwgED/wIBuAaBBwgFDgP4gf/AAXABwIEBDQQAEA==")); +const weatherRainy = heatshrink.decompress(atob("iEQwYLIg/gAgUB///wAFBh/AgfwgED/wIBuEAj4OCv0AjgaCh/4AocAnAFBFIU4EAM//gRBEAIOBhw1C/AmDAosAC4JNIAAg")); +const weatherPartlyRainy = heatshrink.decompress(atob("h0OwYJGjkAnAFCj+AAgU//4FCuEA8EAg8ch/4gEB4////AAoIIBCIMD/wgCg4bBg/8BwMD+AgBh4ZBDQf/FIIABh4IBgAA==")); +const weatherSnowy = heatshrink.decompress(atob("iEQwYROn/8AocH8AECuAFBh0Agf+CIN/4EDx/4j/x4EAgIIBwAXBAogRFDoopFGoxBGABIA=")); +const weatherFoggy = heatshrink.decompress(atob("iEQwYROn/8AgUB/EfwAFBh/AgfwgED/wIBuEABwd/4EcDQgFDgE4Fosf///8f//A/Lj/xCQIRNA=")); +const weatherStormy = heatshrink.decompress(atob("iEQwYLIg/gAgUB///wAFBh/AgfwgED/wIBuEAj4OCv0AjgaCh/4AoX8gE4AoQpBnAdBF4IRBDQMH/kOHgY7DAo4AOA==")); -function loadSettings() { - settings = require("Storage").readJSON("circlesclock.json", 1) || { - 'maxHR': 200, - 'stepGoal': 10000, - 'batteryWarn': 30 - }; - // Load step goal from pedometer widget as fallback - if (settings.stepGoal == undefined) { - const d = require('Storage').readJSON("wpedom.json", 1) || {}; - settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000; - } +const sunSetDown = heatshrink.decompress(atob("iEQwIHEgOAAocT5EGtEEkF//wLDg1ggfACoo")); +const sunSetUp = heatshrink.decompress(atob("iEQwIHEgOAAocT5EGtEEkF//wRFgfAg1gBIY")); + +let settings = storage.readJSON("circlesclock.json", 1) || { + 'minHR': 40, + 'maxHR': 200, + 'confidence': 0, + 'stepGoal': 10000, + 'stepDistanceGoal': 8000, + 'stepLength': 0.8, + 'batteryWarn': 30, + 'showWidgets': false, + 'weatherCircleData': 'humidity', + 'circleCount': 3, + 'circle1': 'hr', + 'circle2': 'steps', + 'circle3': 'battery', + 'circle4': 'weather' +}; +// Load step goal from pedometer widget as fallback +if (settings.stepGoal == undefined) { + const d = require('Storage').readJSON("wpedom.json", 1) || {}; + settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000; } +/* + * Read location from myLocation app + */ +function getLocation() { + return storage.readJSON("mylocation.json", 1) || undefined; +} +let location = getLocation(); + +const showWidgets = settings.showWidgets || false; +const circleCount = settings.circleCount || 3; + +let hrtValue; +let now = Math.round(new Date().getTime() / 1000); + + +// layout values: const colorFg = g.theme.dark ? '#fff' : '#000'; const colorBg = g.theme.dark ? '#000' : '#fff'; const colorGrey = '#808080'; const colorRed = '#ff0000'; -const colorGreen = '#00ff00'; - -let hrtValue; - -const h = g.getHeight(); +const colorGreen = '#008000'; +const colorBlue = '#0000ff'; +const colorYellow = '#ffff00'; +const widgetOffset = showWidgets ? 24 : 0; +const h = g.getHeight() - widgetOffset; const w = g.getWidth(); -const hOffset = 30; +const hOffset = 30 - widgetOffset; const h1 = Math.round(1 * h / 5 - hOffset); const h2 = Math.round(3 * h / 5 - hOffset); -const h3 = Math.round(8 * h / 8 - hOffset); -const w1 = Math.round(w / 6); -const w2 = Math.round(3 * w / 6); -const w3 = Math.round(5 * w / 6); -const radiusOuter = 22; -const radiusInner = 16; +const h3 = Math.round(8 * h / 8 - hOffset - 3); // circle y position + +/* + * circle x positions + * depending on circleCount + * + * | 1 2 3 4 5 6 | + * | (1) (2) (3) | + * => circles start at 1,3,5 / 6 + * + * | 1 2 3 4 5 6 7 8 | + * | (1) (2) (3) (4) | + * => circles start at 1,3,5,7 / 8 + */ +const parts = circleCount * 2; +const circlePosX = [ + Math.round(1 * w / parts), // circle1 + Math.round(3 * w / parts), // circle2 + Math.round(5 * w / parts), // circle3 + Math.round(7 * w / parts), // circle4 +]; + +const radiusOuter = circleCount == 3 ? 25 : 20; +const radiusInner = circleCount == 3 ? 20 : 15; +const circleFont = circleCount == 3 ? "Vector:15" : "Vector:12"; +const circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:13"; +const defaultCircleTypes = ["steps", "hr", "battery", "weather"]; + function draw() { - g.reset(); + g.clear(true); + + if (!showWidgets) { + /* + * 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 and change the + * area to the top bar doesn't get cleared. + */ + if (WIDGETS && typeof WIDGETS === "object") { + for (let wd of WIDGETS) { + wd.draw = () => {}; + wd.area = ""; + } + } + } else { + Bangle.drawWidgets(); + } + g.setColor(colorBg); - g.fillRect(0, 0, w, h); + g.fillRect(0, widgetOffset, w, h); // time g.setFont("Vector:50"); - g.setFontAlign(-1, -1); + g.setFontAlign(0, -1); g.setColor(colorFg); - g.drawString(locale.time(new Date(), 1), w / 10, h1 + 8); + g.drawString(locale.time(new Date(), 1), w / 2, h1 + 8); + now = Math.round(new Date().getTime() / 1000); // date & dow - g.setFont("Vector:20"); + g.setFont("Vector:21"); g.setFontAlign(-1, 0); - g.drawString(locale.date(new Date()), w / 10, h2); - g.drawString(locale.dow(new Date()), w / 10, h2 + 22); + g.drawString(locale.date(new Date()), w > 180 ? 2 * w / 10 : w / 10, h2); + g.drawString(locale.dow(new Date()), w > 180 ? 2 * w / 10 : w / 10, h2 + 22); - // Steps circle - drawSteps(); + drawCircle(1); + drawCircle(2); + drawCircle(3); + if (circleCount >= 4) drawCircle(4); +} - // Heart circle - drawHeartRate(); +function drawCircle(index) { + let type = settings['circle' + index]; + if (!type) type = defaultCircleTypes[index - 1]; + const w = getCirclePosition(type); + switch (type) { + case "steps": + drawSteps(w); + break; + case "stepsDist": + drawStepsDistance(w); + break; + case "hr": + drawHeartRate(w); + break; + case "battery": + drawBattery(w); + break; + case "weather": + drawWeather(w); + break; + case "sunprogress": + case "sunProgress": + drawSunProgress(w); + break; + case "empty": + // we draw nothing here + return; + } +} - // Battery circle - drawBattery(); +// serves as cache for quicker lookup of circle positions +let circlePositionsCache = []; +/* + * Looks in the following order if a circle with the given type is somewhere visible/configured + * 1. circlePositionsCache + * 2. settings + * 3. defaultCircleTypes + * + * In case 2 and 3 the circlePositionsCache will be updated + */ +function getCirclePosition(type) { + if (circlePositionsCache[type] >= 0) { + return circlePosX[circlePositionsCache[type]]; + } + for (let i = 1; i <= circleCount; i++) { + const setting = settings['circle' + i]; + if (setting == type) { + circlePositionsCache[type] = i - 1; + return circlePosX[i - 1]; + } + } + for (let i = 0; i < defaultCircleTypes.length; i++) { + if (type == defaultCircleTypes[i] && (!settings || settings['circle' + (i + 1)] == undefined)) { + circlePositionsCache[type] = i; + return circlePosX[i]; + } + } + return undefined; +} + +function isCircleEnabled(type) { + return getCirclePosition(type) != undefined; } - -function drawSteps() { +function drawSteps(w) { + if (!w) w = getCirclePosition("steps"); const steps = getSteps(); - const blue = '#0000ff'; - g.setColor(colorGrey); - g.fillCircle(w1, h3, radiusOuter); + + drawCircleBackground(w); const stepGoal = settings.stepGoal || 10000; if (stepGoal > 0) { let percent = steps / stepGoal; if (stepGoal < steps) percent = 1; - drawGauge(w1, h3, percent, blue); + drawGauge(w, h3, percent, colorBlue); } - g.setColor(colorBg); - g.fillCircle(w1, h3, radiusInner); + drawInnerCircleAndTriangle(w); - g.fillPoly([w1, h3, w1 - 15, h3 + radiusOuter + 5, w1 + 15, h3 + radiusOuter + 5]); + writeCircleText(w, shortValue(steps)); - g.setFont("Vector:12"); - g.setFontAlign(0, 0); - g.setColor(colorFg); - g.drawString(shortValue(steps), w1 + 2, h3); - - g.drawImage(shoesIcon, w1 - 6, h3 + radiusOuter - 6); + g.drawImage(shoesIcon, w - 6, h3 + radiusOuter - 6); } -function drawHeartRate() { - g.setColor(colorGrey); - g.fillCircle(w2, h3, radiusOuter); +function drawStepsDistance(w) { + if (!w) w = getCirclePosition("steps"); + const steps = getSteps(); + const stepDistance = settings.stepLength || 0.8; + const stepsDistance = Math.round(steps * stepDistance); - if (hrtValue != undefined && hrtValue > 0) { - const minHR = 40; - const percent = (hrtValue - minHR) / (settings.maxHR - minHR); - drawGauge(w2, h3, percent, colorRed); + drawCircleBackground(w); + + const stepDistanceGoal = settings.stepDistanceGoal || 8000; + if (stepDistanceGoal > 0) { + let percent = stepsDistance / stepDistanceGoal; + if (stepDistanceGoal < stepsDistance) percent = 1; + drawGauge(w, h3, percent, colorGreen); } - g.setColor(colorBg); - g.fillCircle(w2, h3, radiusInner); + drawInnerCircleAndTriangle(w); - g.fillPoly([w2, h3, w2 - 15, h3 + radiusOuter + 5, w2 + 15, h3 + radiusOuter + 5]); + writeCircleText(w, shortValue(stepsDistance)); - g.setFont("Vector:12"); - g.setFontAlign(0, 0); - g.setColor(colorFg); - g.drawString(hrtValue != undefined ? hrtValue : "-", w2, h3); - - g.drawImage(heartIcon, w2 - 6, h3 + radiusOuter - 6); + g.drawImage(shoesIconGreen, w - 6, h3 + radiusOuter - 6); } -function drawBattery() { +function drawHeartRate(w) { + if (!w) w = getCirclePosition("hr"); + + drawCircleBackground(w); + + if (hrtValue != undefined) { + const minHR = settings.minHR || 40; + const maxHR = settings.maxHR || 200; + const percent = (hrtValue - minHR) / (maxHR - minHR); + drawGauge(w, h3, percent, colorRed); + } + + drawInnerCircleAndTriangle(w); + + writeCircleText(w, hrtValue != undefined ? hrtValue : "-"); + + g.drawImage(heartIcon, w - 6, h3 + radiusOuter - 6); +} + +function drawBattery(w) { + if (!w) w = getCirclePosition("battery"); const battery = E.getBattery(); - const yellow = '#ffff00'; - g.setColor(colorGrey); - g.fillCircle(w3, h3, radiusOuter); + + drawCircleBackground(w); if (battery > 0) { const percent = battery / 100; - drawGauge(w3, h3, percent, yellow); + drawGauge(w, h3, percent, colorYellow); } - g.setColor(colorBg); - g.fillCircle(w3, h3, radiusInner); - - g.fillPoly([w3, h3, w3 - 15, h3 + radiusOuter + 5, w3 + 15, h3 + radiusOuter + 5]); - - g.setFont("Vector:12"); - g.setFontAlign(0, 0); + drawInnerCircleAndTriangle(w); let icon = powerIcon; let color = colorFg; if (Bangle.isCharging()) { color = colorGreen; icon = powerIconGreen; - } - else { + } else { if (settings.batteryWarn != undefined && battery <= settings.batteryWarn) { color = colorRed; icon = powerIconRed; } } - g.setColor(color); - g.drawString(battery + '%', w3, h3); + writeCircleText(w, battery + '%'); - g.drawImage(icon, w3 - 6, h3 + radiusOuter - 6); + g.drawImage(icon, w - 6, h3 + radiusOuter - 6); +} + +function drawWeather(w) { + if (!w) w = getCirclePosition("weather"); + const weather = getWeather(); + const tempString = weather ? locale.temp(weather.temp - 273.15) : undefined; + const code = weather ? weather.code : -1; + + drawCircleBackground(w); + + const data = settings.weatherCircleData || "humidity"; + switch (data) { + case "humidity": + const humidity = weather ? weather.hum : undefined; + if (humidity >= 0) { + drawGauge(w, h3, humidity / 100, colorYellow); + } + break; + case "wind": + if (weather) { + const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/); + if (wind[1] >= 0) { + if (wind[2] == "kmh") { + wind[1] = windAsBeaufort(wind[1]); + } + // wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale) + drawGauge(w, h3, wind[1] / 12, colorYellow); + } + } + break; + case "empty": + break; + } + + drawInnerCircleAndTriangle(w); + + writeCircleText(w, tempString ? tempString : "?"); + + if (code > 0) { + const icon = getWeatherIconByCode(code); + if (icon) g.drawImage(icon, w - 6, h3 + radiusOuter - 10); + } else { + g.drawString("?", w, h3 + radiusOuter); + } +} + + +function drawSunProgress(w) { + if (!w) w = getCirclePosition("sunprogress"); + const percent = getSunProgress(); + + drawCircleBackground(w); + + drawGauge(w, h3, percent, colorYellow); + + drawInnerCircleAndTriangle(w); + + let icon = powerIcon; + let color = colorFg; + if (isDay()) { + // day + color = colorFg; + icon = sunSetDown; + } else { + // night + color = colorGrey; + icon = sunSetUp; + } + g.setColor(color); + + let text = "?"; + const times = getSunData(); + if (times != undefined) { + const sunRise = Math.round(times.sunrise.getTime() / 1000); + const sunSet = Math.round(times.sunset.getTime() / 1000); + if (!isDay()) { + // night + if (now > sunRise) { + // after sunRise + const upcomingSunRise = sunRise + 60 * 60 * 24; + text = formatSeconds(upcomingSunRise - now); + } else { + text = formatSeconds(sunRise - now); + } + } else { + // day, approx sunrise tomorrow: + text = formatSeconds(sunSet - now); + } + } + + writeCircleText(w, text); + + g.drawImage(icon, w - 6, h3 + radiusOuter - 6); + +} + +/* + * wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale) + */ +function windAsBeaufort(windInKmh) { + const beaufort = [2, 6, 12, 20, 29, 39, 50, 62, 75, 89, 103, 118]; + let l = 0; + while (l < beaufort.length && beaufort[l] < windInKmh) { + l++; + } + return l; +} + + +/* + * Choose weather icon to display based on weather conditition code + * https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2 + */ +function getWeatherIconByCode(code) { + const codeGroup = Math.round(code / 100); + switch (codeGroup) { + case 2: + return weatherStormy; + case 3: + return weatherCloudy; + case 5: + switch (code) { + case 511: + return weatherSnowy; + case 520: + return weatherPartlyRainy; + case 521: + return weatherPartlyRainy; + case 522: + return weatherPartlyRainy; + case 531: + return weatherPartlyRainy; + default: + return weatherRainy; + } + break; + case 6: + return weatherSnowy; + case 7: + return weatherFoggy; + case 8: + switch (code) { + case 800: + return isDay() ? weatherSunny : weatherMoon; + case 801: + return weatherPartlyCloudy; + case 802: + return weatherPartlyCloudy; + default: + return weatherCloudy; + } + break; + default: + return undefined; + } + return undefined; +} + + +function isDay() { + const times = getSunData(); + if (times == undefined) return true; + const sunRise = Math.round(times.sunrise.getTime() / 1000); + const sunSet = Math.round(times.sunset.getTime() / 1000); + + return (now > sunRise && now < sunSet); +} + +function formatSeconds(s) { + if (s > 60 * 60) { // hours + return Math.round(s / (60 * 60)) + "h"; + } + if (s > 60) { // minutes + return Math.round(s / 60) + "m"; + } + return "<1m"; +} + +function getSunData() { + if (location != undefined && location.lat != undefined) { + // get today's sunlight times for lat/lon + return SunCalc.getTimes(new Date(), location.lat, location.lon); + } + return undefined; +} + +/* + * Calculated progress of the sun between sunrise and sunset in percent + * + * Taken from rebble app and modified + */ +function getSunProgress() { + const times = getSunData(); + if (times == undefined) return 0; + const sunRise = Math.round(times.sunrise.getTime() / 1000); + const sunSet = Math.round(times.sunset.getTime() / 1000); + + if (isDay()) { + // during day + const dayLength = sunSet - sunRise; + if (now > sunRise) { + return (now - sunRise) / dayLength; + } else { + return (sunRise - now) / dayLength; + } + } else { + // during night + if (sunSet < sunRise) { + const upcomingSunRise = sunRise + 60 * 60 * 24; + return 1 - (upcomingSunRise - now) / (upcomingSunRise - sunSet); + } else { + const lastSunSet = sunSet - 60 * 60 * 24; + return (now - lastSunSet) / (sunRise - lastSunSet); + } + } +} + +/* + * Draws the background and the grey circle + */ +function drawCircleBackground(w) { + // Draw rectangle background: + g.setColor(colorBg); + g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3); + // Draw grey background circle: + g.setColor(colorGrey); + g.fillCircle(w, h3, radiusOuter); +} + +function drawInnerCircleAndTriangle(w) { + // Draw inner circle + g.setColor(colorBg); + g.fillCircle(w, h3, radiusInner); + // Draw triangle which covers the bottom of the circle + g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]); } function radians(a) { return a * Math.PI / 180; } +/* + * This draws the actual gauge consisting out of lots of little filled circles + */ function drawGauge(cx, cy, percent, color) { - let offset = 30; - let end = 300; - var i = 0; - var r = radiusInner + 3; + const offset = 15; + const end = 345; + const radius = radiusInner + 3; + const size = radiusOuter - radiusInner - 2; if (percent <= 0) return; if (percent > 1) percent = 1; - var startrot = -offset; - var endrot = startrot - ((end - offset) * percent) - 15; + const startRotation = -offset; + const endRotation = startRotation - ((end - offset) * percent); g.setColor(color); - const size = 4; - // draw gauge - for (i = startrot; i > endrot - size; i -= size) { - x = cx + r * Math.sin(radians(i)); - y = cy + r * Math.cos(radians(i)); + for (let i = startRotation; i > endRotation - size; i -= size) { + x = cx + radius * Math.sin(radians(i)); + y = cy + radius * Math.cos(radians(i)); g.fillCircle(x, y, size); } } +function writeCircleText(w, content) { + if (content == undefined) return; + g.setFont(content.length < 4 ? circleFontBig : circleFont); + + g.setFontAlign(0, 0); + g.setColor(colorFg); + g.drawString(content, w, h3); +} + function shortValue(v) { if (isNaN(v)) return '-'; if (v <= 999) return v; @@ -198,54 +585,61 @@ function shortValue(v) { } function getSteps() { - if (WIDGETS.wpedom !== undefined) { + if (Bangle.getHealthStatus) { + return Bangle.getHealthStatus("day").steps; + } + if (WIDGETS && WIDGETS.wpedom !== undefined) { return WIDGETS.wpedom.getSteps(); } return 0; } -Bangle.on('lock', function(isLocked) { - if (!isLocked) { - Bangle.setHRMPower(1, "watch"); - if (hrtValue == undefined) { - hrtValue = '...'; - drawHeartRate(); - } - } else { - Bangle.setHRMPower(0, "watch"); - } - drawHeartRate(); - drawSteps(); -}); +function getWeather() { + const jsonWeather = storage.readJSON('weather.json'); + return jsonWeather && jsonWeather.weather ? jsonWeather.weather : undefined; +} -Bangle.on('HRM', function(hrm) { - //if(hrm.confidence > 90){ - hrtValue = hrm.bpm; - if (Bangle.isLCDOn()) +function enableHRMSensor() { + Bangle.setHRMPower(1, "circleclock"); + if (hrtValue == undefined) { + hrtValue = '...'; drawHeartRate(); - //} else { - // hrtValue = undefined; - //} -}); - -Bangle.on('charging', function(charging) { - drawBattery(); -}); - -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 and change the - * area to the top bar doesn't get cleared. - */ -if (typeof WIDGETS === "object") { - for (let wd of WIDGETS) { - wd.draw = () => {}; - wd.area = ""; } } -loadSettings(); -setInterval(draw, 60000); -draw(); + +Bangle.on('lock', function(isLocked) { + if (!isLocked) { + if (isCircleEnabled("hr")) { + enableHRMSensor(); + } + draw(); + } else { + Bangle.setHRMPower(0, "circleclock"); + } +}); + + +Bangle.on('HRM', function(hrm) { + if (isCircleEnabled("hr")) { + if (hrm.confidence >= (settings.confidence || 0)) { + hrtValue = hrm.bpm; + if (Bangle.isLCDOn()) + drawHeartRate(); + } + } +}); + + Bangle.setUI("clock"); +Bangle.loadWidgets(); + +draw(); +setInterval(draw, 60000); + +Bangle.on('charging', function(charging) { + if (isCircleEnabled("battery")) drawBattery(); +}); + +if (isCircleEnabled("hr")) { + enableHRMSensor(); +} diff --git a/apps/circlesclock/metadata.json b/apps/circlesclock/metadata.json new file mode 100644 index 000000000..bd2ce751b --- /dev/null +++ b/apps/circlesclock/metadata.json @@ -0,0 +1,21 @@ +{ "id": "circlesclock", + "name": "Circles clock", + "shortName":"Circles clock", + "version":"0.08", + "description": "A clock with three or four circles for different data at the bottom in a probably familiar style", + "icon": "app.png", + "screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}], + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "allow_emulator":true, + "readme": "README.md", + "storage": [ + {"name":"circlesclock.app.js","url":"app.js"}, + {"name":"circlesclock.img","url":"app-icon.js","evaluate":true}, + {"name":"circlesclock.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"circlesclock.json"} + ] +} diff --git a/apps/circlesclock/screenshot-dark-4.png b/apps/circlesclock/screenshot-dark-4.png new file mode 100644 index 000000000..4f1a1b457 Binary files /dev/null and b/apps/circlesclock/screenshot-dark-4.png differ diff --git a/apps/circlesclock/screenshot-dark.png b/apps/circlesclock/screenshot-dark.png new file mode 100644 index 000000000..4bf8cae9f Binary files /dev/null and b/apps/circlesclock/screenshot-dark.png differ diff --git a/apps/circlesclock/screenshot-light-4.png b/apps/circlesclock/screenshot-light-4.png new file mode 100644 index 000000000..74d9e2740 Binary files /dev/null and b/apps/circlesclock/screenshot-light-4.png differ diff --git a/apps/circlesclock/screenshot-light.png b/apps/circlesclock/screenshot-light.png new file mode 100644 index 000000000..1a746d5bf Binary files /dev/null and b/apps/circlesclock/screenshot-light.png differ diff --git a/apps/circlesclock/screenshot.png b/apps/circlesclock/screenshot.png deleted file mode 100644 index 94ff885fa..000000000 Binary files a/apps/circlesclock/screenshot.png and /dev/null differ diff --git a/apps/circlesclock/settings.js b/apps/circlesclock/settings.js index ffda51538..1c072fc90 100644 --- a/apps/circlesclock/settings.js +++ b/apps/circlesclock/settings.js @@ -6,18 +6,45 @@ settings[key] = value; storage.write(SETTINGS_FILE, settings); } + + const valuesCircleTypes = ["steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "empty"]; + const namesCircleTypes = ["steps", "distance", "heart", "battery", "weather", "sun progress", "empty"]; + + const weatherData = ["humidity", "wind", "empty"]; + E.showMenu({ '': { 'title': 'circlesclock' }, + '< Back': back, + 'min heartrate': { + value: "minHR" in settings ? settings.minHR : 40, + min: 0, + max : 250, + step: 5, + format: x => { + return x; + }, + onchange: x => save('minHR', x), + }, 'max heartrate': { value: "maxHR" in settings ? settings.maxHR : 200, min: 20, max : 250, - step: 10, + step: 5, format: x => { return x; }, onchange: x => save('maxHR', x), }, + 'hr confidence': { + value: "confidence" in settings ? settings.confidence : 0, + min: 0, + max : 100, + step: 10, + format: x => { + return x; + }, + onchange: x => save('confidence', x), + }, 'step goal': { value: "stepGoal" in settings ? settings.stepGoal : 10000, min: 2000, @@ -28,7 +55,27 @@ }, onchange: x => save('stepGoal', x), }, - 'battery warn lvl': { + 'step length': { + value: "stepLength" in settings ? settings.stepLength : 0.8, + min: 0.1, + max : 1.5, + step: 0.01, + format: x => { + return x; + }, + onchange: x => save('stepLength', x), + }, + 'step dist goal': { + value: "stepDistanceGoal" in settings ? settings.stepDistanceGoal : 8000, + min: 2000, + max : 30000, + step: 1000, + format: x => { + return x; + }, + onchange: x => save('stepDistanceGoal', x), + }, + 'battery warn': { value: "batteryWarn" in settings ? settings.batteryWarn : 30, min: 10, max : 100, @@ -38,6 +85,47 @@ }, onchange: x => save('batteryWarn', x), }, - '< Back': back, + 'show widgets': { + value: "showWidgets" in settings ? settings.showWidgets : false, + format: () => (settings.showWidgets ? 'Yes' : 'No'), + onchange: x => save('showWidgets', x), + }, + 'weather circle': { + value: settings.weatherCircleData ? weatherData.indexOf(settings.weatherCircleData) : 0, + min: 0, max: 2, + format: v => weatherData[v], + onchange: x => save('weatherCircleData', weatherData[x]), + }, + 'circle count': { + value: "circleCount" in settings ? settings.circleCount : 3, + min: 3, + max : 4, + step: 1, + onchange: x => save('circleCount', x), + }, + 'circle1': { + value: settings.circle1 ? valuesCircleTypes.indexOf(settings.circle1) : 0, + min: 0, max: 6, + format: v => namesCircleTypes[v], + onchange: x => save('circle1', valuesCircleTypes[x]), + }, + 'circle2': { + value: settings.circle2 ? valuesCircleTypes.indexOf(settings.circle2) : 2, + min: 0, max: 6, + format: v => namesCircleTypes[v], + onchange: x => save('circle2', valuesCircleTypes[x]), + }, + 'circle3': { + value: settings.circle3 ? valuesCircleTypes.indexOf(settings.circle3) : 3, + min: 0, max: 6, + format: v => namesCircleTypes[v], + onchange: x => save('circle3', valuesCircleTypes[x]), + }, + 'circle4': { + value: settings.circle4 ? valuesCircleTypes.indexOf(settings.circle4) : 4, + min: 0, max: 6, + format: v => namesCircleTypes[v], + onchange: x => save('circle4', valuesCircleTypes[x]), + } }); }); diff --git a/apps/clickms/metadata.json b/apps/clickms/metadata.json new file mode 100644 index 000000000..baa8c9563 --- /dev/null +++ b/apps/clickms/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "clickms", + "name": "Click Master", + "version": "0.01", + "description": "Get several friends to start the game, then compete to see who can press BTN1 the most!", + "icon": "click-master.png", + "tags": "game", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"clickms.app.js","url":"click-master.js"}, + {"name":"clickms.img","url":"click-master-icon.js","evaluate":true} + ] +} diff --git a/apps/cliclockJS2Enhanced/metadata.json b/apps/cliclockJS2Enhanced/metadata.json new file mode 100644 index 000000000..f428650a7 --- /dev/null +++ b/apps/cliclockJS2Enhanced/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "cliclockJS2Enhanced", + "name": "Commandline-Clock JS2 Enhanced", + "shortName": "CLI-Clock JS2", + "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"}], + "type": "clock", + "tags": "clock,cli,command,bash,shell", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"cliclockJS2Enhanced.app.js","url":"app.js"}, + {"name":"cliclockJS2Enhanced.img","url":"app.icon.js","evaluate":true} + ] +} diff --git a/apps/clicompleteclk/metadata.json b/apps/clicompleteclk/metadata.json new file mode 100644 index 000000000..8753c3c37 --- /dev/null +++ b/apps/clicompleteclk/metadata.json @@ -0,0 +1,19 @@ +{ + "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.app.js","url":"app.js"}, + {"name":"clicompleteclk.img","url":"app-icon.js","evaluate":true}, + {"name":"clicompleteclk.settings.js","url":"settings.js"} + ], + "data": [{"name":"clicompleteclk.json"}] +} diff --git a/apps/cliock/metadata.json b/apps/cliock/metadata.json new file mode 100644 index 000000000..c5d3fa49e --- /dev/null +++ b/apps/cliock/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "cliock", + "name": "Commandline-Clock", + "shortName": "CLI-Clock", + "version": "0.15", + "description": "Simple CLI-Styled Clock", + "icon": "app.png", + "screenshots": [{"url":"screenshot_cli.png"}], + "type": "clock", + "tags": "clock,cli,command,bash,shell", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"cliock.app.js","url":"app.js"}, + {"name":"cliock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/clock2x3/metadata.json b/apps/clock2x3/metadata.json new file mode 100644 index 000000000..dee2019e6 --- /dev/null +++ b/apps/clock2x3/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "clock2x3", + "name": "2x3 Pixel Clock", + "version": "0.05", + "description": "This is a simple clock using minimalist 2x3 pixel numerical digits", + "icon": "clock2x3.png", + "screenshots": [{"url":"screenshot_pixel.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"clock2x3.app.js","url":"clock2x3-app.js"}, + {"name":"clock2x3.img","url":"clock2x3-icon.js","evaluate":true} + ] +} diff --git a/apps/clotris/metadata.json b/apps/clotris/metadata.json new file mode 100644 index 000000000..33b3212de --- /dev/null +++ b/apps/clotris/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "clotris", + "name": "Clock-Tris", + "version": "0.01", + "description": "A fully functional clone of a classic game of falling blocks", + "icon": "clock-tris.png", + "tags": "game", + "supports": ["BANGLEJS"], + "screenshots": [{"url":"bangle1-clock-tris-screenshot.png"}], + "allow_emulator": true, + "storage": [ + {"name":"clotris.app.js","url":"clock-tris.js"}, + {"name":"clotris.img","url":"clock-tris-icon.js","evaluate":true}, + {"name":".trishig","url":"clock-tris-high"} + ] +} diff --git a/apps/color_catalog/metadata.json b/apps/color_catalog/metadata.json new file mode 100644 index 000000000..3146a146f --- /dev/null +++ b/apps/color_catalog/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "color_catalog", + "name": "Colors Catalog", + "shortName": "Colors Catalog", + "version": "0.01", + "description": "Displays RGB565 and RGB888 colors, its name and code in screen.", + "icon": "app.png", + "tags": "Color,input,buttons,touch,UI", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"color_catalog.app.js","url":"app.js"}, + {"name":"color_catalog.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/colorful_clock/LICENSE b/apps/colorful_clock/LICENSE new file mode 100644 index 000000000..7487dd5da --- /dev/null +++ b/apps/colorful_clock/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Andreas Rozek + +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. diff --git a/apps/colorful_clock/metadata.json b/apps/colorful_clock/metadata.json new file mode 100644 index 000000000..5b6dbe87e --- /dev/null +++ b/apps/colorful_clock/metadata.json @@ -0,0 +1,17 @@ +{ "id": "colorful_clock", + "name": "Colorful Analog Clock", + "shortName":"Colorful Clock", + "version":"0.03", + "description": "a colorful analog clock", + "icon": "app-icon.png", + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "allow_emulator": true, + "screenshots": [{"url":"app-screenshot.png"}], + "readme": "README.md", + "storage": [ + {"name":"colorful_clock.app.js","url":"app.js"}, + {"name":"colorful_clock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/colorwheel/metadata.json b/apps/colorwheel/metadata.json new file mode 100644 index 000000000..cf20bef46 --- /dev/null +++ b/apps/colorwheel/metadata.json @@ -0,0 +1,15 @@ +{ + "id":"colorwheel", + "name":"Color Wheel", + "tags":"app,tool", + "version":"0.01", + "description":"a tappable wheel of good-looking colors", + "readme":"README.md", + "supports":["BANGLEJS2"], + "allow_emulator":true, + "icon":"colorwheel.png", + "storage": [ + {"name":"colorwheel.app.js","url":"app.js"}, + {"name":"colorwheel.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/compass/metadata.json b/apps/compass/metadata.json new file mode 100644 index 000000000..318d90c86 --- /dev/null +++ b/apps/compass/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "compass", + "name": "Compass", + "version": "0.05", + "description": "Simple compass that points North", + "icon": "compass.png", + "screenshots": [{"url":"screenshot_compass.png"}], + "tags": "tool,outdoors", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"compass.app.js","url":"compass.js"}, + {"name":"compass.img","url":"compass-icon.js","evaluate":true} + ] +} diff --git a/apps/configurable_clock/BangleApps__apps__variable_clock__README.md b/apps/configurable_clock/BangleApps__apps__variable_clock__README.md new file mode 100644 index 000000000..da5bed56d --- /dev/null +++ b/apps/configurable_clock/BangleApps__apps__variable_clock__README.md @@ -0,0 +1,27 @@ +# Variable Analog Clock # + +This app implements an analog clock with various faces, hands and colors to +choose from. + +You have the choice between: + +* 4 different clock faces ![](Screenshot_01.png) ![](Screenshot_02.png) ![](Screenshot_03.png) ![](Screenshot_04.png) and +* 3 different clock hands (optionally with or without second hands) ![](Screenshot_11.png) ![](Screenshot_12.png) ![](Screenshot_13.png) + +Additionally, you may use the currently configured global theme or configure +your own colors for clock fore- and background and second hands. + +Just swipe up or down to switch from clock display to configuration screen + +![](Screenshot_21.png) ![](Screenshot_22.png) ![](Screenshot_23.png) +![](Screenshot_24.png) ![](Screenshot_25.png) + +Chosen settings will be written to the Bangle.js's flash memory and restored +whenever the clock is started again. + +This clock also acts as an example for the building blocks found in the author's +[GitHub repository](https://github.com/rozek/banglejs-2-activities) + +## License ## + +[MIT License](LICENSE) diff --git a/apps/configurable_clock/LICENSE b/apps/configurable_clock/LICENSE new file mode 100644 index 000000000..7487dd5da --- /dev/null +++ b/apps/configurable_clock/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Andreas Rozek + +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. diff --git a/apps/configurable_clock/README.md b/apps/configurable_clock/README.md new file mode 100644 index 000000000..faddd092a --- /dev/null +++ b/apps/configurable_clock/README.md @@ -0,0 +1,29 @@ +# Configurable Analog Clock # + +This app implements an analog clock with various faces, hands and colors to +choose from. + +You have the choice between: + +* 4 different clock faces
![](Screenshot-01.png)   ![](Screenshot-02.png)   ![](Screenshot-03.png)   ![](Screenshot-04.png) and +* 3 different clock hands (optionally with or without second hands)
![](Screenshot-11.png)   ![](Screenshot-12.png)   ![](Screenshot-13.png) + +Additionally, you may use the currently configured global theme or configure +your own colors for clock fore- and background and second hands. + +Just swipe up or down to switch from clock display to the first configuration +screen and continue from there + +![](Screenshot-21.png)   ![](Screenshot-22.png)   +![](Screenshot-23.png)   ![](Screenshot-24.png)   +![](Screenshot-25.png) + +Chosen settings will be written to the Bangle.js's flash memory and restored +whenever the clock is started again. + +This clock also acts as an example for the building blocks found in the author's +[GitHub repository](https://github.com/rozek/banglejs-2-activities) + +## License ## + +[MIT License](LICENSE) diff --git a/apps/configurable_clock/Screenshot-01.png b/apps/configurable_clock/Screenshot-01.png new file mode 100644 index 000000000..b2367784c Binary files /dev/null and b/apps/configurable_clock/Screenshot-01.png differ diff --git a/apps/configurable_clock/Screenshot-02.png b/apps/configurable_clock/Screenshot-02.png new file mode 100644 index 000000000..909a2a04a Binary files /dev/null and b/apps/configurable_clock/Screenshot-02.png differ diff --git a/apps/configurable_clock/Screenshot-03.png b/apps/configurable_clock/Screenshot-03.png new file mode 100644 index 000000000..80407c84f Binary files /dev/null and b/apps/configurable_clock/Screenshot-03.png differ diff --git a/apps/configurable_clock/Screenshot-04.png b/apps/configurable_clock/Screenshot-04.png new file mode 100644 index 000000000..175476c81 Binary files /dev/null and b/apps/configurable_clock/Screenshot-04.png differ diff --git a/apps/configurable_clock/Screenshot-11.png b/apps/configurable_clock/Screenshot-11.png new file mode 100644 index 000000000..bca534613 Binary files /dev/null and b/apps/configurable_clock/Screenshot-11.png differ diff --git a/apps/configurable_clock/Screenshot-12.png b/apps/configurable_clock/Screenshot-12.png new file mode 100644 index 000000000..973b6da5e Binary files /dev/null and b/apps/configurable_clock/Screenshot-12.png differ diff --git a/apps/configurable_clock/Screenshot-13.png b/apps/configurable_clock/Screenshot-13.png new file mode 100644 index 000000000..b87d97712 Binary files /dev/null and b/apps/configurable_clock/Screenshot-13.png differ diff --git a/apps/configurable_clock/Screenshot-21.png b/apps/configurable_clock/Screenshot-21.png new file mode 100644 index 000000000..46d799e6d Binary files /dev/null and b/apps/configurable_clock/Screenshot-21.png differ diff --git a/apps/configurable_clock/Screenshot-22.png b/apps/configurable_clock/Screenshot-22.png new file mode 100644 index 000000000..7ee02568e Binary files /dev/null and b/apps/configurable_clock/Screenshot-22.png differ diff --git a/apps/configurable_clock/Screenshot-23.png b/apps/configurable_clock/Screenshot-23.png new file mode 100644 index 000000000..f3248993b Binary files /dev/null and b/apps/configurable_clock/Screenshot-23.png differ diff --git a/apps/configurable_clock/Screenshot-24.png b/apps/configurable_clock/Screenshot-24.png new file mode 100644 index 000000000..8a7753bfc Binary files /dev/null and b/apps/configurable_clock/Screenshot-24.png differ diff --git a/apps/configurable_clock/Screenshot-25.png b/apps/configurable_clock/Screenshot-25.png new file mode 100644 index 000000000..c2950d7b2 Binary files /dev/null and b/apps/configurable_clock/Screenshot-25.png differ diff --git a/apps/configurable_clock/app-icon.js b/apps/configurable_clock/app-icon.js new file mode 100644 index 000000000..b0cf74241 --- /dev/null +++ b/apps/configurable_clock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgZC/AB1RgkQsAQMyUKAYMIkAPJgNFiEBgACBg0YCRMogEJkGSAwMSEZNAAQMAEAMGgBKHgXAlECwMgzcAmkAhgRGilRssUgMEEYcBwARFiBHBgQKB7AjCawIQEgoCCigDBjEBwwEBEwIAGlmSEYYABI4PAEYhEBNYIjCAYVtwCSElG2xdoAwQjDhpZEEAMUqAHDCIaPBEYlAiwjItkAgYjFqJHDCIdhI4j1CAAhlEZoTUEAAcGEYZKEEYWgCIgjEWYkBoqwCCITLBgcMmPXhgjCgUB2iFDm3pw0YLAMygEgc4QjF49cmA3BbQQjDgGkI5OwNZZ9FEYoRLEYxmBCI5jBEYQACyQRHgmAEYsEEZEka4kAhEEEY8BCIMJCIYjKgGChAFDCwKzDNYyKEJgUDlgRBAoPDRQQjEZQZzEjScIhgjBEwQjEH4aXEgIjBjYCBjQCBMYYADmAjDFIjcGKocAjBKCgJRCAAwaCEARQBmARIhBrEgSMEAApEBmHAAQJrCABUCjFhwwQMI4oA7")) \ No newline at end of file diff --git a/apps/configurable_clock/app-icon.png b/apps/configurable_clock/app-icon.png new file mode 100644 index 000000000..58f50365d Binary files /dev/null and b/apps/configurable_clock/app-icon.png differ diff --git a/apps/configurable_clock/app-screenshot.png b/apps/configurable_clock/app-screenshot.png new file mode 100644 index 000000000..528721759 Binary files /dev/null and b/apps/configurable_clock/app-screenshot.png differ diff --git a/apps/configurable_clock/app.js b/apps/configurable_clock/app.js new file mode 100644 index 000000000..157d57741 --- /dev/null +++ b/apps/configurable_clock/app.js @@ -0,0 +1,1380 @@ + let Layout = require('Layout'); + + let Caret = require("heatshrink").decompress(atob("hEUgMAsFgmEwjEYhkMg0GAYIHBBYIPBgAA==")); + + let ScreenWidth = g.getWidth(), CenterX; + let ScreenHeight = g.getHeight(), CenterY, outerRadius; + + Bangle.loadWidgets(); + +/**** updateClockFaceSize ****/ + + function updateClockFaceSize () { + CenterX = ScreenWidth/2; + CenterY = ScreenHeight/2; + + outerRadius = Math.min(CenterX,CenterY); + + if (global.WIDGETS == null) { return; } + + let WidgetLayouts = { + tl:{ x:0, y:0, Direction:0 }, + tr:{ x:ScreenWidth-1, y:0, Direction:1 }, + bl:{ x:0, y:ScreenHeight-24, Direction:0 }, + br:{ x:ScreenWidth-1, y:ScreenHeight-24, Direction:1 } + }; + + for (let Widget of WIDGETS) { + let WidgetLayout = WidgetLayouts[Widget.area]; // reference, not copy! + if (WidgetLayout == null) { continue; } + + Widget.x = WidgetLayout.x - WidgetLayout.Direction * Widget.width; + Widget.y = WidgetLayout.y; + + WidgetLayout.x += Widget.width * (1-2*WidgetLayout.Direction); + } + + let x,y, dx,dy; + let cx = CenterX, cy = CenterY, r = outerRadius, r2 = r*r; + + x = WidgetLayouts.tl.x; y = WidgetLayouts.tl.y+24; dx = x - cx; dy = y - cy; + if (dx*dx + dy*dy < r2) { + cy = CenterY + 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.min(Math.sqrt(r2),cy-24); + } + + x = WidgetLayouts.tr.x; y = WidgetLayouts.tr.y+24; dx = x - cx; dy = y - cy; + if (dx*dx + dy*dy < r2) { + cy = CenterY + 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.min(Math.sqrt(r2),cy-24); + } + + x = WidgetLayouts.bl.x; y = WidgetLayouts.bl.y; dx = x - cx; dy = y - cy; + if (dx*dx + dy*dy < r2) { + cy = CenterY - 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.min(Math.sqrt(r2),cy); + } + + x = WidgetLayouts.br.x; y = WidgetLayouts.br.y; dx = x - cx; dy = y - cy; + if (dx*dx + dy*dy < r2) { + cy = CenterY - 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.min(Math.sqrt(r2),cy); + } + + CenterX = cx; CenterY = cy; outerRadius = r - 4; + } + + updateClockFaceSize(); + +/**** custom version of Bangle.drawWidgets (does not clear the widget areas) ****/ + + Bangle.drawWidgets = function () { + var w = g.getWidth(), h = g.getHeight(); + + var pos = { + tl:{x:0, y:0, r:0, c:0}, // if r==1, we're right->left + tr:{x:w-1, y:0, r:1, c:0}, + bl:{x:0, y:h-24, r:0, c:0}, + br:{x:w-1, y:h-24, r:1, c:0} + }; + + if (global.WIDGETS) { + for (var wd of WIDGETS) { + var p = pos[wd.area]; + if (!p) continue; + + wd.x = p.x - p.r*wd.width; + wd.y = p.y; + + p.x += wd.width*(1-2*p.r); + p.c++; + } + + g.reset(); // also loads the current theme + + if (pos.tl.c || pos.tr.c) { + g.setClipRect(0,h-24,w-1,h-1); + g.reset(); // also (re)loads the current theme + } + + if (pos.bl.c || pos.br.c) { + g.setClipRect(0,h-24,w-1,h-1); + g.reset(); // also (re)loads the current theme + } + + try { + for (wd of WIDGETS) { + g.clearRect(wd.x,wd.y, wd.x+wd.width-1,23); + wd.draw(wd); + } + } catch (e) { print(e); } + } + }; + +/**** EventConsumerAtPoint ****/ + + let activeLayout; + + function EventConsumerAtPoint (HandlerName, x,y) { + let Layout = (activeLayout || {}).l; + if (Layout == null) { return; } + + function ConsumerIn (Control) { + if ( + (x < Control.x) || (x >= Control.x + Control.w) || + (y < Control.y) || (y >= Control.y + Control.h) + ) { return undefined; } + + if (typeof Control[HandlerName] === 'function') { return Control; } + + if (Control.c != null) { + let ControlList = Control.c; + for (let i = 0, l = ControlList.length; i < l; i++) { + let Consumer = ConsumerIn(ControlList[i]); + if (Consumer != null) { return Consumer; } + } + } + + return undefined; + } + + return ConsumerIn(Layout); + } + +/**** dispatchTouchEvent ****/ + + function dispatchTouchEvent (DefaultHandler) { + function handleTouchEvent (Button, xy) { + if (activeLayout == null) { + if (typeof DefaultHandler === 'function') { + DefaultHandler(); + } + } else { + let Control = EventConsumerAtPoint('onTouch', xy.x,xy.y); + if (Control != null) { + Control.onTouch(Control, Button, xy); + } + } + } + Bangle.on('touch',handleTouchEvent); + } + dispatchTouchEvent(); + +/**** dispatchStrokeEvent ****/ + + function dispatchStrokeEvent (DefaultHandler) { + function handleStrokeEvent (Coordinates) { + if (activeLayout == null) { + if (typeof DefaultHandler === 'function') { + DefaultHandler(); + } + } else { + let Control = EventConsumerAtPoint('onStroke', Coordinates.xy[0],Coordinates.xy[1]); + if (Control != null) { + Control.onStroke(Control, Coordinates); + } + } + } + Bangle.on('stroke',handleStrokeEvent); + } + dispatchStrokeEvent(); +/**** Label ****/ + + function Label (Text, Options) { + function renderLabel (Details) { + let x = Details.x, xAlignment = Details.halign || 0; + let y = Details.y, yAlignment = Details.valign || 0; + + let Width = Details.w, halfWidth = Width/2; + let Height = Details.h, halfHeight = Height/2; + + let Border = Details.border || 0, BorderColor = Details.BorderColor; + let Padding = Details.pad || 0; + let Hilite = Details.hilite || false; + let bold = Details.bold ? 1 : 0; + + if (Hilite || (Details.bgCol != null)) { + g.setBgColor(Hilite ? g.theme.bgH : Details.bgCol); + g.clearRect(x,y, x + Width-1,y + Height-1); + } + + if ((Border > 0) && (BorderColor !== null)) {// draw border of layout cell + g.setColor(BorderColor || Details.col || g.theme.fg); + + switch (Border) { + case 1: g.drawRect(x,y, x+Width-1,y+Height-1); break; + case 2: g.drawRect(x,y, x+Width-1,y+Height-1); + g.drawRect(x+1,y+1, x+Width-2,y+Height-2); break; + default: g.fillPoly([ + x,y, x+Width,y, x+Width,y+Height, x,y+Height, x,y, + x+Border,y+Border, x+Border,y+Height-Border, + x+Width-Border,y+Height-Border, x+Width-Border,y+Border, + x+Border,y+Border + ]); + } + } + + g.setClipRect( + x+Border+Padding,y+Border+Padding, + x + Width-Border-Padding-1,y + Height-Border-Padding-1 + ); + + x += halfWidth + xAlignment*(halfWidth - Border - Padding); + y += halfHeight + yAlignment*(halfHeight - Border - Padding); + + g.setColor (Hilite ? g.theme.fgH : Details.col || g.theme.fg); + g.setBgColor(Hilite ? g.theme.bgH : Details.bgCol || g.theme.bg); + + if (Details.font != null) { g.setFont(Details.font); } + g.setFontAlign(xAlignment,yAlignment); + + g.drawString(Details.label, x,y); + if (bold !== 0) { + g.drawString(Details.label, x+1,y); + g.drawString(Details.label, x,y+1); + g.drawString(Details.label, x+1,y+1); + } + } + + let Result = Object.assign(( + Options == null ? {} : Object.assign({}, Options.common || {}, Options) + ), { + type:'custom', render:renderLabel, label:Text || '' + }); + let Border = Result.border || 0; + let Padding = Result.pad || 0; + + let TextMetrics; + if (! Result.width || ! Result.height) { + if (Result.font == null) { + Result.font = g.getFont(); + } else { + g.setFont(Result.font); + } + TextMetrics = g.stringMetrics(Result.label); + } + + if (Result.col == null) { Result.col = g.getColor(); } + if (Result.bgCol == null) { Result.bgCol = g.getBgColor(); } + + Result.width = Result.width || TextMetrics.width + 2*Border + 2*Padding; + Result.height = Result.height || TextMetrics.height + 2*Border + 2*Padding; + return Result; + } + +/**** Image ****/ + + function Image (Image, Options) { + function renderImage (Details) { + let x = Details.x, xAlignment = Details.halign || 0; + let y = Details.y, yAlignment = Details.valign || 0; + + let Width = Details.w, halfWidth = Width/2 - Details.ImageWidth/2; + let Height = Details.h, halfHeight = Height/2 - Details.ImageHeight/2; + + let Border = Details.border || 0, BorderColor = Details.BorderColor; + let Padding = Details.pad || 0; + let Hilite = Details.hilite || false; + + if (Hilite || (Details.bgCol != null)) { + g.setBgColor(Hilite ? g.theme.bgH : Details.bgCol); + g.clearRect(x,y, x + Width-1,y + Height-1); + } + + if ((Border > 0) && (BorderColor !== null)) {// draw border of layout cell + g.setColor(BorderColor || Details.col || g.theme.fg); + + switch (Border) { + case 1: g.drawRect(x,y, x+Width-1,y+Height-1); break; + case 2: g.drawRect(x,y, x+Width-1,y+Height-1); + g.drawRect(x+1,y+1, x+Width-2,y+Height-2); break; + default: g.fillPoly([ + x,y, x+Width,y, x+Width,y+Height, x,y+Height, x,y, + x+Border,y+Border, x+Border,y+Height-Border, + x+Width-Border,y+Height-Border, x+Width-Border,y+Border, + x+Border,y+Border + ]); + } + } + + g.setClipRect( + x+Border+Padding,y+Border+Padding, + x + Width-Border-Padding-1,y + Height-Border-Padding-1 + ); + + x += halfWidth + xAlignment*(halfWidth - Border - Padding); + y += halfHeight + yAlignment*(halfHeight - Border - Padding); + + if ('rotate' in Details) { // "rotate" centers image at x,y! + x += Details.ImageWidth/2; + y += Details.ImageHeight/2; + } + + g.setColor (Hilite ? g.theme.fgH : Details.col || g.theme.fg); + g.setBgColor(Hilite ? g.theme.bgH : Details.bgCol || g.theme.bg); + + g.drawImage(Image, x,y, Details.ImageOptions); + } + + let Result = Object.assign(( + Options == null ? {} : Object.assign({}, Options.common || {}, Options) + ), { + type:'custom', render:renderImage, Image:Image + }); + let ImageMetrics = g.imageMetrics(Image); + let Scale = Result.scale || 1; + let Border = Result.border || 0; + let Padding = Result.pad || 0; + + Result.ImageWidth = Scale * ImageMetrics.width; + Result.ImageHeight = Scale * ImageMetrics.height; + + if (('rotate' in Result) || ('scale' in Result) || ('frame' in Result)) { + Result.ImageOptions = {}; + if ('rotate' in Result) { Result.ImageOptions.rotate = Result.rotate; } + if ('scale' in Result) { Result.ImageOptions.scale = Result.scale; } + if ('frame' in Result) { Result.ImageOptions.frame = Result.frame; } + } + + Result.width = Result.width || Result.ImageWidth + 2*Border + 2*Padding; + Result.height = Result.height || Result.ImageHeight + 2*Border + 2*Padding; + return Result; + } + +/**** Drawable ****/ + + function Drawable (Callback, Options) { + function renderDrawable (Details) { + let x = Details.x, xAlignment = Details.halign || 0; + let y = Details.y, yAlignment = Details.valign || 0; + + let Width = Details.w, DrawableWidth = Details.DrawableWidth || Width; + let Height = Details.h, DrawableHeight = Details.DrawableHeight || Height; + + let halfWidth = Width/2 - DrawableWidth/2; + let halfHeight = Height/2 - DrawableHeight/2; + + let Border = Details.border || 0, BorderColor = Details.BorderColor; + let Padding = Details.pad || 0; + let Hilite = Details.hilite || false; + + if (Hilite || (Details.bgCol != null)) { + g.setBgColor(Hilite ? g.theme.bgH : Details.bgCol); + g.clearRect(x,y, x + Width-1,y + Height-1); + } + + if ((Border > 0) && (BorderColor !== null)) {// draw border of layout cell + g.setColor(BorderColor || Details.col || g.theme.fg); + + switch (Border) { + case 1: g.drawRect(x,y, x+Width-1,y+Height-1); break; + case 2: g.drawRect(x,y, x+Width-1,y+Height-1); + g.drawRect(x+1,y+1, x+Width-2,y+Height-2); break; + default: g.fillPoly([ + x,y, x+Width,y, x+Width,y+Height, x,y+Height, x,y, + x+Border,y+Border, x+Border,y+Height-Border, + x+Width-Border,y+Height-Border, x+Width-Border,y+Border, + x+Border,y+Border + ]); + } + } + + let DrawableX = x + halfWidth + xAlignment*(halfWidth - Border - Padding); + let DrawableY = y + halfHeight + yAlignment*(halfHeight - Border - Padding); + + g.setClipRect( + Math.max(x+Border+Padding,DrawableX), + Math.max(y+Border+Padding,DrawableY), + Math.min(x+Width -Border-Padding,DrawableX+DrawableWidth)-1, + Math.min(y+Height-Border-Padding,DrawableY+DrawableHeight)-1 + ); + + g.setColor (Hilite ? g.theme.fgH : Details.col || g.theme.fg); + g.setBgColor(Hilite ? g.theme.bgH : Details.bgCol || g.theme.bg); + + Callback(DrawableX,DrawableY, DrawableWidth,DrawableHeight, Details); + } + + let Result = Object.assign(( + Options == null ? {} : Object.assign({}, Options.common || {}, Options) + ), { + type:'custom', render:renderDrawable, cb:Callback + }); + let DrawableWidth = Result.DrawableWidth || 10; + let DrawableHeight = Result.DrawableHeight || 10; + + let Border = Result.border || 0; + let Padding = Result.pad || 0; + + Result.width = Result.width || DrawableWidth + 2*Border + 2*Padding; + Result.height = Result.height || DrawableHeight + 2*Border + 2*Padding; + return Result; + } + + if (g.drawRoundedRect == null) { + g.drawRoundedRect = function drawRoundedRect (x1,y1, x2,y2, r) { + let x,y; + if (x1 > x2) { x = x1; x1 = x2; x2 = x; } + if (y1 > y2) { y = y1; y1 = y2; y2 = y; } + + r = Math.min(r || 0, (x2-x1)/2, (y2-y1)/2); + + let cx1 = x1+r, cx2 = x2-r; + let cy1 = y1+r, cy2 = y2-r; + + this.drawLine(cx1,y1, cx2,y1); + this.drawLine(cx1,y2, cx2,y2); + this.drawLine(x1,cy1, x1,cy2); + this.drawLine(x2,cy1, x2,cy2); + + x = r; y = 0; + + let dx,dy, Error = 0; + while (y <= x) { + dy = 1 + 2*y; y++; Error -= dy; + if (Error < 0) { + dx = 1 - 2*x; x--; Error -= dx; + } + + this.setPixel(cx1 - x, cy1 - y); this.setPixel(cx1 - y, cy1 - x); + this.setPixel(cx2 + x, cy1 - y); this.setPixel(cx2 + y, cy1 - x); + this.setPixel(cx2 + x, cy2 + y); this.setPixel(cx2 + y, cy2 + x); + this.setPixel(cx1 - x, cy2 + y); this.setPixel(cx1 - y, cy2 + x); + } + }; + } + + if (g.fillRoundedRect == null) { + g.fillRoundedRect = function fillRoundedRect (x1,y1, x2,y2, r) { + let x,y; + if (x1 > x2) { x = x1; x1 = x2; x2 = x; } + if (y1 > y2) { y = y1; y1 = y2; y2 = y; } + + r = Math.min(r || 0, (x2-x1)/2, (y2-y1)/2); + + let cx1 = x1+r, cx2 = x2-r; + let cy1 = y1+r, cy2 = y2-r; + + this.fillRect(x1,cy1, x2,cy2); + + x = r; y = 0; + + let dx,dy, Error = 0; + while (y <= x) { + dy = 1 + 2*y; y++; Error -= dy; + if (Error < 0) { + dx = 1 - 2*x; x--; Error -= dx; + } + + this.drawLine(cx1 - x, cy1 - y, cx2 + x, cy1 - y); + this.drawLine(cx1 - y, cy1 - x, cx2 + y, cy1 - x); + this.drawLine(cx1 - x, cy2 + y, cx2 + x, cy2 + y); + this.drawLine(cx1 - y, cy2 + x, cx2 + y, cy2 + x); + } + }; + } + + +/**** Button ****/ + + function Button (Text, Options) { + function renderButton (Details) { + let x = Details.x, Width = Details.w, halfWidth = Width/2; + let y = Details.y, Height = Details.h, halfHeight = Height/2; + + let Padding = Details.pad || 0; + let Hilite = Details.hilite || false; + + if (Details.bgCol != null) { + g.setBgColor(Details.bgCol); + g.clearRect(x,y, x + Width-1,y + Height-1); + } + + if (Hilite) { + g.setColor(g.theme.bgH); // no typo! + g.fillRoundedRect(x+Padding,y+Padding, x+Width-Padding-1,y+Height-Padding-1,8); + } + + g.setColor (Hilite ? g.theme.fgH : Details.col || g.theme.fg); + g.setBgColor(Hilite ? g.theme.bgH : Details.bgCol || g.theme.bg); + + if (Details.font != null) { g.setFont(Details.font); } + g.setFontAlign(0,0); + + g.drawRoundedRect(x+Padding,y+Padding, x+Width-Padding-1,y+Height-Padding-1,8); + + g.setClipRect(x+Padding,y+Padding, x+Width-Padding-1,y+Height-Padding-1); + + g.drawString(Details.label, x+halfWidth,y+halfHeight); + g.drawString(Details.label, x+halfWidth+1,y+halfHeight); + g.drawString(Details.label, x+halfWidth,y+halfHeight+1); + g.drawString(Details.label, x+halfWidth+1,y+halfHeight+1); + } + + let Result = Object.assign(( + Options == null ? {} : Object.assign({}, Options.common || {}, Options) + ), { + type:'custom', render:renderButton, label:Text || 'Tap' + }); + let Padding = Result.pad || 0; + + let TextMetrics; + if (! Result.width || ! Result.height) { + if (Result.font == null) { + Result.font = g.getFont(); + } else { + g.setFont(Result.font); + } + TextMetrics = g.stringMetrics(Result.label); + } + + Result.width = Result.width || TextMetrics.width + 2*10 + 2*Padding; + Result.height = Result.height || TextMetrics.height + 2*5 + 2*Padding; + return Result; + } + + const Checkbox_checked = require("heatshrink").decompress(atob("ikUgMf/+GgEGoEAlEAgOAgEYsFhw8OjE54OB/EYh4OB+EYj+BwecjFw8OGg0YDocUgECsEAsP//A")); + const Checkbox_unchecked = require("heatshrink").decompress(atob("ikUgMf/+GgEGoEAlEAgOAgEYAjkUgECsEAsP//A=")); + +/**** Checkbox ****/ + + function Checkbox (Options) { + function renderCheckbox (Details) { + let x = Details.x, xAlignment = Details.halign || 0; + let y = Details.y, yAlignment = Details.valign || 0; + + let Width = Details.w, halfWidth = Width/2 - 10; + let Height = Details.h, halfHeight = Height/2 - 10; + + let Padding = Details.pad || 0; + + if (Details.bgCol != null) { + g.setBgColor(Details.bgCol); + g.clearRect(x,y, x + Width-1,y + Height-1); + } + + x += halfWidth + xAlignment*(halfWidth - Padding); + y += halfHeight + yAlignment*(halfHeight - Padding); + + g.setColor (Details.col || g.theme.fg); + g.setBgColor(Details.bgCol || g.theme.bg); + + g.drawImage( + Details.checked ? Checkbox_checked : Checkbox_unchecked, x,y + ); + } + + let Result = Object.assign(( + Options == null ? {} : Object.assign({}, Options.common || {}, Options) + ), { + type:'custom', render:renderCheckbox, onTouch:toggleCheckbox + }); + let Padding = Result.pad || 0; + + Result.width = Result.width || 20 + 2*Padding; + Result.height = Result.height || 20 + 2*Padding; + + if (Result.checked == null) { Result.checked = false; } + return Result; + } + + /* private */ function toggleCheckbox (Control) { + g.reset(); + + Control.checked = ! Control.checked; + Control.render(Control); + + if (typeof Control.onChange === 'function') { + Control.onChange(Control); + } + } + +/**** toggleInnerCheckbox ****/ + + /* export */ function toggleInnerCheckbox (Control) { + if (Control.c == null) { + if (('checked' in Control) && ! ('GroupName' in Control)) { + toggleCheckbox(Control); + return true; + } + } else { + let ControlList = Control.c; + for (let i = 0, l = ControlList.length; i < l; i++) { + let done = toggleInnerCheckbox(ControlList[i]); + if (done) { return true; } + } + } + } + + const Radiobutton_checked = require("heatshrink").decompress(atob("ikUgMB/EAsFgjEBwUAgkggFEgECoEAlEPgOB/EYj+BAgmA+EUCYciDodBwEYg0GgEfwA")); + const Radiobutton_unchecked = require("heatshrink").decompress(atob("ikUgMB/EAsFgjEBwUAgkggFEgECoEAlEAgOAgEYAhEUCYciDodBwEYg0GgEfwAA=")); + +/**** Radiobutton ****/ + + function Radiobutton (Options) { + function renderRadiobutton (Details) { + let x = Details.x, xAlignment = Details.halign || 0; + let y = Details.y, yAlignment = Details.valign || 0; + + let Width = Details.w, halfWidth = Width/2 - 10; + let Height = Details.h, halfHeight = Height/2 - 10; + + let Padding = Details.pad || 0; + + if (Details.bgCol != null) { + g.setBgColor(Details.bgCol); + g.clearRect(x,y, x + Width-1,y + Height-1); + } + + x += halfWidth + xAlignment*(halfWidth - Padding); + y += halfHeight + yAlignment*(halfHeight - Padding); + + g.setColor (Details.col || g.theme.fg); + g.setBgColor(Details.bgCol || g.theme.bg); + + g.drawImage( + Details.checked ? Radiobutton_checked : Radiobutton_unchecked, x,y + ); + } + + let Result = Object.assign(( + Options == null ? {} : Object.assign({}, Options.common || {}, Options) + ), { + type:'custom', render:renderRadiobutton, onTouch:checkRadiobutton + }); + let Padding = Result.pad || 0; + + Result.width = Result.width || 20 + 2*Padding; + Result.height = Result.height || 20 + 2*Padding; + + if (Result.checked == null) { Result.checked = false; } + return Result; + } + + /* private */ function checkRadiobutton (Control) { + if (! Control.checked) { + uncheckRadiobuttonsIn((activeLayout || {}).l,Control.GroupName); + toggleRadiobutton(Control); + + if (typeof Control.onChange === 'function') { + Control.onChange(Control); + } + } + } + + /* private */ function toggleRadiobutton (Control) { + g.reset(); + + Control.checked = ! Control.checked; + Control.render(Control); + } + + /* private */ function uncheckRadiobuttonsIn (Control,GroupName) { + if ((Control == null) || (GroupName == null)) { return; } + + if (Control.c == null) { + if (('checked' in Control) && (Control.GroupName === GroupName)) { + if (Control.checked) { toggleRadiobutton(Control); } + } + } else { + let ControlList = Control.c; + for (let i = 0, l = ControlList.length; i < l; i++) { + uncheckRadiobuttonsIn(ControlList[i],GroupName); + } + } + } + +/**** checkInnerRadiobutton ****/ + + /* export */ function checkInnerRadiobutton (Control) { + if (Control.c == null) { + if (('checked' in Control) && ('GroupName' in Control)) { + checkRadiobutton(Control); + return true; + } + } else { + let ControlList = Control.c; + for (let i = 0, l = ControlList.length; i < l; i++) { + let done = checkInnerRadiobutton(ControlList[i]); + if (done) { return true; } + } + } + } + + + let Theme = g.theme; + g.clear(true); + +/**** Settings ****/ + + let Settings; + + function readSettings () { + Settings = Object.assign({}, + { + Face:'1-12', colored:true, + Hands:'rounded', withSeconds:true, + Foreground:'Theme', Background:'Theme', Seconds:'#FF0000' + }, + require('Storage').readJSON('configurable_clock.json', true) || {} + ); + + prepareTransformedPolygon(); + } + + function saveSettings () { + require('Storage').writeJSON('configurable_clock.json', Settings); + prepareTransformedPolygon(); + } + + function prepareTransformedPolygon () { + switch (Settings.Hands) { + case 'simple': transformedPolygon = new Array(simpleHourHandPolygon.length); break; + case 'rounded': transformedPolygon = new Array(roundedHandPolygon.length); break; + case 'hollow': transformedPolygon = new Array(hollowHandPolygon.length); + } + } + +//readSettings(); // not yet + + +/**** Hands ****/ + + let HourHandLength = outerRadius * 0.5; + let HourHandWidth = 2*3, halfHourHandWidth = HourHandWidth/2; + + let MinuteHandLength = outerRadius * 0.8; + let MinuteHandWidth = 2*2, halfMinuteHandWidth = MinuteHandWidth/2; + + let SecondHandLength = outerRadius * 0.9; + let SecondHandOffset = 10; + + let twoPi = 2*Math.PI, deg2rad = Math.PI/180; + let Pi = Math.PI; + let halfPi = Math.PI/2; + + let sin = Math.sin, cos = Math.cos; + +/**** simple Hands ****/ + + let simpleHourHandPolygon = [ + -halfHourHandWidth,halfHourHandWidth, + -halfHourHandWidth,halfHourHandWidth-HourHandLength, + halfHourHandWidth,halfHourHandWidth-HourHandLength, + halfHourHandWidth,halfHourHandWidth, + ]; + + let simpleMinuteHandPolygon = [ + -halfMinuteHandWidth,halfMinuteHandWidth, + -halfMinuteHandWidth,halfMinuteHandWidth-MinuteHandLength, + halfMinuteHandWidth,halfMinuteHandWidth-MinuteHandLength, + halfMinuteHandWidth,halfMinuteHandWidth, + ]; + + +/**** rounded Hands ****/ + + let outerBoltRadius = halfHourHandWidth + 2; + let innerBoltRadius = outerBoltRadius - 4; + let roundedHandOffset = outerBoltRadius + 4; + + let sine = [0, sin(30*deg2rad), sin(60*deg2rad), 1]; + + let roundedHandPolygon = [ + -sine[3],-sine[0], -sine[2],-sine[1], -sine[1],-sine[2], -sine[0],-sine[3], + sine[0],-sine[3], sine[1],-sine[2], sine[2],-sine[1], sine[3],-sine[0], + sine[3], sine[0], sine[2], sine[1], sine[1], sine[2], sine[0], sine[3], + -sine[0], sine[3], -sine[1], sine[2], -sine[2], sine[1], -sine[3], sine[0], + ]; + + let roundedHourHandPolygon = new Array(roundedHandPolygon.length); + for (let i = 0, l = roundedHandPolygon.length; i < l; i+=2) { + roundedHourHandPolygon[i] = halfHourHandWidth*roundedHandPolygon[i]; + roundedHourHandPolygon[i+1] = halfHourHandWidth*roundedHandPolygon[i+1]; + if (i < l/2) { roundedHourHandPolygon[i+1] -= HourHandLength; } + if (i > l/2) { roundedHourHandPolygon[i+1] += roundedHandOffset; } + } + let roundedMinuteHandPolygon = new Array(roundedHandPolygon.length); + for (let i = 0, l = roundedHandPolygon.length; i < l; i+=2) { + roundedMinuteHandPolygon[i] = halfMinuteHandWidth*roundedHandPolygon[i]; + roundedMinuteHandPolygon[i+1] = halfMinuteHandWidth*roundedHandPolygon[i+1]; + if (i < l/2) { roundedMinuteHandPolygon[i+1] -= MinuteHandLength; } + if (i > l/2) { roundedMinuteHandPolygon[i+1] += roundedHandOffset; } + } + + +/**** hollow Hands ****/ + + let BoltRadius = 3; + let hollowHandOffset = BoltRadius + 15; + + let hollowHandPolygon = [ + -sine[3],-sine[0], -sine[2],-sine[1], -sine[1],-sine[2], -sine[0],-sine[3], + sine[0],-sine[3], sine[1],-sine[2], sine[2],-sine[1], sine[3],-sine[0], + sine[3], sine[0], sine[2], sine[1], sine[1], sine[2], sine[0], sine[3], + 0,0, + -sine[0], sine[3], -sine[1], sine[2], -sine[2], sine[1], -sine[3], sine[0] + ]; + + let hollowHourHandPolygon = new Array(hollowHandPolygon.length); + for (let i = 0, l = hollowHandPolygon.length; i < l; i+=2) { + hollowHourHandPolygon[i] = halfHourHandWidth*hollowHandPolygon[i]; + hollowHourHandPolygon[i+1] = halfHourHandWidth*hollowHandPolygon[i+1]; + if (i < l/2) { hollowHourHandPolygon[i+1] -= HourHandLength; } + if (i > l/2) { hollowHourHandPolygon[i+1] -= hollowHandOffset; } + } + hollowHourHandPolygon[25] = -BoltRadius; + + let hollowMinuteHandPolygon = new Array(hollowHandPolygon.length); + for (let i = 0, l = hollowHandPolygon.length; i < l; i+=2) { + hollowMinuteHandPolygon[i] = halfMinuteHandWidth*hollowHandPolygon[i]; + hollowMinuteHandPolygon[i+1] = halfMinuteHandWidth*hollowHandPolygon[i+1]; + if (i < l/2) { hollowMinuteHandPolygon[i+1] -= MinuteHandLength; } + if (i > l/2) { hollowMinuteHandPolygon[i+1] -= hollowHandOffset; } + } + hollowMinuteHandPolygon[25] = -BoltRadius; + + + +/**** transform polygon ****/ + + let transformedPolygon; + + function transformPolygon (originalPolygon, OriginX,OriginY, Phi) { + let sPhi = sin(Phi), cPhi = cos(Phi), x,y; + + for (let i = 0, l = originalPolygon.length; i < l; i+=2) { + x = originalPolygon[i]; + y = originalPolygon[i+1]; + + transformedPolygon[i] = OriginX + x*cPhi + y*sPhi; + transformedPolygon[i+1] = OriginY + x*sPhi - y*cPhi; + } + } + +/**** refreshClock ****/ + + let Timer; + function refreshClock () { + activeLayout = null; + + g.setTheme({ + fg:(Settings.Foreground === 'Theme' ? Theme.fg : Settings.Foreground || '#000000'), + bg:(Settings.Background === 'Theme' ? Theme.bg : Settings.Background || '#FFFFFF') + }); + g.clear(true); // also installs the current theme + + Bangle.drawWidgets(); + renderClock(); + + let Period = (Settings.withSeconds ? 1000 : 60000); + + let Pause = Period - (Date.now() % Period); + Timer = setTimeout(refreshClock,Pause); + } + +/**** renderClock ****/ + + function renderClock () { + g.setColor (Settings.Foreground === 'Theme' ? Theme.fg : Settings.Foreground || '#000000'); + g.setBgColor(Settings.Background === 'Theme' ? Theme.bg : Settings.Background || '#FFFFFF'); + + switch (Settings.Face) { + case 'none': + break; + case '3,6,9,12': + g.setFont('Vector', 22); + + g.setFontAlign(0,-1); + g.drawString('12', CenterX,CenterY-outerRadius); + + g.setFontAlign(1,0); + g.drawString('3', CenterX+outerRadius,CenterY); + + g.setFontAlign(0,1); + g.drawString('6', CenterX,CenterY+outerRadius); + + g.setFontAlign(-1,0); + g.drawString('9', CenterX-outerRadius,CenterY); + break; + case '1-12': + let innerRadius = outerRadius * 0.9 - 10; + + let dark = g.theme.dark; + + let Saturations = [0.8,1.0,1.0,1.0,1.0,1.0,1.0,0.9,0.7,0.7,0.9,0.9]; + let Brightnesses = [1.0,0.9,0.6,0.6,0.8,0.8,0.7,1.0,1.0,1.0,1.0,1.0,]; + + for (let i = 0; i < 60; i++) { + let Phi = i * twoPi/60; + + let x = CenterX + outerRadius * sin(Phi); + let y = CenterY - outerRadius * cos(Phi); + + if (Settings.colored) { + let j = Math.floor(i / 5); + let Saturation = (dark ? Saturations[j] : 1.0); + let Brightness = (dark ? 1.0 : Brightnesses[j]); + + let Color = E.HSBtoRGB(i/60,Saturation,Brightness, true); + g.setColor(Color[0]/255,Color[1]/255,Color[2]/255); + } + + g.fillCircle(x,y, 1); + } + + g.setFont('Vector', 20); + g.setFontAlign(0,0); + + for (let i = 0; i < 12; i++) { + let Phi = i * twoPi/12; + + let Radius = innerRadius; + if (i >= 10) { Radius -= 4; } + + let x = CenterX + Radius * sin(Phi); + let y = CenterY - Radius * cos(Phi); + + if (Settings.colored) { + let Saturation = (dark ? Saturations[i] : 1.0); + let Brightness = (dark ? 1.0 : Brightnesses[i]); + + let Color = E.HSBtoRGB(i/12,Saturation,Brightness, true); + g.setColor(Color[0]/255,Color[1]/255,Color[2]/255); + } + + g.drawString(i == 0 ? '12' : '' + i, x,y); + } + } + + let now = new Date(); + + let Hours = now.getHours() % 12; + let Minutes = now.getMinutes(); + + let HoursAngle = (Hours+(Minutes/60))/12 * twoPi - Pi; + let MinutesAngle = (Minutes/60) * twoPi - Pi; + + g.setColor(Settings.Foreground === 'Theme' ? Theme.fg : Settings.Foreground || '#000000'); + + switch (Settings.Hands) { + case 'simple': + transformPolygon(simpleHourHandPolygon, CenterX,CenterY, HoursAngle); + g.fillPoly(transformedPolygon); + + transformPolygon(simpleMinuteHandPolygon, CenterX,CenterY, MinutesAngle); + g.fillPoly(transformedPolygon); + break; + case 'rounded': + transformPolygon(roundedHourHandPolygon, CenterX,CenterY, HoursAngle); + g.fillPoly(transformedPolygon); + + transformPolygon(roundedMinuteHandPolygon, CenterX,CenterY, MinutesAngle); + g.fillPoly(transformedPolygon); + +// g.setColor(Settings.Foreground === 'Theme' ? Theme.fg || '#000000'); + g.fillCircle(CenterX,CenterY, outerBoltRadius); + + g.setColor(Settings.Background === 'Theme' ? Theme.bg : Settings.Background || '#FFFFFF'); + g.drawCircle(CenterX,CenterY, outerBoltRadius); + g.fillCircle(CenterX,CenterY, innerBoltRadius); + break; + case 'hollow': + transformPolygon(hollowHourHandPolygon, CenterX,CenterY, HoursAngle); + g.drawPoly(transformedPolygon,true); + + transformPolygon(hollowMinuteHandPolygon, CenterX,CenterY, MinutesAngle); + g.drawPoly(transformedPolygon,true); + + g.drawCircle(CenterX,CenterY, BoltRadius); + } + + if (Settings.withSeconds) { + g.setColor(Settings.Seconds === 'Theme' ? Theme.fgH : Settings.Seconds || '#FF0000'); + + let Seconds = now.getSeconds(); + let SecondsAngle = (Seconds/60) * twoPi - Pi; + + let sPhi = Math.sin(SecondsAngle), cPhi = Math.cos(SecondsAngle); + + g.drawLine( + CenterX + SecondHandOffset*sPhi, + CenterY - SecondHandOffset*cPhi, + CenterX - SecondHandLength*sPhi, + CenterY + SecondHandLength*cPhi + ); + } + } + + +/**** MainScreen Logic ****/ + + let Changes = {}, KeysToChange; + + let fullScreen = { + x:0,y:0, w:ScreenWidth,h:ScreenHeight, x2:ScreenWidth-1,y2:ScreenHeight-1 + }; + let AppRect; + + function openMainScreen () { + if (Timer != null) { clearTimeout(Timer); Timer = undefined; } + if (AppRect == null) { AppRect = Bangle.appRect; Bangle.appRect = fullScreen; } + + Bangle.buzz(); + + KeysToChange = 'Face colored Hands withSeconds Foreground Background Seconds'; + + g.setTheme({ fg:'#000000', bg:'#FFFFFF' }); + g.clear(true); // also installs the current theme + + (activeLayout = MainScreen).render(); + } + + function applySettings () { Bangle.buzz(); saveSettings(); Bangle.appRect = AppRect; refreshClock(); } + function withdrawSettings () { Bangle.buzz(); readSettings(); Bangle.appRect = AppRect; refreshClock(); } + +/**** FacesScreen Logic ****/ + + function openFacesScreen () { + Bangle.buzz(); + + KeysToChange = 'Face colored'; + Bangle.appRect = fullScreen; + refreshFacesScreen(); + } + + function refreshFacesScreen () { + activeLayout = FacesScreen; + activeLayout['none'].checked = ((Changes.Face || Settings.Face) === 'none'); + activeLayout['3,6,9,12'].checked = ((Changes.Face || Settings.Face) === '3,6,9,12'); + activeLayout['1-12'].checked = ((Changes.Face || Settings.Face) === '1-12'); + activeLayout['colored'].checked = (Changes.colored == null ? Settings.colored : Changes.colored); + activeLayout.render(); + } + + function chooseFace (Control) { Bangle.buzz(); Changes.Face = Control.id; refreshFacesScreen(); } + function toggleColored () { Bangle.buzz(); Changes.colored = ! Changes.colored; refreshFacesScreen(); } + +/**** HandsScreen Logic ****/ + + function openHandsScreen () { + Bangle.buzz(); + + KeysToChange = 'Hands withSeconds'; + Bangle.appRect = fullScreen; + refreshHandsScreen(); + } + + function refreshHandsScreen () { + activeLayout = HandsScreen; + activeLayout['simple'].checked = ((Changes.Hands || Settings.Hands) === 'simple'); + activeLayout['rounded'].checked = ((Changes.Hands || Settings.Hands) === 'rounded'); + activeLayout['hollow'].checked = ((Changes.Hands || Settings.Hands) === 'hollow'); + activeLayout['withSeconds'].checked = (Changes.withSeconds == null ? Settings.withSeconds : Changes.withSeconds); + activeLayout.render(); + } + + function chooseHand (Control) { Bangle.buzz(); Changes.Hands = Control.id; refreshHandsScreen(); } + function toggleSeconds () { Bangle.buzz(); Changes.withSeconds = ! Changes.withSeconds; refreshHandsScreen(); } + +/**** ColorsScreen Logic ****/ + + function openColorsScreen () { + Bangle.buzz(); + + KeysToChange = 'Foreground Background Seconds'; + Bangle.appRect = fullScreen; + refreshColorsScreen(); + } + + function refreshColorsScreen () { + let Foreground = (Changes.Foreground == null ? Settings.Foreground : Changes.Foreground); + let Background = (Changes.Background == null ? Settings.Background : Changes.Background); + let Seconds = (Changes.Seconds == null ? Settings.Seconds : Changes.Seconds); + + activeLayout = ColorsScreen; + activeLayout['Foreground'].bgCol = (Foreground === 'Theme' ? Theme.fg : Foreground); + activeLayout['Background'].bgCol = (Background === 'Theme' ? Theme.bg : Background); + activeLayout['Seconds'].bgCol = (Seconds === 'Theme' ? Theme.fgH : Seconds); + activeLayout.render(); + } + + function selectForegroundColor () { ColorToChange = 'Foreground'; openColorChoiceScreen(); } + function selectBackgroundColor () { ColorToChange = 'Background'; openColorChoiceScreen(); } + function selectSecondsColor () { ColorToChange = 'Seconds'; openColorChoiceScreen(); } + +/**** ColorChoiceScreen Logic ****/ + + let ColorToChange, chosenColor; + + function openColorChoiceScreen () { + Bangle.buzz(); + + chosenColor = ( + Changes[ColorToChange] == null ? Settings[ColorToChange] : Changes[ColorToChange] + ); + Bangle.appRect = fullScreen; + refreshColorChoiceScreen(); + } + + function refreshColorChoiceScreen () { + activeLayout = ColorChoiceScreen; + activeLayout['#000000'].selected = (chosenColor === '#000000'); + activeLayout['#FF0000'].selected = (chosenColor === '#FF0000'); + activeLayout['#00FF00'].selected = (chosenColor === '#00FF00'); + activeLayout['#0000FF'].selected = (chosenColor === '#0000FF'); + activeLayout['#FFFF00'].selected = (chosenColor === '#FFFF00'); + activeLayout['#FF00FF'].selected = (chosenColor === '#FF00FF'); + activeLayout['#00FFFF'].selected = (chosenColor === '#00FFFF'); + activeLayout['#FFFFFF'].selected = (chosenColor === '#FFFFFF'); + activeLayout['Theme'].selected = (chosenColor === 'Theme'); + activeLayout.render(); + } + + function chooseColor (Control) { Bangle.buzz(); chosenColor = Control.id; refreshColorChoiceScreen(); } + function chooseThemeColor () { Bangle.buzz(); chosenColor = 'Theme'; refreshColorChoiceScreen(); } + + function applyColor () { + Changes[ColorToChange] = chosenColor; + openColorsScreen(); + } + + function withdrawColor () { + openColorsScreen(); + } + +/**** common logic for multiple screens ****/ + + function applyChanges () { + Settings = Object.assign(Settings,Changes); + Changes = {}; + openMainScreen(); + } + + function withdrawChanges () { + Changes = {}; + openMainScreen(); + } + + + g.setFont12x20(); // does not seem to be respected in layout! + + let OkCancelWidth = Math.max( + g.stringWidth('Ok'), g.stringWidth('Cancel') + ) + 2*10; + + let StdFont = { font:'12x20' }; + let legible = Object.assign({ col:'#000000', bgCol:'#FFFFFF' }, StdFont); + let leftAligned = Object.assign({ halign:-1, valign:0 }, legible); + let ColorView = Object.assign({ width:30, border:1, BorderColor:'#000000' }, StdFont); + let ColorChoice = Object.assign({ DrawableWidth:30, DrawableHeight:30, onTouch:chooseColor }, StdFont); + +/**** MainScreen ****/ + + let MainScreen = new Layout({ + type:'v', c:[ + Label('Settings', { common:legible, bold:true, filly:1 }), + { height:4 }, + { type:'h', c:[ + { width:4 }, + Label('Faces', { common:leftAligned, fillx:1 }), + Image(Caret, { common:leftAligned }), + { width:4 }, + ], filly:1, onTouch:openFacesScreen }, + { type:'h', c:[ + { width:4 }, + Label('Hands', { common:leftAligned, fillx:1 }), + Image(Caret, { common:leftAligned }), + { width:4 }, + ], filly:1, onTouch:openHandsScreen }, + { type:'h', c:[ + { width:4 }, + Label('Colors', { common:leftAligned, fillx:1 }), + Image(Caret, { common:leftAligned }), + { width:4 }, + ], filly:1, onTouch:openColorsScreen }, + { height:4 }, + { type:'h', c:[ + Button('Ok', { common:legible, width:OkCancelWidth, onTouch:applySettings }), + { width:4 }, + Button('Cancel', { common:legible, width:OkCancelWidth, onTouch:withdrawSettings }), + ], filly:1 }, + ], bgCol:'#FFFFFF' + }); + + +/**** FacesScreen ****/ + + let FacesScreen = new Layout({ + type:'v', c:[ + Label('Clock Faces', { common:legible, bold:true, filly:1 }), + { height:4 }, + { type:'h', c:[ + { width:4 }, + Radiobutton({ id:'none', GroupName:'Faces', common:legible, onChange:chooseFace }), + Label(' no Face', { common:leftAligned, pad:4, fillx:1 }), + ], filly:1, onTouch:checkInnerRadiobutton }, + { type:'h', c:[ + { width:4 }, + Radiobutton({ id:'3,6,9,12', GroupName:'Faces', common:legible, onChange:chooseFace }), + Label(' 3, 6, 9 and 12', { common:leftAligned, pad:4, fillx:1 }), + ], filly:1, onTouch:checkInnerRadiobutton }, + { type:'h', c:[ + { width:4 }, + Radiobutton({ id:'1-12', GroupName:'Faces', common:legible, onChange:chooseFace }), + Label(' numbers 1...12', { common:leftAligned, pad:4, fillx:1 }), + ], filly:1, onTouch:checkInnerRadiobutton }, + { type:'h', c:[ + { width:30 }, + Checkbox({ id:'colored', common:legible, onChange:toggleColored }), + Label(' colorful', { common:leftAligned, pad:4, fillx:1 }), + ], filly:1, onTouch:toggleInnerCheckbox }, + { height:4 }, + { type:'h', c:[ + Button('Ok', { common:legible, width:OkCancelWidth, onTouch:applyChanges }), + { width:4 }, + Button('Cancel', { common:legible, width:OkCancelWidth, onTouch:withdrawChanges }), + ], filly:1 }, + ], bgCol:'#FFFFFF' + }); + + +/**** HandsScreen ****/ + + let HandsScreen = new Layout({ + type:'v', c:[ + Label('Clock Hands', { common:legible, bold:true, filly:1 }), + { height:4 }, + { type:'h', c:[ + { width:4 }, + Radiobutton({ id:'simple', GroupName:'Faces', common:legible, onChange:chooseHand }), + Label(' simple', { common:leftAligned, pad:4, fillx:1 }), + ], filly:1, onTouch:checkInnerRadiobutton }, + { type:'h', c:[ + { width:4 }, + Radiobutton({ id:'rounded', GroupName:'Faces', common:legible, onChange:chooseHand }), + Label(' rounded + Bolt', { common:leftAligned, pad:4, fillx:1 }), + ], filly:1, onTouch:checkInnerRadiobutton }, + { type:'h', c:[ + { width:4 }, + Radiobutton({ id:'hollow', GroupName:'Faces', common:legible, onChange:chooseHand }), + Label(' hollow + Bolt', { common:leftAligned, pad:4, fillx:1 }), + ], filly:1, onTouch:checkInnerRadiobutton }, + { type:'h', c:[ + { width:4 }, + Checkbox({ id:'withSeconds', common:legible, onChange:toggleSeconds }), + Label(' show Seconds', { common:leftAligned, pad:4, fillx:1 }), + ], filly:1, onTouch:toggleInnerCheckbox }, + { height:4 }, + { type:'h', c:[ + Button('Ok', { common:legible, width:OkCancelWidth, onTouch:applyChanges }), + { width:4 }, + Button('Cancel', { common:legible, width:OkCancelWidth, onTouch:withdrawChanges }), + ], filly:1 }, + ], bgCol:'#FFFFFF' + }); + + +/**** ColorsScreen ****/ + + let ColorsScreen = new Layout({ + type:'v', c:[ + Label('Clock Colors', { common:legible, bold:true, filly:1 }), + { height:4 }, + { type:'h', c:[ + { width:4 }, + Label('Foreground', { common:leftAligned, pad:4, fillx:1 }), + Label('', { id:'Foreground', common:ColorView, bgCol:Theme.fg }), + { width:4 }, + ], filly:1, onTouch:selectForegroundColor }, + { type:'h', c:[ + { width:4 }, + Label('Background', { common:leftAligned, pad:4, fillx:1 }), + Label('', { id:'Background', common:ColorView, bgCol:Theme.bg }), + { width:4 }, + ], filly:1, onTouch:selectBackgroundColor }, + { type:'h', c:[ + { width:4 }, + Label('Seconds', { common:leftAligned, pad:4, fillx:1 }), + Label('', { id:'Seconds', common:ColorView, bgCol:Theme.fgH }), + { width:4 }, + ], filly:1, onTouch:selectSecondsColor }, + { height:4 }, + { type:'h', c:[ + Button('Ok', { common:legible, width:OkCancelWidth, onTouch:applyChanges }), + { width:4 }, + Button('Cancel', { common:legible, width:OkCancelWidth, onTouch:withdrawChanges }), + ], filly:1 }, + ], bgCol:'#FFFFFF' + }); + + +/**** ColorChoiceScreen ****/ + + function drawColorChoice (x,y, Width,Height, Details) { + let selected = Details.selected; + if (selected) { + g.setColor('#FF0000'); + g.fillPoly([ + x,y, x+Width-1,y, x+Width-1,y+Height-1, x,y+Height-1, x,y, + x+3,y+3, x+3,y+Height-4, x+Width-4,y+Height-4, x+Width-4,y+3, x+3,y+3 + ]); + } else { + g.setColor('#000000'); + g.drawRect(x+3,y+3, x+Width-4,y+Height-4); + } + + g.setColor(Details.col); + g.fillRect(x+4,y+4, x+Width-5,y+Height-5); + } + + let ColorChoiceScreen = new Layout({ + type:'v', c:[ + Label('Choose Color', { common:legible, bold:true, filly:1 }), + { height:4 }, + { type:'h', c:[ + Drawable(drawColorChoice, { id:'#000000', common:ColorChoice, col:'#000000' }), + { width:8 }, + Drawable(drawColorChoice, { id:'#FF0000', common:ColorChoice, col:'#FF0000' }), + { width:8 }, + Drawable(drawColorChoice, { id:'#00FF00', common:ColorChoice, col:'#00FF00' }), + { width:8 }, + Drawable(drawColorChoice, { id:'#0000FF', common:ColorChoice, col:'#0000FF' }), + ], filly:1 }, + { type:'h', c:[ + Drawable(drawColorChoice, { id:'#FFFFFF', common:ColorChoice, col:'#FFFFFF' }), + { width:8 }, + Drawable(drawColorChoice, { id:'#FFFF00', common:ColorChoice, col:'#FFFF00' }), + { width:8 }, + Drawable(drawColorChoice, { id:'#FF00FF', common:ColorChoice, col:'#FF00FF' }), + { width:8 }, + Drawable(drawColorChoice, { id:'#00FFFF', common:ColorChoice, col:'#00FFFF' }), + ], filly:1 }, + { type:'h', c:[ + Label('use Theme:', { id:'Theme', common:leftAligned, pad:4 }), + { width:10 }, + Drawable(drawColorChoice, { id:'Theme', common:ColorChoice, col:Theme.fg }), + ], filly:1, onTouch:chooseThemeColor }, + { height:4 }, + { type:'h', c:[ + Button('Ok', { common:legible, width:OkCancelWidth, onTouch:applyColor }), + { width:4 }, + Button('Cancel', { common:legible, width:OkCancelWidth, onTouch:withdrawColor }), + ], filly:1 }, + ], bgCol:'#FFFFFF' + }); + + + + readSettings(); + + Bangle.on('swipe', (Direction) => { + if (Direction === 0) { openMainScreen(); } + }); + + setTimeout(refreshClock, 500); // enqueue first draw request + + Bangle.on('lcdPower', (on) => { + if (on) { + if (Timer != null) { clearTimeout(Timer); Timer = undefined; } + refreshClock(); + } + }); + + Bangle.setUI('clock'); diff --git a/apps/configurable_clock/metadata.json b/apps/configurable_clock/metadata.json new file mode 100644 index 000000000..28feae7e4 --- /dev/null +++ b/apps/configurable_clock/metadata.json @@ -0,0 +1,17 @@ +{ "id": "configurable_clock", + "name": "Configurable Analog Clock", + "shortName":"Configurable Clock", + "version":"0.02", + "description": "an analog clock with several kinds of faces, hands and colors to choose from", + "icon": "app-icon.png", + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "allow_emulator": true, + "screenshots": [{"url":"app-screenshot.png"}], + "readme": "README.md", + "storage": [ + {"name":"configurable_clock.app.js","url":"app.js"}, + {"name":"configurable_clock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/contourclock/metadata.json b/apps/contourclock/metadata.json new file mode 100644 index 000000000..1d402c0f5 --- /dev/null +++ b/apps/contourclock/metadata.json @@ -0,0 +1,16 @@ +{ "id": "contourclock", + "name": "Contour Clock", + "shortName" : "Contour Clock", + "version":"0.01", + "icon": "app.png", + "description": "A Minimalist clockface with large Digits. Looks best with the dark theme", + "screenshots" : [{"url":"screenshot.png"}], + "tags": "clock", + "allow_emulator":true, + "supports" : ["BANGLEJS2"], + "type": "clock", + "storage": [ + {"name":"contourclock.app.js","url":"app.js"}, + {"name":"contourclock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/coretemp/ChangeLog b/apps/coretemp/ChangeLog index ea6911f1a..ad6f0742d 100644 --- a/apps/coretemp/ChangeLog +++ b/apps/coretemp/ChangeLog @@ -1,2 +1,3 @@ 0.01: New app 0.02: Cleanup interface and add settings, widget, add skin temp reporting. +0.03: Move code for recording to this app diff --git a/apps/coretemp/metadata.json b/apps/coretemp/metadata.json new file mode 100644 index 000000000..cb12624ae --- /dev/null +++ b/apps/coretemp/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "coretemp", + "name": "CoreTemp", + "version": "0.03", + "description": "Display CoreTemp device sensor data", + "icon": "coretemp.png", + "type": "app", + "tags": "health", + "readme": "README.md", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"coretemp.wid.js","url":"widget.js"}, + {"name":"coretemp.app.js","url":"coretemp.js"}, + {"name":"coretemp.recorder.js","url":"recorder.js"}, + {"name":"coretemp.settings.js","url":"settings.js"}, + {"name":"coretemp.img","url":"coretemp-icon.js","evaluate":true}, + {"name":"coretemp.boot.js","url":"boot.js"} + ], + "data": [{"name":"coretemp.json","url":"app-settings.json"}], + "screenshots": [{"url":"screenshot.png"}] +} diff --git a/apps/coretemp/recorder.js b/apps/coretemp/recorder.js new file mode 100644 index 000000000..1499605f3 --- /dev/null +++ b/apps/coretemp/recorder.js @@ -0,0 +1,31 @@ +(function(recorders) { + recorders.coretemp = function() { + var core = "", skin = ""; + var hasCore = false; + function onCore(c) { + core=c.core; + skin=c.skin; + hasCore = true; + } + return { + name : "Core", + fields : ["Core","Skin"], + getValues : () => { + var r = [core,skin]; + core = ""; + skin = ""; + return r; + }, + start : () => { + hasCore = false; + Bangle.on('CoreTemp', onCore); + }, + stop : () => { + hasCore = false; + Bangle.removeListener('CoreTemp', onCore); + }, + draw : (x,y) => g.setColor(hasCore?"#0f0":"#8f8").drawImage(atob("DAyBAAHh0js3EuDMA8A8AWBnDj9A8A=="),x,y) + }; + } +}) + diff --git a/apps/countdowntimer/metadata.json b/apps/countdowntimer/metadata.json new file mode 100644 index 000000000..ac7146add --- /dev/null +++ b/apps/countdowntimer/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "countdowntimer", + "name": "Countdown Timer", + "version": "0.01", + "description": "A simple countdown timer with a focus on usability", + "icon": "countdowntimer.png", + "tags": "timer,tool", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"countdowntimer.app.js","url":"countdowntimer.js"}, + {"name":"countdowntimer.img","url":"countdowntimer-icon.js","evaluate":true} + ] +} diff --git a/apps/counter/metadata.json b/apps/counter/metadata.json new file mode 100644 index 000000000..e455fda95 --- /dev/null +++ b/apps/counter/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "counter", + "name": "Counter", + "version": "0.03", + "description": "Simple counter", + "icon": "counter_icon.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "screenshots": [{"url":"bangle1-counter-screenshot.png"}], + "allow_emulator": true, + "storage": [ + {"name":"counter.app.js","url":"counter.js"}, + {"name":"counter.img","url":"counter-icon.js","evaluate":true} + ] +} diff --git a/apps/cprassist/metadata.json b/apps/cprassist/metadata.json new file mode 100644 index 000000000..d832e98c5 --- /dev/null +++ b/apps/cprassist/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "cprassist", + "name": "CPR Assist", + "version": "0.02", + "description": "Provides assistance while performing a CPR", + "icon": "cprassist-icon.png", + "tags": "tool,firstaid", + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "screenshots": [{"url":"bangle1-CPR-assist-screenshot.png"}], + "storage": [ + {"name":"cprassist.app.js","url":"cprassist.js"}, + {"name":"cprassist.img","url":"cprassist-icon.js","evaluate":true}, + {"name":"cprassist.settings.js","url":"settings.js"} + ] +} diff --git a/apps/crowclk/ChangeLog b/apps/crowclk/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/crowclk/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/crowclk/README.md b/apps/crowclk/README.md new file mode 100644 index 000000000..7a073edd5 --- /dev/null +++ b/apps/crowclk/README.md @@ -0,0 +1,11 @@ +# Crow Clock + +Crow Clock features the face of Mystery Science Theater's Crow T. Robot. +The code is based almost entirely on Bold Clock. + +## Features + +Its got Crow right there on the face! What more do you need? + +![](screenshot_crow.png) + diff --git a/apps/crowclk/crow_clock-icon.js b/apps/crowclk/crow_clock-icon.js new file mode 100644 index 000000000..1b424845f --- /dev/null +++ b/apps/crowclk/crow_clock-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkG/4AImcikUzBpIAHmURiIXBAYMjCx0hiU/AwfzA4wWIiYJHmMSCxUxgYLKERH/+UU8kvBY/zp1FBZEhp3uDA/yBQURFw8fl3tBoKJEmQWB9vk+MfFw3/n3SEwVCn/zkgGCkni/4wFFwP/mnjkQRB93UpoDBDoM96f/+JUEmKxB+nhl8ypvtolE73kofyj1PPYKSEWAXy8UimckFoXUiUzkUuC4IqBIwogB8gWBJAPd7pGCSAJECJAfxfAXzokSp3t7wvBAYPkkNNB4ZICRoIACokkFYIAE7viogPDkQCBMYkkonuolFqtVqgGCC4hgB+bEEmlNqEFolAAQfUoQPDLgPyVYku8sArx0BAQQGB6TTFOwYABnvVgFEqnkp1FokAhoXEIoPxeYn0CANEggXBoAGCoYQEibCDVAdFqgtBGIPlPII/EC46oBpqIB73tAYPURwioCVIQwFUQIACAoIuFC5AYBpwXD8idEC5fyd4tPC58093tCoIzBRooXCO43/n3u7wXBDQPiC58uI4vkBw0Ta4oABmq5BAAVFqQXITA0wgAAEgSdGj/yTI0lC4tUC44BBL41ACwcEO43xFoMTU47YC8ne8YNFRoTAG+jXMCgUxPAvzFwS/B9qFGIgXyMA00d4Xe6QLFRghgGGAfkFwxDEJAwkBqtVWY3/iQPEJA3zUwIhGUoQADiIXPkIeGGAkzmYXBkczDIZAHGAUyolE7vuqEA73tA4MjH44gCineaYYXBbQlBPo6SCrwQDFYIFD8qMEAA0hroqEAAXkqR8GRYshqotBAAdViQWLJQcVDIVVqJELGQ0ykUikYsJ")) diff --git a/apps/crowclk/crow_clock.js b/apps/crowclk/crow_clock.js new file mode 100644 index 000000000..97e5e61d9 --- /dev/null +++ b/apps/crowclk/crow_clock.js @@ -0,0 +1,153 @@ +var img = require("heatshrink").decompress(atob("y2WwkG/4A/AFUzAA5H/mUiiIACiAEDkUjJvUzI4UAABMBJoJM2+cyI4qRDTIYLEiU/JORIFawczHwPzAgKiCCAkjSWExaIcSaBvzkKaDTFw0BJAaARmZMDMBxJhZIJ9T+b3DclRJDSSSYHJdRJESSayKDzIAMJLpLCmIgeABAoDET0yiBLkmRyjN0RwEgEjEsHyEoMBEr/zkMAgtFSsAlBqlVJYIleXIMFp3uJb5JC73kqDieXAVU9wABJb0hineEYPkgEAcTfzkCUBJIRLdSYNVJIQjCcTjeBgFdJQhLCmZJeSwcTN7LeBiUUJQvtJa5JBqlOEQvkiUQgMvSjMCmZKGJYcjJLnu6sjkJ5BSjMj+ZKHcYURJaJJCbooACoM/GAKWXSgQEBkq5CO41FqJLPmURqjcGEQRKBGIqUUMYchOoPkgpLG6hLBFwJJT91ArwDBoQyHSib5DmJGB8lRijFG8lViUzD5EzkMVoj7IJQVSGZCURgBhDJQXtiUhPo/kGgMjJgvzSQVNCo3UEAJwCibJISiL3E+RvD+cVGo/tolBiUimYABmSSCVQy1Cka7DJQY1CAwgANkCUEM4MdXQcxiorBHA9EqMRAAMVqgQJqLUBJQXlFwqWBn5JP+SUBCYnzihKEkRLJTIQABSI5JEJQQHBJQv/kMAj7fRVIxKCoM/mIBBJZQAL8lBifzFIMlEgaCHJJyoJEoVAicxOQMyitNJKXUoMSn5KBDYJKCHA0ggKeFABEySg6eBZYNAiMhDwQvBqjWG7oABJI1EqMiOIPziUhgoKBqTOPAA7yBLY8xbAPRkURBoZLCcgXUolVAAdEUYXkotRia7C+cRiUUJRLPBgTPGb5//+Ne93lkchNIZ9CJgNFIoQADJoRIBCAJiEgMzJQPkRZDhO+SlJ+RKCl/ygMjJQfzVgNURoQAE6lFqB+BmI1C+UQifzJRfxcJswb5BABjpKBj/yiURJYJKBcQRIGAAdESoS7BLwMRifyrpuCaJMCJRbfJDIJxCJQMTmRLB+cRitOJJQAB8lFiMvmUhkfyWgK5B6rVJkEAHhDfMAAK8D+YPBJYMiJJxLCqqtCn5KEoI+JmI9LBgJjJ/8lJQoUBitNH4nUogAEJYrjBIQPxl8xMYJKKcJkgb5IABmonBqMzJQLeBJIhICA4fdJgxLBJQUjkIiBqQwJHxZWMRoInBqERJQMxqg5DIAPebo4KBd4fUSwMxiMQp3tJRbUK+TsLBoKVCkMBkUVIYSSBcYoAF9qYEoMSkIBB73kGJg/JKpQYDrouBmcVqqCCSRSYHCAPkqtQkclAoJKLapTrBC5QYBihKBn4DBQgwANUwJcCosvJQR8LIAU/KiBKHmZKCbhhLJAYPlkckA4JKMaxDqKJQgoB6MhqhJVAAPkJYPliS3DGRZBIKZAAGXwPVgtOGQQAH7oADbASXGMQNFghKOa4MDBAswdI6uHp1FrvtJIjOBqoALogUEohmBqvuoQxMJQMBBAsQgQXMJQNEbwVO9xNBqtQgAANgpNBToRQBotUiQyNkMARonzgAXO+VVO4LFB8lFJAdURINVIokVBAIQEDYIZBSwUTPp0Al45EgAXO+UF7x8B9tFIAdUHAI5BIIcFbYYSEJYQLBoAyPgEfAwfxKIoXKb4Q2FgpJCJYoSBAAdALwiWC8pKO+cQCAkxgLnEJRVeFYPtRQZAGIIRTF93ldYgcBcIJKQgQGDkBKPmIpB73kGgpKFUIMEBArrEgFe9xMBJRxECAohQEJRx/EgpAFIII9CT44ACK4NN9xKPbQibBgZKXRYXkoiMETwYKDoJKGSqBKGCx/yr3kpxKHoEFIodVAgYKDdQIWENYIzQgEvAgcfCx8UJQLTBAAVUbQQECJQoKEJQldNIQ4CJRxcCJ4gAM+cU91EaYJ+EoCaEJQgKCgpKFM4NOoLOCGZpKDmMACx//ktOonuJRZXBJQoKBJQcF7wdBqIyP/5KWmNeYQQ0DaobbEAgZcBrxPDcwPtpvkG4QAOiECG4UBCyDhD9qWCgpBBorfDKwNUAoQKBBwIUE6lOosvGaEgJQUgJSLhCFwKFCQwSeBIgblDBQLXBUgSUCMwNRZCH/mBGCJwYAPmQvC93UJYJBDIQRUCqhSDKYSfCMoTfRJQReBJSbhCGAJLDbAVEqoAFLYJUBqEFCAKcCoLfR/8xJQPziBKS/8kIQXeGoJACotVqlNR4SlBA4JUCJ4QXCoLfRJQsDJSUlolNPoSSEJAbnEBQNUIoIRBbwNEqJKS+MAJQT4S/8hiMVGQQ/Dc4IAHBYNdJIVE9tBiMSJSkvJSvzmcxineGwVFTQZLJpwSB91FiUzmYxS+RKXJgUhZwPUaQJJKAANFqpJDSSRKdJYdEbxQAD9tVptFiJJVJThLCShziDSaxKGj4cWJYMVShoACqMjFi5KDgBKY+UUJJ/u6rCXJT0xSiDhBqRK1kveJSHuoM/JTUQWa/zb4/UogABprhHl5Kz+VeJI7oCJgLhGFrBKbmJBCHgZEF8gNF9tSJWcl7w8ERwwHBJYtBn5KyiqNLBAVNJTnxJTXziiUMBI/ll5KXn5KBgZKWjo5D9qUHT45KXmMBVwMggT8WrzfMAAThE8jEWJUPUJJLhFJS8wJQcBJSyPEHwjhHJT6ZBJS1U7xKPCAhKWbgcxgBKWitVqlN9qaEJQ9O6lFqtQJS0QJQiZBACfziEAAAJNBogAJI4QRBY4RKVMQUygEvfqxKCACJ8CPCkAJQXygEffqxKUgJKaZAL9VcAgASYaqQBC4QyBgYcWACp4VmJiEiD+VJV0Bn4FCkAFEJSNVJKlVJSpEFKApKRqlFJKUFohKU+baFcwpKRr3tS6MFp3kJS0DGYj+VJQPu8lQJKHu8sfFikACwnzJSvzinuJZ8FpoSB8rCUbI7nFJSfu6lUogAJotUCIVFJS0/A4kggIHFAB0lHAXuogEDAA3kbwIABoIrUIQKdNNJ45DJRneAgVSYKjYH+UAiZKUrxKPAYYqUIJBTBD6sUHQXUJRRWD8oqU+LXHTxC1OJQfkcoZKKoIzGABswgM/BI0hgAJHABklIwRKKBQftqIpURYIWHmKfHABsxrqKGJQ3eAYTfU+cACxHyiAhVcJrfZRQMjGZCgJABkhaQaWHKYfkqQnUkEBCxMxgJtU+SWK8hSDop9IXpiJBGZTsJSxtNbAZEDJIPeSjA9MK5gkLiqSDJYIACJIXuoKUUaYMAaZbtLEplUbgft7pIDbwMSEiiHBHhhYBcKvzkJLEAAlFiJuVb4LSM+ThWJYbjDTIRJBYxbfYcIUAOSpLCitUogACqsSkYgWb5rhZFQcAgtVqEAgKTWb4USGB8CSyywCAAZ6OABMhb5wwDOy5Kdb6CnSJU0xgETLsRKjGwMADCJeSJUUyZiaWYJTfzDgMjCyUhSyxKb+UAgQXT+SWWfIIADgSUqPwaWTmZKGmZnSmSsWSx3zmczkUiiIACiDgFBQYQBCgIiLPioADkMAiSKHmUikMRitVqtEAAVFJQkFBQYQBqMRikikZOHV4MCSiqWEfQaPBRoJGBHANN73uAAfVJQkEBYnt6hPCJwJNBIQfzV4IuDSzAjBmaPBIxBKPAAhOCiMSIgQtEAC5nCicyiNUog2JJSZNDopMBmSUbfocAgtNGhhKVAAPkqATBiZJaWgcFpxKk91AgEBbzIAD+S1BqneGZvlJQlUJJ1FJILebAAcyJYQ0N9tEqoABqirO6jfBiRJe//zcQVNZh4AQ8hJBgTedJYkgJYKCOJKbegAAfycQKXeJM5LFohJa6hJoAAMyJYVU7xJXohJCiZJmJYkAqtEACtFJIc/JVBLEJoQARI4IABboJJqJYzlBbZ9VJIhIrAAXzkJMEolNI5HUJAkAiSSsTA0Rco1UogACbYsAiLctTBBMGABEBiMimZIzAAczmUhJpBHBiUjJHCZEmczkQAFI4La0AGoA==")); + +var hour_hand = { + width : 61, height : 8, bpp : 1, + transparent : 0, + buffer : E.toArrayBuffer(atob("/////////////////////////////////////////////////////////////////////////////////w==")) +}; +var minute_hand = { + width : 110, height : 4, bpp : 1, + transparent : 0, + buffer : E.toArrayBuffer(atob("/////////////////////////////////////////////////////////////////////////w==")) +}; + +//g.fillRect(0,24,239,239); // Apps area +let intervalRef = null; +const p180 = Math.PI/180; +const clock_center = {x:Math.floor((g.getWidth()-1)/2), y:24+Math.floor((g.getHeight()-25)/2)}; +// ={ x: 119, y: 131 } +const radius = Math.floor((g.getWidth()-24+1)/2); // =108 + +let tick0 = Graphics.createArrayBuffer(30,8,1,{msb:true}); +tick0.fillRect(0,0,tick0.getWidth()-1, tick0.getHeight()-1); +let tick5 = Graphics.createArrayBuffer(20,6,1,{msb:true}); +tick5.fillRect(0,0,tick5.getWidth()-1, tick5.getHeight()-1); +let tick1 = Graphics.createArrayBuffer(8,4,1,{msb:true}); +tick1.fillRect(0,0,tick1.getWidth()-1, tick1.getHeight()-1); + +// Adjust hand lengths to be within 'tick' points +minute_hand.width=radius-tick1.getWidth()-6; +hour_hand.width=radius-tick5.getWidth()-6; + +function big_wheel_x(angle){ + return clock_center.x + radius * Math.cos(angle*p180); +} +function big_wheel_y(angle){ + return clock_center.y + radius * Math.sin(angle*p180); +} +function rotate_around_x(center_x, angle, tick){ + return center_x + Math.cos(angle*p180) * tick.getWidth()/2; +} +function rotate_around_y(center_y, angle, tick){ + return center_y + Math.sin(angle*p180) * tick.getWidth()/2; +} +function hour_pos_x(angle){ + return clock_center.x + Math.cos(angle*p180) * hour_hand.width/2; +} +function hour_pos_y(angle){ + return clock_center.y + Math.sin(angle*p180) * hour_hand.width/2; +} +function minute_pos_x(angle){ + return clock_center.x + Math.cos(angle*p180) * minute_hand.width/2; +} +function minute_pos_y(angle){ + return clock_center.y + Math.sin(angle*p180) * minute_hand.width/2; +} +function minute_angle(date){ + //let minutes = date.getMinutes() + date.getSeconds()/60; + let minutes = date.getMinutes(); + return 6*minutes - 90; +} +function hour_angle(date){ + let hours= date.getHours() + date.getMinutes()/60; + return 30*hours - 90; +} + +function draw_clock(){ + //console.log("draw_clock"); + let date = new Date(); + g.reset(); + g.clearRect(0,24,239,239); // clear app area + + g.drawImage(img, 12, 24); + + // draw cross lines for testing + // g.setColor(1,0,0); + // g.drawLine(clock_center.x - radius, clock_center.y, clock_center.x + radius, clock_center.y); + // g.drawLine(clock_center.x, clock_center.y - radius, clock_center.x, clock_center.y + radius); + + g.setColor(g.theme.fg); + let ticks = [0, 90, 180, 270]; + ticks.forEach((item)=>{ + let agl = item+180; + g.drawImage(tick0.asImage(), rotate_around_x(big_wheel_x(item), agl, tick0), rotate_around_y(big_wheel_y(item), agl, tick0), {rotate:agl*p180}); + }); + ticks = [30, 60, 120, 150, 210, 240, 300, 330]; + ticks.forEach((item)=>{ + let agl = item+180; + g.drawImage(tick5.asImage(), rotate_around_x(big_wheel_x(item), agl, tick5), rotate_around_y(big_wheel_y(item), agl, tick5), {rotate:agl*p180}); + }); + + let hour_agl = hour_angle(date); + let minute_agl = minute_angle(date); + g.drawImage(hour_hand, hour_pos_x(hour_agl), hour_pos_y(hour_agl), {rotate:hour_agl*p180}); // + g.drawImage(minute_hand, minute_pos_x(minute_agl), minute_pos_y(minute_agl), {rotate:minute_agl*p180}); // + g.setColor(g.theme.fg); + g.fillCircle(clock_center.x, clock_center.y, 6); + g.setColor(g.theme.bg); + g.fillCircle(clock_center.x, clock_center.y, 3); + + // draw minute ticks. Takes long time to draw! + g.setColor(g.theme.fg); + for (var i=0; i<60; i++){ + let agl = i*6+180; + g.drawImage(tick1.asImage(), rotate_around_x(big_wheel_x(i*6), agl, tick1), rotate_around_y(big_wheel_y(i*6), agl, tick1), {rotate:agl*p180}); + } + + g.flip(); + //console.log(date); +} +function clearTimers(){ + //console.log("clearTimers"); + if(intervalRef) { + clearInterval(intervalRef); + intervalRef = null; + //console.log("interval is cleared"); + } +} +function startTimers(){ + //console.log("startTimers"); + if(intervalRef) clearTimers(); + intervalRef = setInterval(draw_clock, 60*1000); + //console.log("interval is set"); + draw_clock(); +} + +Bangle.on('lcdPower', (on) => { + if (on) { + //console.log("lcdPower: on"); + Bangle.drawWidgets(); + startTimers(); + } else { + //console.log("lcdPower: off"); + clearTimers(); + } +}); +Bangle.on('faceUp',function(up){ + //console.log("faceUp: " + up + " LCD: " + Bangle.isLCDOn()); + if (up && !Bangle.isLCDOn()) { + //console.log("faceUp and LCD off"); + clearTimers(); + Bangle.setLCDPower(true); + } +}); + +g.clear(); + + + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +startTimers(); +// Show launcher when button pressed +Bangle.setUI("clock"); diff --git a/apps/crowclk/crow_clock.png b/apps/crowclk/crow_clock.png new file mode 100644 index 000000000..8d3f61786 Binary files /dev/null and b/apps/crowclk/crow_clock.png differ diff --git a/apps/crowclk/metadata.json b/apps/crowclk/metadata.json new file mode 100644 index 000000000..365393e6c --- /dev/null +++ b/apps/crowclk/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "crowclk", + "name": "Crow Clock", + "version": "0.01", + "description": "A simple clock based on Bold Clock that has MST3K's Crow T. Robot for a face", + "icon": "crow_clock.png", + "screenshots": [{"url":"screenshot_crow.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"crowclk.app.js","url":"crow_clock.js"}, + {"name":"crowclk.img","url":"crow_clock-icon.js","evaluate":true} + ] +} diff --git a/apps/crowclk/screenshot_crow.png b/apps/crowclk/screenshot_crow.png new file mode 100644 index 000000000..49c59c560 Binary files /dev/null and b/apps/crowclk/screenshot_crow.png differ diff --git a/apps/cscsensor/metadata.json b/apps/cscsensor/metadata.json new file mode 100644 index 000000000..af338c59e --- /dev/null +++ b/apps/cscsensor/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "cscsensor", + "name": "Cycling speed sensor", + "shortName": "CSCSensor", + "version": "0.06", + "description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch", + "icon": "icons8-cycling-48.png", + "tags": "outdoors,exercise,ble,bluetooth", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"cscsensor.app.js","url":"cscsensor.app.js"}, + {"name":"cscsensor.settings.js","url":"settings.js"}, + {"name":"cscsensor.img","url":"cscsensor-icon.js","evaluate":true} + ] +} diff --git a/apps/ctrclk/metadata.json b/apps/ctrclk/metadata.json new file mode 100644 index 000000000..d539cff11 --- /dev/null +++ b/apps/ctrclk/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "ctrclk", + "name": "Centerclock", + "version": "0.03", + "description": "Watch-centered digital 24h clock with date in dd.mm.yyyy format.", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "screenshots": [{"url":"bangle1-center-clock-screenshot.png"}], + "allow_emulator": true, + "storage": [ + {"name":"ctrclk.app.js","url":"app.js"}, + {"name":"ctrclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/cubescramble/metadata.json b/apps/cubescramble/metadata.json new file mode 100644 index 000000000..0da0b38a1 --- /dev/null +++ b/apps/cubescramble/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "cubescramble", + "name": "Cube Scramble", + "version":"0.04", + "description": "A random scramble generator for the 3x3 Rubik's cube with a basic timer", + "icon": "cube-scramble.png", + "tags": "", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "screenshots": [{"url":"bangle2-cube-scramble-screenshot.png"},{"url":"bangle1-cube-scramble-screenshot.png"}], + "storage": [ + {"name":"cubescramble.app.js","url":"cube-scramble.js"}, + {"name":"cubescramble.img","url":"cube-scramble-icon.js","evaluate":true} + ] +} diff --git a/apps/custom/metadata.json b/apps/custom/metadata.json new file mode 100644 index 000000000..3c2478f52 --- /dev/null +++ b/apps/custom/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "custom", + "name": "Custom Boot Code ", + "version": "0.01", + "description": "Add code you want to run at boot time", + "icon": "custom.png", + "type": "bootloader", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "custom": "custom.html", + "storage": [ + {"name":"custom"} + ] +} diff --git a/apps/dane/metadata.json b/apps/dane/metadata.json new file mode 100644 index 000000000..64f66cca0 --- /dev/null +++ b/apps/dane/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "dane", + "name": "Digital Assistant, not EDITH", + "shortName": "DANE", + "version": "0.16", + "description": "A Watchface inspired by Tony Stark's EDITH and based on https://arwes.dev/", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "storage": [ + {"name":"dane.app.js","url":"app.js"}, + {"name":"dane.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/dane_tcr/metadata.json b/apps/dane_tcr/metadata.json new file mode 100644 index 000000000..817d0c59b --- /dev/null +++ b/apps/dane_tcr/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "dane_tcr", + "name": "DANE Touch Launcher", + "shortName": "DANE Toucher", + "version": "0.07", + "description": "Touch enable left to right launcher in the style of the DANE Watchface", + "icon": "app.png", + "type": "launch", + "tags": "tool,system,launcher", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"dane_tcr.app.js","url":"app.js"}, + {"name":"dane_tcr.settings.js","url":"settings.js"} + ], + "data": [{"name":"dane_tcr.json"}] +} diff --git a/apps/daysl/app.js b/apps/daysl/app.js index 56f85e615..50695328e 100644 --- a/apps/daysl/app.js +++ b/apps/daysl/app.js @@ -24,12 +24,7 @@ if (!settings) resetSettings(); function showMenu() { const datemenu = { '': { - 'title': 'Set Date', - 'predraw': function() { - datemenu.Date.value = settings.day; - datemenu.Month.value = settings.month; - datemenu.Year.value = settings.year; - } + 'title': 'Set Date' }, 'Day': { value: settings.day, @@ -64,4 +59,4 @@ function showMenu() { return E.showMenu(datemenu); } -showMenu(); \ No newline at end of file +showMenu(); diff --git a/apps/daysl/metadata.json b/apps/daysl/metadata.json new file mode 100644 index 000000000..3dfeea745 --- /dev/null +++ b/apps/daysl/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "daysl", + "name": "Days left", + "version": "0.03", + "description": "Shows you the days left until a certain date. Date can be set with a settings app and is written to a file.", + "icon": "app.png", + "tags": "", + "supports": ["BANGLEJS", "BANGLEJS2"], + "allow_emulator": false, + "storage": [ + {"name":"daysl.app.js","url":"app.js"}, + {"name":"daysl.img","url":"app-icon.js","evaluate":true}, + {"name":"daysl.wid.js","url":"widget.js"} + ] +} diff --git a/apps/dclock/metadata.json b/apps/dclock/metadata.json new file mode 100644 index 000000000..b024ed8c3 --- /dev/null +++ b/apps/dclock/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "dclock", + "name": "Dev Clock", + "version": "0.10", + "description": "A Digital Clock including timestamp (tst), beats(@), days in current month (dm) and days since new moon (l)", + "icon": "clock-dev.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "screenshots": [{"url":"bangle2-dev-clock-screenshot.png"},{"url":"bangle1-dev-clock-screenshot.png"}], + "storage": [ + {"name":"dclock.app.js","url":"clock-dev.js"}, + {"name":"dclock.img","url":"clock-dev-icon.js","evaluate":true} + ] +} diff --git a/apps/de-stress/metadata.json b/apps/de-stress/metadata.json new file mode 100644 index 000000000..47ea3a15d --- /dev/null +++ b/apps/de-stress/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "de-stress", + "name": "De-Stress", + "shortName": "De-Stress", + "version": "0.02", + "description": "Simple haptic heartbeat", + "icon": "app.png", + "tags": "", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"de-stress.app.js","url":"app.js"}, + {"name":"de-stress.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/demoapp/metadata.json b/apps/demoapp/metadata.json new file mode 100644 index 000000000..df6554ef5 --- /dev/null +++ b/apps/demoapp/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "demoapp", + "name": "Demo Loop", + "version": "0.02", + "description": "Simple demo app - displays Bangle.js, JS logo, graphics, and Bangle.js information", + "icon": "app.png", + "type": "app", + "tags": "", + "screenshots": [{"url":"bangle1-demo-loop-screenshot1.png"},{"url":"bangle1-demo-loop-screenshot2.png"},{"url":"bangle1-demo-loop-screenshot3.png"},{"url":"bangle1-demo-loop-screenshot4.png"}], + "supports": ["BANGLEJS"], + "allow_emulator": true, + "storage": [ + {"name":"demoapp.app.js","url":"app.js"}, + {"name":"demoapp.img","url":"app-icon.js","evaluate":true} + ], + "sortorder": -9 +} diff --git a/apps/devstopwatch/ChangeLog b/apps/devstopwatch/ChangeLog index e2b392fe9..7e90e061e 100644 --- a/apps/devstopwatch/ChangeLog +++ b/apps/devstopwatch/ChangeLog @@ -1,3 +1,8 @@ 0.01: App created 0.02: Persist state to storage to enable stopwatch to continue in the background 0.03: Modified to use setUI, theme and different screens +0.04: *bugfix* stopwatch broken with v0.03 setUI + realigned quick n dirty screen positions + help adjusted to fit bangle1 & bangle2 screen-size with widgets + fixed bangle2 colors for chrono and last lap highlight + added screen for bangle2 and a small README \ No newline at end of file diff --git a/apps/devstopwatch/README.md b/apps/devstopwatch/README.md new file mode 100644 index 000000000..02a13151f --- /dev/null +++ b/apps/devstopwatch/README.md @@ -0,0 +1,18 @@ +# dev stop watch + +stores state at kill + +## Bangle 1 +![](bangle1-dev-stopwatch-screenshot.png) + +* BTN1: start/lap +* BTN2: launcher +* BTN3: reset + +## Bangle 2 +![](bangle2-dev-stopwatch-screenshot.png) + +* TAP top right: start/lap +* TAP bottom right: reset +* Use BTN to get to launcher + diff --git a/apps/devstopwatch/app.js b/apps/devstopwatch/app.js index 83bb693a9..d2a4b1117 100644 --- a/apps/devstopwatch/app.js +++ b/apps/devstopwatch/app.js @@ -3,11 +3,11 @@ const EMPTY_H = '00:00:000'; const MAX_LAPS = 6; const XY_CENTER = g.getWidth() / 2; const big = g.getWidth()>200; -const Y_CHRONO = 40; -const Y_HEADER = big?80:60; -const Y_LAPS = big?125:90; +const Y_CHRONO = big?40:30; +const Y_HEADER = big?95:65; +const Y_LAPS = big?125:80; const H_LAPS = big?15:8; -const Y_BTN3 = big?225:165; +const Y_HELP = big?225:135; const FONT = '6x8'; const CHRONO = '/* C H R O N O */'; @@ -27,18 +27,17 @@ var state = require("Storage").readJSON("devstopwatch.state.json",1) || { // Show launcher when button pressed Bangle.setUI("clockupdown", btn=>{ - if (btn==0) { - reset = false; - - if (state.started) { - changeLap(); - } else { - if (!reset) { - chronoInterval = setInterval(chronometer, 10); - } + switch (btn) { + case -1: + if (state.started) { + changeLap(); + } else { + chronoInterval = setInterval(chronometer, 10); + } + break; + case 1: resetChrono(); break; + default: Bangle.showLauncher(); break; //launcher handeled by ROM } -} - if (btn==1) resetChrono(); }); function resetChrono() { @@ -105,6 +104,7 @@ function printChrono() { var print = ''; + g.setColor(g.theme.fg); g.setFont(FONT, big?2:1); print = CHRONO; g.drawString(print, XY_CENTER, Y_CHRONO, true); @@ -124,7 +124,8 @@ function printChrono() { let suffix = ' '; if (state.currentLapIndex === i) { let suffix = '*'; - g.setColor("#f70"); + if (process.env.HWVERSION==2) g.setColor("#0ee"); + else g.setColor("#f70"); } const lapLine = `L${i - 1} ${state.laps[i]} ${suffix}\n`; @@ -133,8 +134,17 @@ function printChrono() { g.setColor(g.theme.fg); g.setFont(FONT, 1); - print = 'Press 3 to reset'; - g.drawString(print, XY_CENTER, Y_BTN3, true); + //help for model 2 or 1 + if (process.env.HWVERSION==2) { + print = /*LANG*/'TAP right top/bottom'; + g.drawString(print, XY_CENTER, Y_HELP, true); + print = /*LANG*/'start&lap/reset, BTN1: EXIT'; + g.drawString(print, XY_CENTER, Y_HELP+10, true); + } + else { + print = /*LANG*/'BTNs 1:startlap 2:exit 3:reset'; + g.drawString(print, XY_CENTER, Y_HELP, true); + } g.flip(); } diff --git a/apps/devstopwatch/bangle1-dev-stopwatch-screenshot.png b/apps/devstopwatch/bangle1-dev-stopwatch-screenshot.png index b668794b1..8a9c9b46e 100644 Binary files a/apps/devstopwatch/bangle1-dev-stopwatch-screenshot.png and b/apps/devstopwatch/bangle1-dev-stopwatch-screenshot.png differ diff --git a/apps/devstopwatch/bangle2-dev-stopwatch-screenshot.png b/apps/devstopwatch/bangle2-dev-stopwatch-screenshot.png new file mode 100644 index 000000000..a01c0c261 Binary files /dev/null and b/apps/devstopwatch/bangle2-dev-stopwatch-screenshot.png differ diff --git a/apps/devstopwatch/metadata.json b/apps/devstopwatch/metadata.json new file mode 100644 index 000000000..c4b6c7a67 --- /dev/null +++ b/apps/devstopwatch/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "devstopwatch", + "name": "Dev Stopwatch", + "shortName": "Dev Stopwatch", + "version": "0.04", + "description": "Stopwatch with 5 laps supported (cyclically replaced)", + "icon": "app.png", + "tags": "stopwatch,chrono,timer,chronometer", + "supports": ["BANGLEJS","BANGLEJS2"], + "screenshots": [{"url":"bangle1-dev-stopwatch-screenshot.png"},{"url":"bangle2-dev-stopwatch-screenshot.png"}], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"devstopwatch.app.js","url":"app.js"}, + {"name":"devstopwatch.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/digiclock/metadata.json b/apps/digiclock/metadata.json new file mode 100644 index 000000000..55034e678 --- /dev/null +++ b/apps/digiclock/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "digiclock", + "name": "Digital Clock Face", + "shortName": "Digi Clock", + "version": "0.02", + "description": "A simple digital clock with the time, day, month, and year", + "icon": "digiclock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"digiclock.app.js","url":"digiclock.js"}, + {"name":"digiclock.img","url":"digiclock-icon.js","evaluate":true} + ] +} diff --git a/apps/diract/ChangeLog b/apps/diract/ChangeLog index 5560f00bc..34fc73a76 100644 --- a/apps/diract/ChangeLog +++ b/apps/diract/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Tweaked proximity identification settings diff --git a/apps/diract/README.md b/apps/diract/README.md index efecade3f..49e6e7add 100644 --- a/apps/diract/README.md +++ b/apps/diract/README.md @@ -5,7 +5,7 @@ ## Usage -Real-time interactions will be recognised by [Pareto Anywhere](https://www.reelyactive.com/pareto/anywhere/) open source middleware and any other program which observes the [DirAct open standard](https://reelyactive.github.io/diract/). +Real-time interactions will be recognised by [Pareto Anywhere](https://www.reelyactive.com/pareto/anywhere/) open source middleware and any other program which observes the [DirAct open standard](https://reelyactive.github.io/diract/). See our [Bangle.js Development Guide](https://reelyactive.github.io/diy/banglejs-dev/) for details. ## Features diff --git a/apps/diract/diract.js b/apps/diract/diract.js index dba41cccb..69f0a88e4 100644 --- a/apps/diract/diract.js +++ b/apps/diract/diract.js @@ -1,5 +1,5 @@ /** - * Copyright reelyActive 2017-2021 + * Copyright reelyActive 2017-2022 * We believe in an open Internet of Things * * DirAct is jointly developed by reelyActive and Code Blue Consulting @@ -11,14 +11,14 @@ const NAMESPACE_FILTER_ID = [ 0xc0, 0xde, 0xb1, 0x0e, 0x1d, 0xd1, 0xe0, 0x1b, 0xed, 0x0c ]; const EXCITER_INSTANCE_IDS = new Uint32Array([ 0xe8c17e45 ]); const RESETTER_INSTANCE_IDS = new Uint32Array([ 0x4e5e77e4 ]); -const PROXIMITY_RSSI_THRESHOLD = -65; -const PROXIMITY_LED_RSSI_THRESHOLD = -65; +const PROXIMITY_RSSI_THRESHOLD = -85; +const PROXIMITY_LED_RSSI_THRESHOLD = -85; const PROXIMITY_TABLE_SIZE = 8; const DIGEST_TABLE_SIZE = 32; const OBSERVE_PERIOD_MILLISECONDS = 400; -const BROADCAST_PERIOD_MILLISECONDS = 3600; +const BROADCAST_PERIOD_MILLISECONDS = 1600; const BROADCAST_DIGEST_PAGE_MILLISECONDS = 400; -const PROXIMITY_PACKET_INTERVAL_MILLISECONDS = 400; +const PROXIMITY_PACKET_INTERVAL_MILLISECONDS = 200; const DIGEST_PACKET_INTERVAL_MILLISECONDS = 100; const DIGEST_TIME_CYCLE_THRESHOLD = 86400; const EXCITER_HOLDOFF_SECONDS = 60; diff --git a/apps/diract/metadata.json b/apps/diract/metadata.json new file mode 100644 index 000000000..af9406e91 --- /dev/null +++ b/apps/diract/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "diract", + "name": "DirAct", + "shortName": "DirAct", + "version": "0.02", + "description": "Proximity interaction detection.", + "icon": "diract.png", + "type": "app", + "tags": "tool,sensors", + "supports" : [ "BANGLEJS2" ], + "allow_emulator": false, + "readme": "README.md", + "storage": [ + { "name": "diract.app.js", "url": "diract.js" }, + { "name": "diract.img", "url": "diract-icon.js", "evaluate": true } + ] +} diff --git a/apps/dotclock/metadata.json b/apps/dotclock/metadata.json new file mode 100644 index 000000000..396e63917 --- /dev/null +++ b/apps/dotclock/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "dotclock", + "name": "Dot Clock", + "version": "0.03", + "description": "A Minimal Dot Analog Clock", + "icon": "clock-dot.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "screenshots": [{"url":"bangle2-dot-clcok-screenshot.png"},{"url":"bangle1-dot-clock-screenshot.png"}], + "storage": [ + {"name":"dotclock.app.js","url":"clock-dot.js"}, + {"name":"dotclock.img","url":"clock-dot-icon.js","evaluate":true} + ] +} diff --git a/apps/dotmatrixclock/metadata.json b/apps/dotmatrixclock/metadata.json new file mode 100644 index 000000000..3425dc1b2 --- /dev/null +++ b/apps/dotmatrixclock/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "dotmatrixclock", + "name": "Dotmatrix Clock", + "version": "0.01", + "description": "A clear white-on-blue dotmatrix simulated clock", + "icon": "dotmatrixclock.png", + "type": "clock", + "tags": "clock,dotmatrix,retro", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"dotmatrixclock.app.js","url":"app.js"}, + {"name":"dotmatrixclock.img","url":"dotmatrixclock-icon.js","evaluate":true} + ] +} diff --git a/apps/doztime/README.md b/apps/doztime/README.md index 075b2f66a..2f5b04780 100644 --- a/apps/doztime/README.md +++ b/apps/doztime/README.md @@ -11,4 +11,4 @@ The year itself begins on the December solstice. Because that always happens, th The epoch (year numbering) begins in the last year when the perihelion coincided with the June solstice, near the beginning of the Holocene era. That astronomical basis makes the calendar free from politics, religion, or geography. -While the year number remains cardinal, BTN5 toggles between cardinal and ordinal for the rest of the calendar segments. BTN4 adds or removes a quickly changing digit to or from the clock. +While the year number remains cardinal, tapping on the right side of the watch face toggles between cardinal and ordinal for the rest of the calendar segments. Tapping on the left adds or removes a quickly changing digit to or from the clock. diff --git a/apps/doztime/metadata.json b/apps/doztime/metadata.json new file mode 100644 index 000000000..d206cb0c3 --- /dev/null +++ b/apps/doztime/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "doztime", + "name": "Dozenal Time", + "shortName": "Dozenal Time", + "version": "0.04", + "description": "A dozenal Holocene calendar and dozenal diurnal clock", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"doztime.app.js","url":"app-bangle1.js","supports":["BANGLEJS"]}, + {"name":"doztime.app.js","url":"app-bangle2.js","supports":["BANGLEJS2"]}, + {"name":"doztime.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/dsdrelay/metadata.json b/apps/dsdrelay/metadata.json new file mode 100644 index 000000000..1d043ba4a --- /dev/null +++ b/apps/dsdrelay/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "dsdrelay", + "name": "DSD BLE Relay controller", + "shortName": "DSDRelay", + "version": "0.01", + "description": "Control BLE relay board from the watch", + "icon": "icons8-relay-48.png", + "tags": "ble,bluetooth", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"dsdrelay.app.js","url":"dsdrelay.app.js"}, + {"name":"dsdrelay.img","url":"dsdrelay-icon.js","evaluate":true} + ] +} diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json new file mode 100644 index 000000000..8ff5bd592 --- /dev/null +++ b/apps/dtlaunch/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "dtlaunch", + "name": "Desktop Launcher", + "version": "0.07", + "description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.", + "screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}], + "icon": "icon.png", + "type": "launch", + "tags": "tool,system,launcher", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"dtlaunch.app.js","url":"app-b1.js", "supports": ["BANGLEJS"]}, + {"name":"dtlaunch.app.js","url":"app-b2.js", "supports": ["BANGLEJS2"]}, + {"name":"dtlaunch.settings.js","url":"settings-b1.js", "supports": ["BANGLEJS"]}, + {"name":"dtlaunch.settings.js","url":"settings-b2.js", "supports": ["BANGLEJS2"]}, + {"name":"dtlaunch.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"dtlaunch.json"}] +} diff --git a/apps/edisonsball/metadata.json b/apps/edisonsball/metadata.json new file mode 100644 index 000000000..f429c7b67 --- /dev/null +++ b/apps/edisonsball/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "edisonsball", + "name": "Edison's Ball", + "shortName": "Edison's Ball", + "version": "0.01", + "description": "Hypnagogia/Micro-Sleep alarm for experimental use in exploring sleep transition and combating drowsiness", + "icon": "app-icon.png", + "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"edisonsball.app.js","url":"app.js"}, + {"name":"edisonsball.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/emojuino/metadata.json b/apps/emojuino/metadata.json new file mode 100644 index 000000000..05d32f186 --- /dev/null +++ b/apps/emojuino/metadata.json @@ -0,0 +1,22 @@ +{ + "id": "emojuino", + "name": "Emojuino", + "shortName": "Emojuino", + "version": "0.03", + "description": "Emojis & Espruino: broadcast Unicode emojis via Bluetooth Low Energy.", + "icon": "emojuino.png", + "screenshots": [ + { "url": "screenshot-tx.png" }, + { "url": "screenshot-swipe.png" }, + { "url": "screenshot-welcome.png" } + ], + "type": "app", + "tags": "emoji", + "supports" : [ "BANGLEJS2" ], + "allow_emulator": true, + "readme": "README.md", + "storage": [ + { "name": "emojuino.app.js", "url": "emojuino.js" }, + { "name": "emojuino.img", "url": "emojuino-icon.js", "evaluate": true } + ] +} diff --git a/apps/espruinoctrl/metadata.json b/apps/espruinoctrl/metadata.json new file mode 100644 index 000000000..5798c7842 --- /dev/null +++ b/apps/espruinoctrl/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "espruinoctrl", + "name": "Espruino Control", + "shortName": "Espruino Ctrl", + "version": "0.01", + "description": "Send commands to other Espruino devices via the Bluetooth UART interface. Customisable commands!", + "icon": "app.png", + "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", + "custom": "custom.html", + "storage": [ + {"name":"espruinoctrl.app.js"}, + {"name":"espruinoctrl.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/fclock/metadata.json b/apps/fclock/metadata.json new file mode 100644 index 000000000..da553e110 --- /dev/null +++ b/apps/fclock/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "fclock", + "name": "fclock", + "shortName": "F Clock", + "version": "0.02", + "description": "Simple design of a digital clock", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"fclock.app.js","url":"fclock.app.js"}, + {"name":"fclock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/fd6fdetect/ChangeLog b/apps/fd6fdetect/ChangeLog index b85df5ace..2ab32d15a 100644 --- a/apps/fd6fdetect/ChangeLog +++ b/apps/fd6fdetect/ChangeLog @@ -1,2 +1,2 @@ -0.1: Added source code -0.2: Added a README file +0.01: Added source code +0.02: Added a README file diff --git a/apps/fd6fdetect/metadata.json b/apps/fd6fdetect/metadata.json new file mode 100644 index 000000000..d795ef8da --- /dev/null +++ b/apps/fd6fdetect/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "fd6fdetect", + "name": "fd6fdetect", + "shortName": "fd6fdetect", + "version": "0.02", + "description": "Allows you to see 0xFD6F beacons near you.", + "icon": "app.png", + "tags": "tool", + "readme": "README.md", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"fd6fdetect.app.js","url":"app.js"}, + {"name":"fd6fdetect.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/ffcniftya/ChangeLog b/apps/ffcniftya/ChangeLog index 18bc264a3..420c553f5 100644 --- a/apps/ffcniftya/ChangeLog +++ b/apps/ffcniftya/ChangeLog @@ -1 +1,2 @@ 0.01: New Clock Nifty A +0.02: Shows the current week number (ISO8601), can be disabled via settings "" diff --git a/apps/ffcniftya/README.md b/apps/ffcniftya/README.md index f1fee9b1f..86f1f5c2d 100644 --- a/apps/ffcniftya/README.md +++ b/apps/ffcniftya/README.md @@ -1,4 +1,14 @@ # Nifty-A Clock +Colors are black/white - photos have non correct camera color "blue" + +## This is the clock + ![](screenshot_nifty.png) +## The week number (ISO8601) can be turned of in settings +(default is **"On"**) + +![](screenshot_settings_nifty.png) + + diff --git a/apps/ffcniftya/app.js b/apps/ffcniftya/app.js index 31742f64a..5da1ec48e 100644 --- a/apps/ffcniftya/app.js +++ b/apps/ffcniftya/app.js @@ -1,5 +1,6 @@ const locale = require("locale"); const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; +const CFG = require('Storage').readJSON("ffcniftya.json", 1) || {showWeekNum: true}; /* Clock *********************************************/ const scale = g.getWidth() / 176; @@ -16,6 +17,18 @@ const center = { y: Math.round(((viewport.height - widget) / 2) + widget), } +function ISO8601_week_no(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 + var tdt = new Date(date.valueOf()); + var dayn = (date.getDay() + 6) % 7; + tdt.setDate(tdt.getDate() - dayn + 3); + var firstThursday = tdt.valueOf(); + tdt.setMonth(0, 1); + if (tdt.getDay() !== 4) { + tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7); + } + return 1 + Math.ceil((firstThursday - tdt) / 604800000); +} + function d02(value) { return ('0' + value).substr(-2); } @@ -29,23 +42,26 @@ function draw() { const minutes = d02(now.getMinutes()); const day = d02(now.getDate()); const month = d02(now.getMonth() + 1); - const year = now.getFullYear(); - - const month2 = locale.month(now, 3); - const day2 = locale.dow(now, 3); + const year = now.getFullYear(now); + const weekNum = d02(ISO8601_week_no(now)); + const monthName = locale.month(now, 3); + const dayName = locale.dow(now, 3); + const centerTimeScaleX = center.x + 32 * scale; g.setFontAlign(1, 0).setFont("Vector", 90 * scale); - g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale); - g.drawString(minutes, center.x + 32 * scale, center.y + 46 * scale); + g.drawString(hour, centerTimeScaleX, center.y - 31 * scale); + g.drawString(minutes, centerTimeScaleX, center.y + 46 * scale); g.fillRect(center.x + 30 * scale, center.y - 72 * scale, center.x + 32 * scale, center.y + 74 * scale); + const centerDatesScaleX = center.x + 40 * scale; g.setFontAlign(-1, 0).setFont("Vector", 16 * scale); - g.drawString(year, center.x + 40 * scale, center.y - 62 * scale); - g.drawString(month, center.x + 40 * scale, center.y - 44 * scale); - g.drawString(day, center.x + 40 * scale, center.y - 26 * scale); - g.drawString(month2, center.x + 40 * scale, center.y + 48 * scale); - g.drawString(day2, center.x + 40 * scale, center.y + 66 * scale); + g.drawString(year, centerDatesScaleX, center.y - 62 * scale); + g.drawString(month, centerDatesScaleX, center.y - 44 * scale); + g.drawString(day, centerDatesScaleX, center.y - 26 * scale); + if (CFG.showWeekNum) g.drawString(d02(ISO8601_week_no(now)), centerDatesScaleX, center.y + 15 * scale); + g.drawString(monthName, centerDatesScaleX, center.y + 48 * scale); + g.drawString(dayName, centerDatesScaleX, center.y + 66 * scale); } diff --git a/apps/ffcniftya/metadata.json b/apps/ffcniftya/metadata.json new file mode 100644 index 000000000..ce91cc225 --- /dev/null +++ b/apps/ffcniftya/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "ffcniftya", + "name": "Nifty-A Clock", + "version": "0.02", + "description": "A nifty clock with time and date", + "icon": "app.png", + "screenshots": [{"url":"screenshot_nifty.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"ffcniftya.app.js","url":"app.js"}, + {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true}, + {"name":"ffcniftya.settings.js","url":"settings.js"} + ], + "data": [{"name":"ffcniftya.json"}] +} diff --git a/apps/ffcniftya/screenshot_nifty.png b/apps/ffcniftya/screenshot_nifty.png index 0df056223..de939f6ba 100644 Binary files a/apps/ffcniftya/screenshot_nifty.png and b/apps/ffcniftya/screenshot_nifty.png differ diff --git a/apps/ffcniftya/screenshot_settings_nifty.png b/apps/ffcniftya/screenshot_settings_nifty.png new file mode 100644 index 000000000..b81a4662c Binary files /dev/null and b/apps/ffcniftya/screenshot_settings_nifty.png differ diff --git a/apps/ffcniftya/settings.js b/apps/ffcniftya/settings.js new file mode 100644 index 000000000..46e4ef5aa --- /dev/null +++ b/apps/ffcniftya/settings.js @@ -0,0 +1,23 @@ +(function(back) { + var FILE = "ffcniftya.json"; + // Load settings + var cfg = require('Storage').readJSON(FILE, 1) || { showWeekNum: true }; + + function writeSettings() { + require('Storage').writeJSON(FILE, cfg); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "Nifty-A Clock" }, + "< Back" : () => back(), + 'week number?': { + value: cfg.showWeekNum, + format: v => v?"On":"Off", + onchange: v => { + cfg.showWeekNum = v; + writeSettings(); + } + } + }); +}) \ No newline at end of file diff --git a/apps/ffcniftyb/metadata.json b/apps/ffcniftyb/metadata.json new file mode 100644 index 000000000..e4e099a51 --- /dev/null +++ b/apps/ffcniftyb/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "ffcniftyb", + "name": "Nifty-B Clock", + "version": "0.02", + "description": "A nifty clock (series B) with time, date and color configuration", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"ffcniftyb.app.js","url":"app.js"}, + {"name":"ffcniftyb.img","url":"app-icon.js","evaluate":true}, + {"name":"ffcniftyb.settings.js","url":"settings.js"} + ], + "data": [{"name":"ffcniftyb.json"}] +} diff --git a/apps/fileman/metadata.json b/apps/fileman/metadata.json new file mode 100644 index 000000000..f5589e396 --- /dev/null +++ b/apps/fileman/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "fileman", + "name": "File manager", + "shortName": "FileManager", + "version": "0.03", + "description": "Simple file manager, allows user to examine watch storage and display, load or delete individual files", + "icon": "icons8-filing-cabinet-48.png", + "tags": "tools", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"fileman.app.js","url":"fileman.app.js"}, + {"name":"fileman.img","url":"fileman-icon.js","evaluate":true} + ] +} diff --git a/apps/files/metadata.json b/apps/files/metadata.json new file mode 100644 index 000000000..ac73a7717 --- /dev/null +++ b/apps/files/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "files", + "name": "App Manager", + "version": "0.07", + "description": "Show currently installed apps, free space, and allow their deletion from the watch", + "icon": "files.png", + "tags": "tool,system,files", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"files.app.js","url":"files.js"}, + {"name":"files.img","url":"files-icon.js","evaluate":true} + ] +} diff --git a/apps/findphone/metadata.json b/apps/findphone/metadata.json new file mode 100644 index 000000000..d67c6ec93 --- /dev/null +++ b/apps/findphone/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "findphone", + "name": "Find Phone", + "shortName": "Find Phone", + "version": "0.03", + "description": "Find your phone via Gadgetbridge. Click any button to let your phone ring. 📳 Note: The functionality is available even without this app, just go to Settings, App Settings, Gadgetbridge, Find Phone.", + "icon": "app.png", + "tags": "tool,android", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"findphone.app.js","url":"app.js"}, + {"name":"findphone.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/flagrse/metadata.json b/apps/flagrse/metadata.json new file mode 100644 index 000000000..b57971a92 --- /dev/null +++ b/apps/flagrse/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "flagrse", + "name": "Espruino Flag Raiser", + "version": "0.01", + "description": "App to send a command to another Espruino to cause it to raise a flag", + "icon": "app.png", + "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"flagrse.app.js","url":"app.js"}, + {"name":"flagrse.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/flappy/metadata.json b/apps/flappy/metadata.json new file mode 100644 index 000000000..910797066 --- /dev/null +++ b/apps/flappy/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "flappy", + "name": "Flappy Bird", + "version": "0.05", + "description": "A Flappy Bird game clone", + "icon": "app.png", + "screenshots": [{"url":"screenshot1_flappy.png"},{"url":"screenshot2_flappy.png"}], + "tags": "game", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"flappy.app.js","url":"app.js"}, + {"name":"flappy.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/flipper/ChangeLog b/apps/flipper/ChangeLog new file mode 100644 index 000000000..9db0e26c5 --- /dev/null +++ b/apps/flipper/ChangeLog @@ -0,0 +1 @@ +0.01: first release diff --git a/apps/flipper/README.md b/apps/flipper/README.md new file mode 100644 index 000000000..88025b8b2 --- /dev/null +++ b/apps/flipper/README.md @@ -0,0 +1,20 @@ +# Flipper + +![](flipper.png) + + *A utility to switch from the dark to the light theme and vice versa* + +* If the current theme is dark it will switch to the light theme +* If the current theme is light it will switch to the dark theme +* Combine with the awesome pattern launcher and it saves loads of time + + +## Demo Video + +There's no screenshot but there is a [demo video.](https://espruino.microco.sm/api/v1/files/9caa1afef7e4cce1d9b518af2dd271f1a57c5ecc.mp4) + +## Future Enhancements + +* Nothing left to add + +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/flipper/flipper.app.js b/apps/flipper/flipper.app.js new file mode 100644 index 000000000..7171306b1 --- /dev/null +++ b/apps/flipper/flipper.app.js @@ -0,0 +1,39 @@ +const storage = require('Storage'); +let settings = storage.readJSON('setting.json', 1); + +function cl(x) { return g.setColor(x).getColor(); } + +function upd(th) { + g.theme = th; + settings.theme = th; + storage.write('setting.json', settings); + delete g.reset; + g._reset = g.reset; + g.reset = function(n) { return g._reset().setColor(th.fg).setBgColor(th.bg); }; + g.clear = function(n) { if (n) g.reset(); return g.clearRect(0,0,g.getWidth(),g.getHeight()); }; + g.clear(1); +} + +function flipTheme() { + if (!g.theme.dark) { + upd({ + fg:cl("#fff"), bg:cl("#000"), + fg2:cl("#0ff"), bg2:cl("#000"), + fgH:cl("#fff"), bgH:cl("#00f"), + dark:true + }); + } else { + upd({ + fg:cl("#000"), bg:cl("#fff"), + fg2:cl("#000"), bg2:cl("#cff"), + fgH:cl("#000"), bgH:cl("#0ff"), + dark:false + }); + } +} + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +flipTheme(); +setTimeout(load, 20); diff --git a/apps/flipper/flipper.icon.js b/apps/flipper/flipper.icon.js new file mode 100644 index 000000000..494072c3c --- /dev/null +++ b/apps/flipper/flipper.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4X/AAO/mMUzs975K+ggLKysUAYNVqoLFitUoAKBqtQBYkJBIQABqwLEgQLEqtABggJDqkVBaoNCBZQwEgILWgoJENYsVBIcVBYpDEgpSIBYMBKQg6CuogCBY1UgoLCXAQLDqAsDBYhSBqEJHAoLDoEBcQ4LBEwILIMooLdIg4LaVoyaGERLcFao4LIdRAACYYUQBY5RKAH4Ar")) diff --git a/apps/flipper/flipper.png b/apps/flipper/flipper.png new file mode 100644 index 000000000..b91543070 Binary files /dev/null and b/apps/flipper/flipper.png differ diff --git a/apps/flipper/metadata.json b/apps/flipper/metadata.json new file mode 100644 index 000000000..aac4f1643 --- /dev/null +++ b/apps/flipper/metadata.json @@ -0,0 +1,18 @@ + +{ + "id": "flipper", + "name": "flipper", + "version": "0.01", + "description": "Switch between dark and light theme and vice versa, combine with pattern launcher and swipe to flip.", + "readme":"README.md", + "screenshots": [{"url":"flipper.png"}], + "icon": "flipper.png", + "type": "app", + "tags": "game", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"flipper.app.js","url":"flipper.app.js"}, + {"name":"flipper.img","url":"flipper.icon.js","evaluate":true} + ] +} diff --git a/apps/floralclk/metadata.json b/apps/floralclk/metadata.json new file mode 100644 index 000000000..43323abaa --- /dev/null +++ b/apps/floralclk/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "floralclk", + "name": "Floral Clock", + "version": "0.01", + "description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: Works on any Bangle.js 2 but requires firmware 2v11 or later on Bangle.js 1**", + "icon": "app.png", + "screenshots": [{"url":"screenshot_floral.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"floralclk.app.js","url":"app.js"}, + {"name":"floralclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/flow/metadata.json b/apps/flow/metadata.json new file mode 100644 index 000000000..cbb81082d --- /dev/null +++ b/apps/flow/metadata.json @@ -0,0 +1,15 @@ +{ + "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 } + ] +} diff --git a/apps/fontclock/metadata.json b/apps/fontclock/metadata.json new file mode 100644 index 000000000..4f4875e51 --- /dev/null +++ b/apps/fontclock/metadata.json @@ -0,0 +1,28 @@ +{ + "id": "fontclock", + "name": "Font Clock", + "version": "0.01", + "description": "Choose the font and design of clock face from a library of available designs", + "icon": "fontclock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "custom": "custom.html", + "allow_emulator": false, + "storage": [ + {"name":"fontclock.app.js","url":"fontclock.js"}, + {"name":"fontclock.img","url":"fontclock-icon.js","evaluate":true}, + {"name":"fontclock.hand.js","url":"fontclock.hand.js"}, + {"name":"fontclock.thinhand.js","url":"fontclock.thinhand.js"}, + {"name":"fontclock.thickhand.js","url":"fontclock.thickhand.js"}, + {"name":"fontclock.hourscriber.js","url":"fontclock.hourscriber.js"}, + {"name":"fontclock.font.js","url":"fontclock.font.js"}, + {"name":"fontclock.font.abril_ff50.js","url":"fontclock.font.abril_ff50.js"}, + {"name":"fontclock.font.cpstc58.js","url":"fontclock.font.cpstc58.js"}, + {"name":"fontclock.font.mntn25.js","url":"fontclock.font.mntn25.js"}, + {"name":"fontclock.font.mntn50.js","url":"fontclock.font.mntn50.js"}, + {"name":"fontclock.font.vector25.js","url":"fontclock.font.vector25.js"}, + {"name":"fontclock.font.vector50.js","url":"fontclock.font.vector50.js"} + ] +} diff --git a/apps/ftclock/.gitignore b/apps/ftclock/.gitignore new file mode 100644 index 000000000..b384cf1f2 --- /dev/null +++ b/apps/ftclock/.gitignore @@ -0,0 +1,4 @@ +timezonedb.csv.zip +country.csv +zone.csv +timezone.csv diff --git a/apps/ftclock/ChangeLog b/apps/ftclock/ChangeLog new file mode 100644 index 000000000..c944dd9ac --- /dev/null +++ b/apps/ftclock/ChangeLog @@ -0,0 +1,2 @@ +0.01: first release +0.02: RAM efficient version of `fourTwentyTz.js` (as suggested by @gfwilliams). diff --git a/apps/ftclock/README.md b/apps/ftclock/README.md new file mode 100644 index 000000000..f30151552 --- /dev/null +++ b/apps/ftclock/README.md @@ -0,0 +1,24 @@ +# Four Twenty Clock + +A clock that tells when and where it's going to be [4:20](https://en.wikipedia.org/wiki/420_%28cannabis_culture%29) next + +![screensot](screenshot.png) ![screenshot at 4:20](screenshot1.png) + +## Generating `fourTwentyTz.js` + +Once in a while we need to regenerate it for 2 reasons: + +* One or more places got in or out of daylight saving time (DST) mode. +* The database saying _when_ places enter/exit DST mode got updated. + +I'll do my best to release a new version every time this happens, +but if you ever need to do this yourself, here's how: + +* `cd` to the `ftclock` folder +* If you haven't done so yet, run `npm install` there (this would create the `node_modules` folder). +* Get and unzip the latest `timezone.csv.zip` from https://timezonedb.com/download +* Run `npm run make` + +## Creator + +[Nimrod Kerrett](zzzen.com) diff --git a/apps/ftclock/app-icon.js b/apps/ftclock/app-icon.js new file mode 100644 index 000000000..297847e95 --- /dev/null +++ b/apps/ftclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwghC/AH4A/AH4A/AAMHu4ACuwHBs4HDsEGBIQLCsADBgwPDCAQGEuwXFBwI0GEAMHuAGCCoMHC4pMHEAIXEAgIGEBwI9BC4wSCC8IVCMAwIBs4XKUQJfITQgXCDwp8EHAqaECoLFEu4cDBIggBs6uFZozuGBAVmC4g+FMgZQEZQ5vGC4iRIC5IrDN4h5EC5J3BCoIKGgyaEC44VBC46yEDgoeDgxqLC5SCMAgoTFY47GFC4xFBdwwPBD4oWFAH4A/AH4A/AH4AjA==")) diff --git a/apps/ftclock/app.js b/apps/ftclock/app.js new file mode 100644 index 000000000..b12db10f1 --- /dev/null +++ b/apps/ftclock/app.js @@ -0,0 +1,51 @@ +let getNextFourTwenty = require("fourTwenty").getNextFourTwenty; +require("FontTeletext10x18Ascii").add(Graphics); +let leaf_img = "\x17\x18\x81\x00\x00\x10\x00\x00 \x00\x00@\x00\x01\xc0\x00\x03\x80\x00\x0f\x80\x00\x1f\x00\x00>\x00\x00|\x00\xc0\xf8\x19\xe1\xf0\xf1\xe3\xe3\xc3\xf7\xdf\x83\xff\xfe\x03\xff\xf8\x03\xff\xe0\x03\xff\x80\x03\xfe\x00\x7f\xff\xc0\xff\xff\xc0\x06\xe0\x00\x18\xc0\x00 \x80\x00\x00\x00"; + +// timeout used to update every minute +let drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +function draw() { + g.reset(); + let date = new Date(); + let timeStr = require("locale").time(date,1); + let next420 = getNextFourTwenty(); + g.clearRect(0,26,g.getWidth(),g.getHeight()); + g.setColor("#00ff00").setFontAlign(0,-1).setFont("Teletext10x18Ascii",2); + g.drawString(next420.minutes? timeStr: `\0${leaf_img}${timeStr}\0${leaf_img}`, g.getWidth()/2, 28); + g.setColor(g.theme.fg); + g.setFontAlign(-1,-1).setFont("Teletext10x18Ascii"); + g.drawString(g.wrapString(next420.text, g.getWidth()-8).join("\n"),4,60); + + // queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.clear(); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); +// draw immediately at first, queue update +draw(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); +// Show launcher when middle button pressed +Bangle.setUI("clock"); diff --git a/apps/ftclock/app.png b/apps/ftclock/app.png new file mode 100644 index 000000000..0553837ca Binary files /dev/null and b/apps/ftclock/app.png differ diff --git a/apps/ftclock/fourTwenty.js b/apps/ftclock/fourTwenty.js new file mode 100644 index 000000000..b2a2aa8fb --- /dev/null +++ b/apps/ftclock/fourTwenty.js @@ -0,0 +1,47 @@ +let ftz = require("fourTwentyTz"), + offsets = ftz.offsets, + timezones = ftz.timezones; + +function get420offset() { + let current_time = Math.floor((Date.now()%(24*3600*1000))/60000); + let current_min = current_time%60; + if (current_min>20 && current_min<25) { + current_time -= current_min-20; // 5 minutes grace period + } + let offset = 16*60+20-current_time; + if (offset<0) { + offset += 24*60; + } + return offset; +} + +function makeFourTwentyText(minutes, places) { + //let plural = minutes==1? "": "s"; + //let msgprefix = minutes? `${minutes} minute${plural} to`: "It is now"; + let msgprefix = minutes? `${minutes}m to`: "It is now"; + let msgsuffix = places.length>1? ", and other fine places": ""; + let msgplace = places[Math.floor(Math.random()*places.length)]; + return `${msgprefix} 4:20 at ${msgplace}${msgsuffix}.`; +} + +function getNextFourTwenty() { + let offs = get420offset(); + for (let i=0; i { + countries[r[0]] = r[1]; + }) + .on('end', () => { + fs.createReadStream(__dirname+'/zone.csv') + .pipe(csv.parse()) + .on('data', (r) => { + let parts = r[2].replace('_',' ').split('/'); + let city = parts[parts.length-1]; + let country =''; + if (parts.length>2) { // e.g. America/North_Dakota/New_Salem + country = parts[1]; // e.g. North Dakota + } else { + country = countries[r[1]]; // e.g. United States + } + zones[parseInt(r[0])] = {"name": `${city}, ${country}`}; + }) + .on('end', () => { + fs.createReadStream(__dirname+'/timezone.csv') + .pipe(csv.parse()) + .on('data', (r) => { + code = parseInt(r[0]); + if (!(code in zones)) return; + starttime = parseInt(r[2] || "0"); // Bugger. They're feeding us blanks for UTC now + offs = parseInt(r[3]); + if (offs<0) { + offs += 60*60*24; + } + zone = zones[code]; + if (starttime { + for (z in zones) { + zone = zones[z]; + if (zone.offs%60) continue; // One a dem funky timezones. Ignore. + zonelist = offsdict[zone.offs] || []; + zonelist.push(zone.name); + offsdict[zone.offs] = zonelist; + } + offsets = []; + for (o in offsdict) { + offsets.unshift(parseInt(o)); + } + fs.open("fourTwentyTz.js","w", (err, fd) => { + if (err) { + console.log("Can't open output file"); + return; + } + fs.write(fd, "// Generated by mkFourTwentyTz.js\n", handleWrite); + fs.write(fd, `// ${Date()}\n`, handleWrite); + fs.write(fd, "// Data source: https://timezonedb.com/files/timezonedb.csv.zip\n", handleWrite); + fs.write(fd, "exports.offsets = ", handleWrite); + fs.write(fd, JSON.stringify(offsets), handleWrite); + fs.write(fd, ";\n", handleWrite); + fs.write(fd, "exports.timezones = function(offs) {\n", handleWrite); + fs.write(fd, " switch (offs) {\n", handleWrite); + for (i=0; i -

THIS IS CURRENTLY BETA - PLEASE USE THE NORMAL FIRMWARE UPDATE - INSTRUCTIONS FOR BANGLE.JS 1 AND BANGLE.JS 2. For usage on Bangle.js 2 you'll likely need to have an updated bootloader.

+

This tool allows you to update the bootloader on Bangle.js 2 devices + from within the App Loader.

+

Firmware updates using the App Loader are only possible on Bangle.js 2. For firmware updates on Bangle.js 1 please see the Bangle.js 1 instructions

-

Your current firmware version is unknown

+
    +

    Your current firmware version is unknown and bootloader is unknown

    +
-

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.

+

 
@@ -38,7 +47,6 @@
     
 
     
diff --git a/apps/fwupdate/metadata.json b/apps/fwupdate/metadata.json
new file mode 100644
index 000000000..8f33e493a
--- /dev/null
+++ b/apps/fwupdate/metadata.json
@@ -0,0 +1,14 @@
+{
+  "id": "fwupdate",
+  "name": "Firmware Update",
+  "version": "0.04",
+  "description": "Uploads new Espruino firmwares to Bangle.js 2",
+  "icon": "app.png",
+  "type": "RAM",
+  "tags": "tools,system",
+  "supports": ["BANGLEJS2"],
+  "custom": "custom.html",
+  "customConnect": true,
+  "storage": [],
+  "sortorder": 20
+}
diff --git a/apps/gallifr/metadata.json b/apps/gallifr/metadata.json
new file mode 100644
index 000000000..9ce7d7f97
--- /dev/null
+++ b/apps/gallifr/metadata.json
@@ -0,0 +1,20 @@
+{
+  "id": "gallifr",
+  "name": "Time Traveller's Chronometer",
+  "shortName": "Time Travel Clock",
+  "version": "0.02",
+  "description": "A clock for time travellers. The light pie segment shows the minutes, the black circle, the hour. The dial itself reads 'time' just in case you forget.",
+  "icon": "gallifr.png",
+  "screenshots": [{"url":"screenshot_time.png"}],
+  "type": "clock",
+  "tags": "clock",
+  "supports": ["BANGLEJS","BANGLEJS2"],
+  "readme": "README.md",
+  "allow_emulator": true,
+  "storage": [
+    {"name":"gallifr.app.js","url":"app.js"},
+    {"name":"gallifr.img","url":"app-icon.js","evaluate":true},
+    {"name":"gallifr.settings.js","url":"settings.js"}
+  ],
+  "data": [{"name":"gallifr.json"}]
+}
diff --git a/apps/gbdebug/metadata.json b/apps/gbdebug/metadata.json
new file mode 100644
index 000000000..20b709d47
--- /dev/null
+++ b/apps/gbdebug/metadata.json
@@ -0,0 +1,14 @@
+{ "id": "gbdebug",
+  "name": "Gadgetbridge Debug",
+  "shortName":"GB Debug",
+  "version":"0.01",
+  "description": "Debug info for Gadgetbridge. Run this app and when Gadgetbridge messages arrive they are displayed on-screen.",
+  "icon": "app.png",
+  "tags": "",
+  "supports" : ["BANGLEJS2"],
+  "readme": "README.md",
+  "storage": [
+    {"name":"gbdebug.app.js","url":"app.js"},
+    {"name":"gbdebug.img","url":"app-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/gbmusic/metadata.json b/apps/gbmusic/metadata.json
new file mode 100644
index 000000000..9400f70e0
--- /dev/null
+++ b/apps/gbmusic/metadata.json
@@ -0,0 +1,21 @@
+{
+  "id": "gbmusic",
+  "name": "Gadgetbridge Music Controls",
+  "shortName": "Music Controls",
+  "version": "0.08",
+  "description": "Control the music on your Gadgetbridge-connected phone",
+  "icon": "icon.png",
+  "screenshots": [{"url":"screenshot_v1.png"},{"url":"screenshot_v2.png"}],
+  "type": "app",
+  "tags": "tools,bluetooth,gadgetbridge,music",
+  "supports": ["BANGLEJS","BANGLEJS2"],
+  "readme": "README.md",
+  "allow_emulator": true,
+  "storage": [
+    {"name":"gbmusic.app.js","url":"app.js"},
+    {"name":"gbmusic.settings.js","url":"settings.js"},
+    {"name":"gbmusic.wid.js","url":"widget.js"},
+    {"name":"gbmusic.img","url":"icon.js","evaluate":true}
+  ],
+  "data": [{"name":"gbmusic.json"},{"name":"gbmusic.load.json"}]
+}
diff --git a/apps/gbridge/metadata.json b/apps/gbridge/metadata.json
new file mode 100644
index 000000000..cdbc95c11
--- /dev/null
+++ b/apps/gbridge/metadata.json
@@ -0,0 +1,18 @@
+{
+  "id": "gbridge",
+  "name": "Gadgetbridge",
+  "version": "0.25",
+  "description": "(NOT RECOMMENDED) Displays Gadgetbridge notifications from Android. Please use the 'Android' Bangle.js app instead.",
+  "icon": "app.png",
+  "type": "widget",
+  "tags": "tool,system,android,widget",
+  "supports": ["BANGLEJS","BANGLEJS2"],
+  "dependencies": {"notify":"type"},
+  "readme": "README.md",
+  "storage": [
+    {"name":"gbridge.settings.js","url":"settings.js"},
+    {"name":"gbridge.img","url":"app-icon.js","evaluate":true},
+    {"name":"gbridge.wid.js","url":"widget.js"}
+  ],
+  "data": [{"name":"gbridge.json"}]
+}
diff --git a/apps/gbtwist/metadata.json b/apps/gbtwist/metadata.json
new file mode 100644
index 000000000..24f39a9d4
--- /dev/null
+++ b/apps/gbtwist/metadata.json
@@ -0,0 +1,17 @@
+{
+  "id": "gbtwist",
+  "name": "Gadgetbridge Twist Control",
+  "shortName": "Twist Control",
+  "version": "0.01",
+  "description": "Shake your wrist to control your music app via Gadgetbridge",
+  "icon": "app.png",
+  "type": "app",
+  "tags": "tools,bluetooth,gadgetbridge,music",
+  "supports": ["BANGLEJS"],
+  "readme": "README.md",
+  "allow_emulator": false,
+  "storage": [
+    {"name":"gbtwist.app.js","url":"app.js"},
+    {"name":"gbtwist.img","url":"app-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/geissclk/metadata.json b/apps/geissclk/metadata.json
new file mode 100644
index 000000000..456854dbd
--- /dev/null
+++ b/apps/geissclk/metadata.json
@@ -0,0 +1,16 @@
+{
+  "id": "geissclk",
+  "name": "Geiss Clock",
+  "version": "0.03",
+  "description": "7 segment clock with animated background in the style of Ryan Geiss' music visualisation. NOTE: The first run will take ~1 minute to do some precalculation",
+  "icon": "clock.png",
+  "type": "clock",
+  "tags": "clock",
+  "supports": ["BANGLEJS"],
+  "storage": [
+    {"name":"geissclk.app.js","url":"clock.js"},
+    {"name":"geissclk.precompute.js","url":"precompute.js"},
+    {"name":"geissclk.img","url":"clock-icon.js","evaluate":true}
+  ],
+  "data": [{"name":"geissclk.0.map"},{"name":"geissclk.1.map"},{"name":"geissclk.2.map"},{"name":"geissclk.3.map"},{"name":"geissclk.4.map"},{"name":"geissclk.5.map"},{"name":"geissclk.0.pal"},{"name":"geissclk.1.pal"},{"name":"geissclk.2.pal"}]
+}
diff --git a/apps/gesture/metadata.json b/apps/gesture/metadata.json
new file mode 100644
index 000000000..952faa5ea
--- /dev/null
+++ b/apps/gesture/metadata.json
@@ -0,0 +1,16 @@
+{
+  "id": "gesture",
+  "name": "Gesture Test",
+  "version": "0.01",
+  "description": "BETA! Uploads a basic Tensorflow Gesture model, and then outputs each gesture as a message",
+  "icon": "gesture.png",
+  "type": "app",
+  "tags": "gesture,ai",
+  "supports": ["BANGLEJS", "BANGLEJS2"],
+  "storage": [
+    {"name":"gesture.app.js","url":"gesture.js"},
+    {"name":".tfnames","url":"gesture-tfnames.js","evaluate":true},
+    {"name":".tfmodel","url":"gesture-tfmodel.js","evaluate":true},
+    {"name":"gesture.img","url":"gesture-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/getup/metadata.json b/apps/getup/metadata.json
new file mode 100644
index 000000000..0c5a7cc5b
--- /dev/null
+++ b/apps/getup/metadata.json
@@ -0,0 +1,18 @@
+{
+  "id": "getup",
+  "name": "Get Up",
+  "shortName": "Get Up",
+  "version": "0.01",
+  "description": "Reminds you to getup every x minutes. Sitting to long is dangerous!",
+  "icon": "app.png",
+  "tags": "tools,health",
+  "supports": ["BANGLEJS"],
+  "readme": "README.md",
+  "screenshots": [{"url":"bangle1-get-up-screenshot.png"}],
+  "allow_emulator": true,
+  "storage": [
+    {"name":"getup.app.js","url":"app.js"},
+    {"name":"getup.settings.js","url":"settings.js"},
+    {"name":"getup.img","url":"app-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/gmeter/metadata.json b/apps/gmeter/metadata.json
new file mode 100644
index 000000000..550153f31
--- /dev/null
+++ b/apps/gmeter/metadata.json
@@ -0,0 +1,14 @@
+{
+  "id": "gmeter",
+  "name": "G-Meter",
+  "shortName": "G-Meter",
+  "version": "0.01",
+  "description": "Simple G-Meter",
+  "icon": "app.png",
+  "tags": "",
+  "supports": ["BANGLEJS"],
+  "storage": [
+    {"name":"gmeter.app.js","url":"app.js"},
+    {"name":"gmeter.img","url":"app-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/golfscore/metadata.json b/apps/golfscore/metadata.json
new file mode 100644
index 000000000..8bef32765
--- /dev/null
+++ b/apps/golfscore/metadata.json
@@ -0,0 +1,15 @@
+{ "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}
+  ]
+}
diff --git a/apps/gpsautotime/metadata.json b/apps/gpsautotime/metadata.json
new file mode 100644
index 000000000..a64a45f6d
--- /dev/null
+++ b/apps/gpsautotime/metadata.json
@@ -0,0 +1,14 @@
+{
+  "id": "gpsautotime",
+  "name": "GPS auto time",
+  "shortName": "GPS auto time",
+  "version": "0.01",
+  "description": "A widget that automatically updates the Bangle.js time to the GPS time whenever there is a valid GPS fix.",
+  "icon": "widget.png",
+  "type": "widget",
+  "tags": "widget,gps",
+  "supports": ["BANGLEJS"],
+  "storage": [
+    {"name":"gpsautotime.wid.js","url":"widget.js"}
+  ]
+}
diff --git a/apps/gpsinfo/ChangeLog b/apps/gpsinfo/ChangeLog
index 3ff284cb8..5bb531bc7 100644
--- a/apps/gpsinfo/ChangeLog
+++ b/apps/gpsinfo/ChangeLog
@@ -2,4 +2,8 @@
 0.03: Show number of satellites while waiting for fix
 0.04: Add Maidenhead readout of GPS location
 0.05: Refactor to use 'layout' library for multi-device support
-0.06: Added number of satellites in view and fixed crash with GPS time
+0.06: Add number of satellites in view and fix crash with GPS time
+0.07: Resolve one FIFO_FULL case and exit App with button press
+0.08: Leave GPS power switched on on exit (will switch off after 0.5 seconds anyway)
+0.09: Fix FIFO_FULL error
+0.10: Show satellites "in view" separated by GNS-system
diff --git a/apps/gpsinfo/gps-info.js b/apps/gpsinfo/gps-info.js
index a16d4a04e..0eca2ccf5 100644
--- a/apps/gpsinfo/gps-info.js
+++ b/apps/gpsinfo/gps-info.js
@@ -4,7 +4,7 @@ function satelliteImage() {
 
 var Layout = require("Layout");
 var layout;
-Bangle.setGPSPower(1, "app");
+//Bangle.setGPSPower(1, "app");
 E.showMessage("Loading..."); // avoid showing rubbish on screen
 
 var lastFix = {
@@ -16,9 +16,9 @@ var lastFix = {
   time: 0,
   satellites: 0
 };
-var SATinView = 0;
-var nofBD = 0;
-var nofGP = 0;
+var SATinView = 0, lastSATinView = -1, nofGP = 0, nofBD = 0, nofGL = 0;
+const leaveNofixLayout = 1;  // 0 = stay on initial screen for debugging (default = 1)
+var listenerGPSraw = 0;
 
 function formatTime(now) {
   if (now == undefined) {
@@ -62,7 +62,7 @@ function getMaidenHead(param1,param2){
 function onGPS(fix) {
   if (lastFix.fix != fix.fix) {
     // if fix is different, change the layout
-    if (fix.fix) {
+    if (fix.fix && leaveNofixLayout) {
       layout = new Layout( {
         type:"v", c: [
           {type:"txt", font:"6x8:2", label:"GPS Info" },
@@ -86,13 +86,18 @@ function onGPS(fix) {
             {type:"txt", font:"6x8", pad:3, label:"Satellites used" }
           ]},
           {type:"txt", font:"6x8", label:"", fillx:true, id:"progress" }
-        ]},{lazy:true});
+        ]},{lazy:false});
     }
     g.clearRect(0,24,g.getWidth(),g.getHeight());
     layout.render();
   }
-  lastFix = fix;
-  if (fix.fix) {
+  if (fix.fix && leaveNofixLayout) {
+    if (listenerGPSraw == 1) {
+      Bangle.removeListener('GPS-raw', onGPSraw);
+      listenerGPSraw = 0;
+      lastSATinView = -1;
+      Bangle.buzz(50);
+    }
     var locale = require("locale");
     var satellites = fix.satellites;
     var maidenhead = getMaidenHead(fix.lat,fix.lon);
@@ -103,23 +108,53 @@ function onGPS(fix) {
     layout.time.label = "Time: "+formatTime(fix.time);
     layout.sat.label = "Satellites: "+satellites;
     layout.maidenhead.label = "Maidenhead: "+maidenhead;
+    layout.render();
   } else {
-    layout.sat.label = fix.satellites;
-    layout.progress.label = "in view: " + SATinView;
+    if (fix.satelites != lastFix.satelites) {
+      layout.clear(layout.sat);
+      layout.sat.label = fix.satellites;
+      layout.render(layout.sat);
+    }
+    if (SATinView != lastSATinView) {
+      if (!leaveNofixLayout) SATinView = -1;
+      lastSATinView = SATinView;
+      layout.clear(layout.progress);
+      layout.progress.label = "in view GP/BD/GL: " + nofGP + " " + nofBD + " " + nofGL;
+      // console.log("in view GP/BD/GL: " + nofGP + " " + nofBD + " " + nofGL);
+      layout.render(layout.progress);
+    }
   }
-  layout.render();
+
+  if (listenerGPSraw == 0 && !fix.fix) {
+    setTimeout(() => Bangle.on('GPS-raw', onGPSraw), 10);
+    listenerGPSraw = 1;
+  }
+  lastFix = fix;
 }
 
 function onGPSraw(nmea) {
   if (nmea.slice(3,6) == "GSV") {
-    // console.log(nmea);
-    if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13));
+    // console.log(nmea.slice(1,3) + "  " + nmea.slice(11,13));
     if (nmea.slice(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13));
-    SATinView = nofBD + nofGP;
+    if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13));
+    if (nmea.slice(0,7) == "$GLGSV,") nofGL = Number(nmea.slice(11,13));
+    SATinView = nofGP + nofBD + nofGL;
   }
 }
 
+
 Bangle.loadWidgets();
 Bangle.drawWidgets();
 Bangle.on('GPS', onGPS);
-Bangle.on('GPS-raw', onGPSraw);
+//Bangle.on('GPS-raw', onGPSraw);
+Bangle.setGPSPower(1, "app");
+
+function  exitApp() {
+  load();
+}
+
+setWatch(_=>exitApp(), BTN1);
+if (global.BTN2) {
+  setWatch(_=>exitApp(), BTN2);
+  setWatch(_=>exitApp(), BTN3);
+}
diff --git a/apps/gpsinfo/metadata.json b/apps/gpsinfo/metadata.json
new file mode 100644
index 000000000..60bd90c03
--- /dev/null
+++ b/apps/gpsinfo/metadata.json
@@ -0,0 +1,14 @@
+{
+  "id": "gpsinfo",
+  "name": "GPS Info",
+  "version": "0.10",
+  "description": "An application that displays information about altitude, lat/lon, satellites and time",
+  "icon": "gps-info.png",
+  "type": "app",
+  "tags": "gps",
+  "supports": ["BANGLEJS","BANGLEJS2"],
+  "storage": [
+    {"name":"gpsinfo.app.js","url":"gps-info.js"},
+    {"name":"gpsinfo.img","url":"gps-info-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/gpsnav/metadata.json b/apps/gpsnav/metadata.json
new file mode 100644
index 000000000..5c1830318
--- /dev/null
+++ b/apps/gpsnav/metadata.json
@@ -0,0 +1,16 @@
+{
+  "id": "gpsnav",
+  "name": "GPS Navigation",
+  "version": "0.05",
+  "description": "Displays GPS Course and Speed, + Directions to waypoint and waypoint recording, now with waypoint editor",
+  "icon": "icon.png",
+  "tags": "tool,outdoors,gps",
+  "supports": ["BANGLEJS"],
+  "readme": "README.md",
+  "interface": "waypoints.html",
+  "storage": [
+    {"name":"gpsnav.app.js","url":"app.min.js"},
+    {"name":"gpsnav.img","url":"app-icon.js","evaluate":true}
+  ],
+  "data": [{"name":"waypoints.json","url":"waypoints.json"}]
+}
diff --git a/apps/gpspoilog/metadata.json b/apps/gpspoilog/metadata.json
new file mode 100644
index 000000000..0a0902cea
--- /dev/null
+++ b/apps/gpspoilog/metadata.json
@@ -0,0 +1,15 @@
+{
+  "id": "gpspoilog",
+  "name": "GPS POI Logger",
+  "shortName": "GPS POI Log",
+  "version": "0.01",
+  "description": "A simple app to log points of interest with their GPS coordinates and read them back onto your PC. Based on the https://www.espruino.com/Bangle.js+Storage tutorial",
+  "icon": "app.png",
+  "tags": "outdoors",
+  "supports": ["BANGLEJS"],
+  "interface": "interface.html",
+  "storage": [
+    {"name":"gpspoilog.app.js","url":"app.js"},
+    {"name":"gpspoilog.img","url":"app-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/gpsrec/metadata.json b/apps/gpsrec/metadata.json
new file mode 100644
index 000000000..088b8c741
--- /dev/null
+++ b/apps/gpsrec/metadata.json
@@ -0,0 +1,19 @@
+{
+  "id": "gpsrec",
+  "name": "GPS Recorder",
+  "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",
+  "storage": [
+    {"name":"gpsrec.app.js","url":"app.js"},
+    {"name":"gpsrec.img","url":"app-icon.js","evaluate":true},
+    {"name":"gpsrec.wid.js","url":"widget.js"},
+    {"name":"gpsrec.settings.js","url":"settings.js"}
+  ],
+  "data": [{"name":"gpsrec.json"},{"wildcard":".gpsrc?","storageFile":true}]
+}
diff --git a/apps/gpssetup/metadata.json b/apps/gpssetup/metadata.json
new file mode 100644
index 000000000..b8b6dfc23
--- /dev/null
+++ b/apps/gpssetup/metadata.json
@@ -0,0 +1,18 @@
+{
+  "id": "gpssetup",
+  "name": "GPS Setup",
+  "shortName": "GPS Setup",
+  "version": "0.02",
+  "description": "Configure the GPS power options and store them in the GPS nvram",
+  "icon": "gpssetup.png",
+  "tags": "gps,tools,outdoors",
+  "supports": ["BANGLEJS"],
+  "readme": "README.md",
+  "storage": [
+    {"name":"gpssetup","url":"gpssetup.js"},
+    {"name":"gpssetup.settings.js","url":"settings.js"},
+    {"name":"gpssetup.app.js","url":"app.js"},
+    {"name":"gpssetup.img","url":"icon.js","evaluate":true}
+  ],
+  "data": [{"name":"gpssetup.settings.json","url":"settings.json"}]
+}
diff --git a/apps/gpstime/metadata.json b/apps/gpstime/metadata.json
new file mode 100644
index 000000000..27ee16105
--- /dev/null
+++ b/apps/gpstime/metadata.json
@@ -0,0 +1,13 @@
+{
+  "id": "gpstime",
+  "name": "GPS Time",
+  "version": "0.05",
+  "description": "Update the Bangle.js's clock based on the time from the GPS receiver",
+  "icon": "gpstime.png",
+  "tags": "tool,gps",
+  "supports": ["BANGLEJS","BANGLEJS2"],
+  "storage": [
+    {"name":"gpstime.app.js","url":"gpstime.js"},
+    {"name":"gpstime.img","url":"gpstime-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/gpstimeserver/metadata.json b/apps/gpstimeserver/metadata.json
new file mode 100644
index 000000000..973fa34a9
--- /dev/null
+++ b/apps/gpstimeserver/metadata.json
@@ -0,0 +1,14 @@
+{
+  "id": "gpstimeserver",
+  "name": "GPS Time Server",
+  "version": "0.01",
+  "description": "A widget which automatically starts the GPS and turns Bangle.js into a Bluetooth time server.",
+  "icon": "widget.png",
+  "type": "widget",
+  "tags": "widget",
+  "supports": ["BANGLEJS"],
+  "readme": "README.md",
+  "storage": [
+    {"name":"gpstimeserver.wid.js","url":"widget.js"}
+  ]
+}
diff --git a/apps/gpstouch/metadata.json b/apps/gpstouch/metadata.json
new file mode 100644
index 000000000..45e3d786b
--- /dev/null
+++ b/apps/gpstouch/metadata.json
@@ -0,0 +1,16 @@
+{
+  "id": "gpstouch",
+  "name": "GPS Touch",
+  "version": "0.02",
+  "description": "A touch based GPS watch, shows OS map reference",
+  "icon": "gpstouch.png",
+  "screenshots": [{"url":"screenshot4.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot1.png"}],
+  "tags": "tools,app",
+  "supports": ["BANGLEJS2"],
+  "readme": "README.md",
+  "storage": [
+    {"name":"geotools","url":"geotools.js"},
+    {"name":"gpstouch.app.js","url":"gpstouch.app.js"},
+    {"name":"gpstouch.img","url":"gpstouch.icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/grocery/metadata.json b/apps/grocery/metadata.json
new file mode 100644
index 000000000..8c0e34dff
--- /dev/null
+++ b/apps/grocery/metadata.json
@@ -0,0 +1,16 @@
+{
+  "id": "grocery",
+  "name": "Grocery",
+  "version": "0.02",
+  "description": "Simple grocery (shopping) list - Display a list of product and track if you already put them in your cart.",
+  "icon": "grocery.png",
+  "type": "app",
+  "tags": "tool,outdoors,shopping,list",
+  "supports": ["BANGLEJS", "BANGLEJS2"],
+  "custom": "grocery.html",
+  "allow_emulator": true,
+  "storage": [
+    {"name":"grocery.app.js","url":"app.js"},
+    {"name":"grocery.img","url":"grocery-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/hamloc/metadata.json b/apps/hamloc/metadata.json
new file mode 100644
index 000000000..932b639b7
--- /dev/null
+++ b/apps/hamloc/metadata.json
@@ -0,0 +1,15 @@
+{
+  "id": "hamloc",
+  "name": "QTH Locator / Maidenhead Locator System",
+  "shortName": "QTH Locator",
+  "version": "0.01",
+  "description": "Convert your current GPS location to the Maidenhead locator system used by HAM amateur radio operators",
+  "icon": "app.png",
+  "tags": "tool,outdoors,gps",
+  "supports": ["BANGLEJS"],
+  "readme": "README.md",
+  "storage": [
+    {"name":"hamloc.app.js","url":"app.js"},
+    {"name":"hamloc.img","url":"app-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/hardalarm/metadata.json b/apps/hardalarm/metadata.json
new file mode 100644
index 000000000..13a8fb920
--- /dev/null
+++ b/apps/hardalarm/metadata.json
@@ -0,0 +1,18 @@
+{
+  "id": "hardalarm",
+  "name": "Hard Alarm",
+  "shortName": "HardAlarm",
+  "version": "0.02",
+  "description": "Make sure you wake up! Count to the right number to turn off the alarm",
+  "icon": "app.png",
+  "tags": "tool,alarm,widget",
+  "supports": ["BANGLEJS"],
+  "storage": [
+    {"name":"hardalarm.app.js","url":"app.js"},
+    {"name":"hardalarm.boot.js","url":"boot.js"},
+    {"name":"hardalarm.js","url":"hardalarm.js"},
+    {"name":"hardalarm.img","url":"app-icon.js","evaluate":true},
+    {"name":"hardalarm.wid.js","url":"widget.js"}
+  ],
+  "data": [{"name":"hardalarm.json"}]
+}
diff --git a/apps/hcclock/metadata.json b/apps/hcclock/metadata.json
new file mode 100644
index 000000000..e372a0a2c
--- /dev/null
+++ b/apps/hcclock/metadata.json
@@ -0,0 +1,16 @@
+{
+  "id": "hcclock",
+  "name": "Hi-Contrast Clock",
+  "version": "0.03",
+  "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.",
+  "icon": "hcclock-icon.png",
+  "type": "clock",
+  "tags": "clock",
+  "screenshots": [{"url":"bangle1-high-contrast-clock-screenshot.png"}],
+  "supports": ["BANGLEJS"],
+  "allow_emulator": true,
+  "storage": [
+    {"name":"hcclock.app.js","url":"hcclock.app.js"},
+    {"name":"hcclock.img","url":"hcclock-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog
index c65cc3ab4..a693b2a83 100644
--- a/apps/health/ChangeLog
+++ b/apps/health/ChangeLog
@@ -8,3 +8,4 @@
 0.07: Added coloured bar charts
 0.08: Suppress bleed through of E.showMenu's when displaying bar charts
 0.09: Fix file naming so months are 1-based (not 0) (fix #1119)
+0.10: Adds additional 3 minute setting for HRM
diff --git a/apps/health/app.js b/apps/health/app.js
index 08d6ead17..7a55eec27 100644
--- a/apps/health/app.js
+++ b/apps/health/app.js
@@ -28,8 +28,8 @@ function menuSettings() {
     "< Back":()=>menuMain(),
     "Heart Rt":{
       value : 0|s.hrm,
-      min : 0, max : 2,
-      format : v=>["Off","10 mins","Always"][v],
+      min : 0, max : 3,
+      format : v=>["Off","3 mins","10 mins","Always"][v],
       onchange : v => { s.hrm=v;setSettings(s); }
     }
   });
diff --git a/apps/health/boot.js b/apps/health/boot.js
index c72e62b41..7b9aa51aa 100644
--- a/apps/health/boot.js
+++ b/apps/health/boot.js
@@ -1,18 +1,28 @@
 (function(){
-   var settings = require("Storage").readJSON("health.json",1)||{};
-   var hrm = 0|settings.hrm;
-   if (hrm==1) {
-     function onHealth() {
-       Bangle.setHRMPower(1, "health");
-       setTimeout(()=>Bangle.setHRMPower(0, "health"),2*60000); // give it 2 minutes
+  var settings = require("Storage").readJSON("health.json",1)||{};
+  var hrm = 0|settings.hrm;
+  if (hrm == 1 || hrm == 2) {
+   function onHealth() {
+     Bangle.setHRMPower(1, "health");
+     setTimeout(()=>Bangle.setHRMPower(0, "health"),hrm*60000); // give it 1 minute detection time for 3 min setting and 2 minutes for 10 min setting
+     if (hrm == 1){
+       for (var i = 1; i <= 2; i++){
+         setTimeout(()=>{
+           Bangle.setHRMPower(1, "health");
+           setTimeout(()=>{
+             Bangle.setHRMPower(0, "health");
+           }, (i * 200000) + 60000);
+         }, (i * 200000));
+       }
      }
-     Bangle.on("health", onHealth);
-     Bangle.on('HRM', h => {
-       if (h.confidence>80) Bangle.setHRMPower(0, "health");
-     });
-     if (Bangle.getHealthStatus().bpmConfidence) return;
-     onHealth();
-   } else Bangle.setHRMPower(hrm!=0, "health");
+   }
+   Bangle.on("health", onHealth);
+   Bangle.on('HRM', h => {
+     if (h.confidence>80) Bangle.setHRMPower(0, "health");
+   });
+   if (Bangle.getHealthStatus().bpmConfidence) return;
+   onHealth();
+  } else Bangle.setHRMPower(hrm!=0, "health");
 })();
 
 Bangle.on("health", health => {
diff --git a/apps/health/interface.html b/apps/health/interface.html
index f04857926..0791acd24 100644
--- a/apps/health/interface.html
+++ b/apps/health/interface.html
@@ -51,7 +51,7 @@ function saveCSV(data, date, title) {
 }
 
 function downloadHealth(filename, callback) {
-  Util.showModal("Downloading Track...");
+  Util.showModal("Downloading Health info...");
   Util.readStorage(filename, data => {
     Util.hideModal();
     callback(data);
diff --git a/apps/health/metadata.json b/apps/health/metadata.json
new file mode 100644
index 000000000..da9d764ea
--- /dev/null
+++ b/apps/health/metadata.json
@@ -0,0 +1,17 @@
+{
+  "id": "health",
+  "name": "Health Tracking",
+  "version": "0.10",
+  "description": "Logs health data and provides an app to view it (requires firmware 2v10.100 or later)",
+  "icon": "app.png",
+  "tags": "tool,system,health",
+  "supports": ["BANGLEJS","BANGLEJS2"],
+  "readme": "README.md",
+  "interface": "interface.html",
+  "storage": [
+    {"name":"health.app.js","url":"app.js"},
+    {"name":"health.img","url":"app-icon.js","evaluate":true},
+    {"name":"health.boot.js","url":"boot.js"},
+    {"name":"health","url":"lib.js"}
+  ]
+}
diff --git a/apps/heart/metadata.json b/apps/heart/metadata.json
new file mode 100644
index 000000000..6265dbfef
--- /dev/null
+++ b/apps/heart/metadata.json
@@ -0,0 +1,17 @@
+{
+  "id": "heart",
+  "name": "Heart Rate Recorder",
+  "shortName": "HRM Record",
+  "version": "0.07",
+  "description": "Application that allows you to record your heart rate. Can run in background",
+  "icon": "app.png",
+  "tags": "tool,health,widget",
+  "supports": ["BANGLEJS","BANGLEJS2"],
+  "interface": "interface.html",
+  "storage": [
+    {"name":"heart.app.js","url":"app.js"},
+    {"name":"heart.img","url":"app-icon.js","evaluate":true},
+    {"name":"heart.wid.js","url":"widget.js"}
+  ],
+  "data": [{"name":"heart.json"},{"wildcard":".heart?","storageFile":true}]
+}
diff --git a/apps/hebrew_calendar/metadata.json b/apps/hebrew_calendar/metadata.json
new file mode 100644
index 000000000..a2b7932b6
--- /dev/null
+++ b/apps/hebrew_calendar/metadata.json
@@ -0,0 +1,30 @@
+{
+  "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
+    }
+  ]
+}
diff --git a/apps/helloworld/metadata.json b/apps/helloworld/metadata.json
new file mode 100644
index 000000000..b8fe1b1e3
--- /dev/null
+++ b/apps/helloworld/metadata.json
@@ -0,0 +1,15 @@
+{
+  "id": "helloworld",
+  "name": "hello, world!",
+  "shortName": "hello world",
+  "version": "0.02",
+  "description": "A cross cultural hello world!/hola mundo! app with colors and languages",
+  "icon": "app.png",
+  "tags": "input,interface,buttons,touch",
+  "supports": ["BANGLEJS"],
+  "readme": "README.md",
+  "storage": [
+    {"name":"helloworld.app.js","url":"app.js"},
+    {"name":"helloworld.img","url":"app-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/hidbkbd/metadata.json b/apps/hidbkbd/metadata.json
new file mode 100644
index 000000000..135b86651
--- /dev/null
+++ b/apps/hidbkbd/metadata.json
@@ -0,0 +1,14 @@
+{
+  "id": "hidbkbd",
+  "name": "Binary Bluetooth Keyboard",
+  "shortName": "Binary BT Kbd",
+  "version": "0.02",
+  "description": "Enable HID in settings, pair with your phone/PC, then type messages using the onscreen keyboard by tapping repeatedly on the key you want",
+  "icon": "hid-binary-keyboard.png",
+  "tags": "bluetooth",
+  "supports": ["BANGLEJS"],
+  "storage": [
+    {"name":"hidbkbd.app.js","url":"hid-binary-keyboard.js"},
+    {"name":"hidbkbd.img","url":"hid-binary-keyboard-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/hidcam/metadata.json b/apps/hidcam/metadata.json
new file mode 100644
index 000000000..b2ef33229
--- /dev/null
+++ b/apps/hidcam/metadata.json
@@ -0,0 +1,15 @@
+{
+  "id": "hidcam",
+  "name": "Camera shutter",
+  "shortName": "Cam shutter",
+  "version": "0.03",
+  "description": "Enable HID, connect to your phone, start your camera and trigger the shot on your Bangle",
+  "icon": "app.png",
+  "tags": "bluetooth,tool",
+  "supports": ["BANGLEJS"],
+  "readme": "README.md",
+  "storage": [
+    {"name":"hidcam.app.js","url":"app.js"},
+    {"name":"hidcam.img","url":"app-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/hidjoystick/metadata.json b/apps/hidjoystick/metadata.json
new file mode 100644
index 000000000..e2b78a97b
--- /dev/null
+++ b/apps/hidjoystick/metadata.json
@@ -0,0 +1,14 @@
+{
+  "id": "hidjoystick",
+  "name": "Bluetooth Joystick",
+  "shortName": "Joystick",
+  "version": "0.01",
+  "description": "Emulates a 2 axis/5 button Joystick using the accelerometer as stick input and buttons 1-3, touch left as button 4 and touch right as button 5.",
+  "icon": "app.png",
+  "tags": "bluetooth",
+  "supports": ["BANGLEJS"],
+  "storage": [
+    {"name":"hidjoystick.app.js","url":"app.js"},
+    {"name":"hidjoystick.img","url":"app-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/hidkbd/metadata.json b/apps/hidkbd/metadata.json
new file mode 100644
index 000000000..15e5410b4
--- /dev/null
+++ b/apps/hidkbd/metadata.json
@@ -0,0 +1,14 @@
+{
+  "id": "hidkbd",
+  "name": "Bluetooth Keyboard",
+  "shortName": "Bluetooth Kbd",
+  "version": "0.02",
+  "description": "Enable HID in settings, pair with your phone/PC, then use this app to control other apps",
+  "icon": "hid-keyboard.png",
+  "tags": "bluetooth",
+  "supports": ["BANGLEJS"],
+  "storage": [
+    {"name":"hidkbd.app.js","url":"hid-keyboard.js"},
+    {"name":"hidkbd.img","url":"hid-keyboard-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/hidmsic/metadata.json b/apps/hidmsic/metadata.json
new file mode 100644
index 000000000..dc0079d74
--- /dev/null
+++ b/apps/hidmsic/metadata.json
@@ -0,0 +1,14 @@
+{
+  "id": "hidmsic",
+  "name": "Bluetooth Music Controls",
+  "shortName": "Music Control",
+  "version": "0.02",
+  "description": "Enable HID in settings, pair with your phone, then use this app to control music from your watch!",
+  "icon": "hid-music.png",
+  "tags": "bluetooth",
+  "supports": ["BANGLEJS"],
+  "storage": [
+    {"name":"hidmsic.app.js","url":"hid-music.js"},
+    {"name":"hidmsic.img","url":"hid-music-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/hidmsicswipe/metadata.json b/apps/hidmsicswipe/metadata.json
new file mode 100644
index 000000000..3f1ea5f4f
--- /dev/null
+++ b/apps/hidmsicswipe/metadata.json
@@ -0,0 +1,14 @@
+{
+  "id": "hidmsicswipe",
+  "name": "Bluetooth Music Swipe Controls",
+  "shortName": "Swipe Control",
+  "version": "0.01",
+  "description": "Based on the original Bluetooth Music Controls. Swipe up/down for volume, left/right for previous and next, tap for play/pause and btn1 to lock and unlock the controls. Enable HID in settings, pair with your phone, then use this app to control music from your watch!",
+  "icon": "hidmsicswipe.png",
+  "tags": "bluetooth",
+  "supports": ["BANGLEJS2"],
+  "storage": [
+    {"name":"hidmsicswipe.app.js","url":"hidmsicswipe.js"},
+    {"name":"hidmsicswipe.img","url":"hidmsicswipe-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/horsey/metadata.json b/apps/horsey/metadata.json
new file mode 100644
index 000000000..256d1f373
--- /dev/null
+++ b/apps/horsey/metadata.json
@@ -0,0 +1,13 @@
+{
+  "id": "horsey",
+  "name": "Horse Race!",
+  "version": "0.01",
+  "description": "Get several friends to start the game, then compete to see who can press BTN1 the most!",
+  "icon": "horse-race.png",
+  "tags": "game",
+  "supports": ["BANGLEJS"],
+  "storage": [
+    {"name":"horsey.app.js","url":"horse-race.js"},
+    {"name":"horsey.img","url":"horse-race-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/hourstrike/ChangeLog b/apps/hourstrike/ChangeLog
index 09eb45b36..aee136eef 100644
--- a/apps/hourstrike/ChangeLog
+++ b/apps/hourstrike/ChangeLog
@@ -6,3 +6,4 @@
 0.06: Move the next strike time to the first row of display
 0.07: Change the boot function to avoid reloading the entire watch
 0.08: Default to no strikes. Fix file-not-found issue during the first boot. Add data file.
+0.09: Add some customisation options
diff --git a/apps/hourstrike/app.js b/apps/hourstrike/app.js
index 7dc62d440..9169b5def 100644
--- a/apps/hourstrike/app.js
+++ b/apps/hourstrike/app.js
@@ -1,5 +1,6 @@
 const storage = require('Storage');
 var settings = storage.readJSON('hourstrike.json', 1);
+const chimes = ["Buzz", "Beep"];
 
 function updateSettings() {
   storage.write('hourstrike.json', settings);
@@ -26,6 +27,12 @@ function showMainMenu() {
   mainmenu.Strength = {
     value: settings.vlevel*10, min: 1, max: 10, format: v=>v/10,
     onchange: v=> {settings.vlevel = v/10; updateSettings();}};
+  mainmenu.Strikecount = {
+    value: settings.scount, min: 1, max: 2, format: v=>v,
+    onchange: v=> {settings.scount = v; updateSettings();}};
+  mainmenu.Chimetype = {
+    value: settings.buzzOrBeep, min: 0, max: 1, format: v => chimes[v],
+    onchange: v=> {settings.buzzOrBeep = v; updateSettings();}};
   mainmenu['< Back'] = ()=>load();
   return E.showMenu(mainmenu);
 }
diff --git a/apps/hourstrike/boot.js b/apps/hourstrike/boot.js
index 027b8bb5b..7f0cdd4e8 100644
--- a/apps/hourstrike/boot.js
+++ b/apps/hourstrike/boot.js
@@ -30,9 +30,23 @@
   }
   function strike_func () {
     var setting = require('Storage').readJSON('hourstrike.json',1)||[];
-    Bangle.buzz(200, setting.vlevel||0.5)
-      .then(() => new Promise(resolve => setTimeout(resolve,200)))
-      .then(() => Bangle.buzz(200, setting.vlevel||0.5));
+    if (0 == setting.buzzOrBeep) {
+      if (2 == setting.scount) {
+        Bangle.buzz(200, setting.vlevel||0.5)
+          .then(() => new Promise(resolve => setTimeout(resolve,200)))
+          .then(() => Bangle.buzz(200, setting.vlevel||0.5));
+      } else {
+        Bangle.buzz(200, setting.vlevel||0.5);
+      }
+    } else {
+      if (2 == setting.scount) {
+        Bangle.beep(200)
+          .then(() => new Promise(resolve => setTimeout(resolve,100)))
+          .then(() => Bangle.beep(300));
+      } else {
+        Bangle.beep(200);
+      }
+    }
     setup();
   }
   setup();
diff --git a/apps/hourstrike/hourstrike.json b/apps/hourstrike/hourstrike.json
index 09b17dc8e..6e4d583de 100644
--- a/apps/hourstrike/hourstrike.json
+++ b/apps/hourstrike/hourstrike.json
@@ -1 +1 @@
-{"interval":-1,"start":9,"end":21,"vlevel":0.5,"next_hour":-1,"next_minute":-1}
+{"interval":-1,"start":9,"end":21,"vlevel":0.5,"scount":2,"buzzOrBeep":0,"next_hour":-1,"next_minute":-1}
diff --git a/apps/hourstrike/metadata.json b/apps/hourstrike/metadata.json
new file mode 100644
index 000000000..614db54e4
--- /dev/null
+++ b/apps/hourstrike/metadata.json
@@ -0,0 +1,17 @@
+{
+  "id": "hourstrike",
+  "name": "Hour Strike",
+  "shortName": "Hour Strike",
+  "version": "0.09",
+  "description": "Strike the clock on the hour. A great tool to remind you an hour has passed!",
+  "icon": "app-icon.png",
+  "tags": "tool,alarm",
+  "supports": ["BANGLEJS", "BANGLEJS2"],
+  "readme": "README.md",
+  "storage": [
+    {"name":"hourstrike.app.js","url":"app.js"},
+    {"name":"hourstrike.boot.js","url":"boot.js"},
+    {"name":"hourstrike.img","url":"app-icon.js","evaluate":true},
+    {"name":"hourstrike.json","url":"hourstrike.json"}
+  ]
+}
diff --git a/apps/hralarm/ChangeLog b/apps/hralarm/ChangeLog
new file mode 100644
index 000000000..4c21f3ace
--- /dev/null
+++ b/apps/hralarm/ChangeLog
@@ -0,0 +1 @@
+0.01: New Widget!
diff --git a/apps/hralarm/README.md b/apps/hralarm/README.md
new file mode 100644
index 000000000..37b14ad9d
--- /dev/null
+++ b/apps/hralarm/README.md
@@ -0,0 +1,15 @@
+# Heart rate alarm
+
+This invisible widget vibrates whenever the heart rate gets close to the upper limit or goes over or under the configured limits.
+
+## Usage
+
+Configure the heart rate limits in the apps settings. This widget uses both 'HRM' and 'BTHRM' events.
+
+## Features
+
+Long vibration every 10 seconds on reaching upper limit, short vibrations between upper limit and warning threshold and an single vibration when reaching the lower limit again.
+
+## Requests/Creator
+
+https://github.com/halemmerich
diff --git a/apps/hralarm/metadata.json b/apps/hralarm/metadata.json
new file mode 100644
index 000000000..1fae68084
--- /dev/null
+++ b/apps/hralarm/metadata.json
@@ -0,0 +1,16 @@
+{
+  "id": "hralarm",
+  "name": "Heart rate alarm",
+  "shortName":"HR Alarm",
+  "version":"0.01",
+  "description": "This invisible widget vibrates whenever the heart rate gets close to the upper limit or goes over or under the configured limits",
+  "icon": "widget.png",  
+  "type": "widget",
+  "tags": "widget",
+  "supports" : ["BANGLEJS2"],
+  "readme": "README.md",
+  "storage": [
+    {"name":"hralarm.wid.js","url":"widget.js"},
+    {"name":"hralarm.settings.js","url":"settings.js"}
+  ]
+}
diff --git a/apps/hralarm/settings.js b/apps/hralarm/settings.js
new file mode 100644
index 000000000..3158ab8b7
--- /dev/null
+++ b/apps/hralarm/settings.js
@@ -0,0 +1,57 @@
+(function(back) {
+  var FILE = "hralarm.json";
+  
+  var settings = Object.assign({
+    enabled: false,
+    upper: 180,
+    warning: 170,
+    lower: 150,
+  }, require('Storage').readJSON(FILE, true) || {});
+
+  function writeSettings() {
+    require('Storage').writeJSON(FILE, settings);
+  }
+
+  E.showMenu({
+    '': { 'title': 'HR Alarm' },
+    '< Back': back,
+    'Enabled': {
+      value: !!settings.enabled,
+      format: v => settings.enabled ? "On" : "Off",
+      onchange: v => {
+        settings.enabled = v;
+        writeSettings();
+      }
+    },
+    'Upper limit': {
+      value: settings.upper,
+      min: 0,
+      step:5,
+      max: 300,
+      onchange: v => {
+        settings.upper = v;
+        writeSettings();
+      }
+    },
+    'Lower limit': {
+      value: settings.lower,
+      min: 0,
+      step:5,
+      max: 300,
+      onchange: v => {
+        settings.lower = v;
+        writeSettings();
+      }
+    },
+    'Warning at': {
+      value: settings.warning,
+      min: 0,
+      step:5,
+      max: 300,
+      onchange: v => {
+        settings.warning = v;
+        writeSettings();
+      }
+    }
+  });
+})
diff --git a/apps/hralarm/widget.js b/apps/hralarm/widget.js
new file mode 100644
index 000000000..30a94fdf2
--- /dev/null
+++ b/apps/hralarm/widget.js
@@ -0,0 +1,27 @@
+(() => {
+  var settings = require('Storage').readJSON("hralarm.json", true) || {};
+  if (!settings.enabled){ Bangle.setHRMPower(0, 'hralarm'); return; }
+  Bangle.setHRMPower(1, 'hralarm');
+  var hitLimit = 0;
+  var checkHr = function(hr){
+    if (hr.bpm > settings.warning && hr.bpm <= settings.upper){
+      Bangle.buzz(100, 1);
+    }
+    if (hitLimit < getTime() && hr.bpm > settings.upper){
+      hitLimit = getTime() + 10;
+      Bangle.buzz(2000, 1);
+    }
+    if (hitLimit > 0 && hr.bpm < settings.lower){
+      hitLimit = 0;
+      Bangle.buzz(500, 1);
+    }
+  };
+  Bangle.on("HRM", checkHr);
+  Bangle.on("BTHRM", checkHr);
+
+  WIDGETS["hralarm"]={
+    area:"tl",
+    width: 0,
+    draw: function(){}
+  };
+})()
diff --git a/apps/hralarm/widget.png b/apps/hralarm/widget.png
new file mode 100644
index 000000000..726cf3f9b
Binary files /dev/null and b/apps/hralarm/widget.png differ
diff --git a/apps/hrings/metadata.json b/apps/hrings/metadata.json
new file mode 100644
index 000000000..c47523377
--- /dev/null
+++ b/apps/hrings/metadata.json
@@ -0,0 +1,16 @@
+{
+  "id": "hrings",
+  "name": "Hypno Rings",
+  "version": "0.01",
+  "description": "Experiment with trippy rings, press buttons for change",
+  "icon": "hypno-rings.png",
+  "type": "app",
+  "tags": "rings,hypnosis,psychadelic",
+  "supports": ["BANGLEJS"],
+  "allow_emulator": true,
+  "screenshots": [{"url":"bangle1-hypno-rings-screenshot.png"}],
+  "storage": [
+    {"name":"hrings.app.js","url":"hypno-rings.js"},
+    {"name":"hrings.img","url":"hypno-rings-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/hrm/metadata.json b/apps/hrm/metadata.json
new file mode 100644
index 000000000..1504253bd
--- /dev/null
+++ b/apps/hrm/metadata.json
@@ -0,0 +1,13 @@
+{
+  "id": "hrm",
+  "name": "Heart Rate Monitor",
+  "version": "0.06",
+  "description": "Measure your heart rate and see live sensor data",
+  "icon": "heartrate.png",
+  "tags": "health",
+  "supports": ["BANGLEJS","BANGLEJS2"],
+  "storage": [
+    {"name":"hrm.app.js","url":"heartrate.js"},
+    {"name":"hrm.img","url":"heartrate-icon.js","evaluate":true}
+  ]
+}
diff --git a/apps/hrmaccevents/ChangeLog b/apps/hrmaccevents/ChangeLog
new file mode 100644
index 000000000..5560f00bc
--- /dev/null
+++ b/apps/hrmaccevents/ChangeLog
@@ -0,0 +1 @@
+0.01: New App!
diff --git a/apps/hrmaccevents/app.png b/apps/hrmaccevents/app.png
new file mode 100644
index 000000000..337b3cdc8
Binary files /dev/null and b/apps/hrmaccevents/app.png differ
diff --git a/apps/hrmaccevents/custom.html b/apps/hrmaccevents/custom.html
new file mode 100644
index 000000000..c0098eb12
--- /dev/null
+++ b/apps/hrmaccevents/custom.html
@@ -0,0 +1,190 @@
+
+ 
+  Bangle.js Accelerometer streaming
+ 
+ 
+
+
+
+
+
+

+ + + diff --git a/apps/hrmaccevents/metadata.json b/apps/hrmaccevents/metadata.json new file mode 100644 index 000000000..de59dceac --- /dev/null +++ b/apps/hrmaccevents/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "hrmaccevents", + "name": "HRM Accelerometer event recorder", + "shortName": "HRM ACC recorder", + "version": "0.01", + "type": "ram", + "description": "Record HRM and accelerometer events in high resolution to CSV files in your browser", + "icon": "app.png", + "tags": "debug", + "supports": ["BANGLEJS","BANGLEJS2"], + "custom": "custom.html", + "customConnect": true, + "storage": [ ] +} diff --git a/apps/hrrawexp/metadata.json b/apps/hrrawexp/metadata.json new file mode 100644 index 000000000..3920731aa --- /dev/null +++ b/apps/hrrawexp/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "hrrawexp", + "name": "HRM Data Exporter", + "shortName": "HRM Data Exporter", + "version": "0.01", + "description": "export raw hrm signal data to a csv file", + "icon": "app-icon.png", + "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", + "interface": "interface.html", + "storage": [ + {"name":"hrrawexp.app.js","url":"app.js"}, + {"name":"hrrawexp.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/imgclock/metadata.json b/apps/imgclock/metadata.json new file mode 100644 index 000000000..799d11acc --- /dev/null +++ b/apps/imgclock/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "imgclock", + "name": "Image background clock", + "shortName": "Image Clock", + "version": "0.08", + "description": "A clock with an image as a background", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "custom": "custom.html", + "storage": [ + {"name":"imgclock.app.js","url":"app.js"}, + {"name":"imgclock.img","url":"app-icon.js","evaluate":true}, + {"name":"imgclock.face.img"}, + {"name":"imgclock.face.json"}, + {"name":"imgclock.face.bg","content":""} + ] +} diff --git a/apps/impwclock/ChangeLog b/apps/impwclock/ChangeLog index 7bc119426..6555fcc8f 100644 --- a/apps/impwclock/ChangeLog +++ b/apps/impwclock/ChangeLog @@ -2,3 +2,4 @@ 0.02: Stopped watchface from flashing every interval 0.03: Move to Bangle.setUI to launcher support 0.04: Tweaks for compatibility with BangleJS2 +0.05: Time-word now readable on Bangle.js 2 diff --git a/apps/impwclock/clock-impword.js b/apps/impwclock/clock-impword.js index 8bb5da6ba..c42dbda44 100644 --- a/apps/impwclock/clock-impword.js +++ b/apps/impwclock/clock-impword.js @@ -46,7 +46,7 @@ const dy = big ? 22 : 16; const fontSize = big ? 3 : 2; // "6x8" const passivColor = 0x3186 /*grey*/ ; const activeColorNight = 0xF800 /*red*/ ; -const activeColorDay = 0xFFFF /* white */; +const activeColorDay = g.theme.fg; var hidxPrev; var showDigitalTime = false; diff --git a/apps/impwclock/metadata.json b/apps/impwclock/metadata.json new file mode 100644 index 000000000..120fbe795 --- /dev/null +++ b/apps/impwclock/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "impwclock", + "name": "Imprecise Word Clock", + "version": "0.05", + "description": "Imprecise word clock for vacations, weekends, and those who never need accurate time.", + "icon": "clock-impword.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "screenshots": [{"url":"bangle1-impercise-word-clock-screenshot.png"}], + "allow_emulator": true, + "storage": [ + {"name":"impwclock.app.js","url":"clock-impword.js"}, + {"name":"impwclock.img","url":"clock-impword-icon.js","evaluate":true} + ] +} diff --git a/apps/intervalTimer/metadata.json b/apps/intervalTimer/metadata.json new file mode 100644 index 000000000..2722473c1 --- /dev/null +++ b/apps/intervalTimer/metadata.json @@ -0,0 +1,15 @@ +{ + "id":"intervalTimer", + "name":"Interval Timer", + "shortName":"Interval Timer", + "icon": "app.png", + "version":"0.01", + "description": "Interval Timer for workouts, HIIT, or whatever else.", + "tags": "timer, interval, hiit, workout", + "readme":"README.md", + "supports":["BANGLEJS2"], + "storage": [ + {"name":"intervalTimer.app.js","url":"app.js"}, + {"name":"intervalTimer.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/intervals/metadata.json b/apps/intervals/metadata.json new file mode 100644 index 000000000..bc054a539 --- /dev/null +++ b/apps/intervals/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "intervals", + "name": "Intervals App", + "shortName": "Intervals", + "version": "0.01", + "description": "Intervals for training. It is possible to configure work time and rest time and number of sets.", + "icon": "intervals.png", + "tags": "", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"intervals.app.js","url":"intervals.app.js"}, + {"name":"intervals.img","url":"intervals-icon.js","evaluate":true} + ] +} diff --git a/apps/ios/metadata.json b/apps/ios/metadata.json new file mode 100644 index 000000000..26e474f89 --- /dev/null +++ b/apps/ios/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "ios", + "name": "iOS Integration", + "version": "0.08", + "description": "Display notifications/music/etc from iOS devices", + "icon": "app.png", + "tags": "tool,system,ios,apple,messages,notifications", + "dependencies": {"messages":"app"}, + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"ios.app.js","url":"app.js"}, + {"name":"ios.img","url":"app-icon.js","evaluate":true}, + {"name":"ios.boot.js","url":"boot.js"} + ], + "sortorder": -8 +} diff --git a/apps/isoclock/metadata.json b/apps/isoclock/metadata.json new file mode 100644 index 000000000..313153dde --- /dev/null +++ b/apps/isoclock/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "isoclock", + "name": "ISO Compliant Clock Face", + "shortName": "ISO Clock", + "version": "0.02", + "description": "Tweaked fork of digiclock for ISO date and time", + "icon": "isoclock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"isoclock.app.js","url":"isoclock.js"}, + {"name":"isoclock.img","url":"isoclock-icon.js","evaluate":true} + ] +} diff --git a/apps/jbells/metadata.json b/apps/jbells/metadata.json new file mode 100644 index 000000000..397638669 --- /dev/null +++ b/apps/jbells/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "jbells", + "name": "Jingle Bells", + "version": "0.01", + "description": "Play Jingle Bells", + "icon": "jbells.png", + "type": "app", + "tags": "sound", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"jbells.app.js","url":"jbells.js"}, + {"name":"jbells.img","url":"jbells-icon.js","evaluate":true} + ] +} diff --git a/apps/jbm8b/metadata.json b/apps/jbm8b/metadata.json new file mode 100644 index 000000000..4bae23cdc --- /dev/null +++ b/apps/jbm8b/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "jbm8b", + "name": "Magic 8 Ball", + "shortName": "Magic 8 Ball", + "version": "0.03", + "description": "A simple fortune telling app", + "icon": "app.png", + "tags": "game", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"jbm8b.app.js","url":"app.js"}, + {"name":"jbm8b.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/jbm8b_IT/metadata.json b/apps/jbm8b_IT/metadata.json new file mode 100644 index 000000000..dcb2aaffc --- /dev/null +++ b/apps/jbm8b_IT/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "jbm8b_IT", + "name": "Magic 8 Ball Italiano", + "shortName": "Magic 8 Ball IT", + "version": "0.01", + "description": "La palla predice il futuro", + "icon": "app.png", + "screenshots": [{"url":"bangle1-magic-8-ball-italiano-screenshot.png"}], + "tags": "game", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "storage": [ + {"name":"jbm8b_IT.app.js","url":"app.js"}, + {"name":"jbm8b_IT.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/kitchen/metadata.json b/apps/kitchen/metadata.json new file mode 100644 index 000000000..ab2e7183c --- /dev/null +++ b/apps/kitchen/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "kitchen", + "name": "Kitchen Combo", + "version": "0.13", + "description": "Combination of the Stepo, Walkersclock, Arrow and Waypointer apps into a multiclock format. 'Everything but the kitchen sink'", + "icon": "kitchen.png", + "type": "clock", + "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS"], + "readme": "README.md", + "interface": "waypoints.html", + "storage": [ + {"name":"kitchen.app.js","url":"kitchen.app.js"}, + {"name":"stepo2.kit.js","url":"stepo2.kit.js"}, + {"name":"swatch.kit.js","url":"swatch.kit.js"}, + {"name":"gps.kit.js","url":"gps.kit.js"}, + {"name":"compass.kit.js","url":"compass.kit.js"}, + {"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true} + ], + "data": [{"name":"waypoints.json","url":"waypoints.json"}] +} diff --git a/apps/lapcounter/ChangeLog b/apps/lapcounter/ChangeLog index 9db0e26c5..2893d3193 100644 --- a/apps/lapcounter/ChangeLog +++ b/apps/lapcounter/ChangeLog @@ -1 +1,3 @@ 0.01: first release +0.02: Themeable app icon +0.03: Behave better on Bangle.js 1 diff --git a/apps/lapcounter/app-icon.js b/apps/lapcounter/app-icon.js index a443b3a41..354c07124 100644 --- a/apps/lapcounter/app-icon.js +++ b/apps/lapcounter/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwwkBiIA/AH4A/AAkQgEBAREAC6oABdZQXkI6wuKC5iPUFxoXIOpoX/C6QFCC6IsCC6ZEDC/4XcPooXOFgoXQIgwX/C7IUFC5wsIC5ouCC6hcJC5h1DF9YwBChCPOAH4A/AH4Ap")) +require("heatshrink").decompress(atob("mEwwI0xg+evPsAon+ApX8Aon4AonwAod78AFDv4FWvoFE/IFDz4FXvIFD3wFE/wFW7wFDh5xBAoUfAok/Aol/BZUXAogA6A=")) diff --git a/apps/lapcounter/app.js b/apps/lapcounter/app.js index 215f6140a..f2f3873d7 100644 --- a/apps/lapcounter/app.js +++ b/apps/lapcounter/app.js @@ -5,7 +5,7 @@ let tStart; let tNow; let counter=-1; -const icon = require("heatshrink").decompress(atob("mEwwkBiIA/AH4A/AAkQgEBAREAC6oABdZQXkI6wuKC5iPUFxoXIOpoX/C6QFCC6IsCC6ZEDC/4XcPooXOFgoXQIgwX/C7IUFC5wsIC5ouCC6hcJC5h1DF9YwBChCPOAH4A/AH4Ap")); +const icon = require("heatshrink").decompress(atob("mEwwI0xg+evPsAon+ApX8Aon4AonwAod78AFDv4FWvoFE/IFDz4FXvIFD3wFE/wFW7wFDh5xBAoUfAok/Aol/BZUXAogA6A=")); function timeToText(t) { // Courtesy of stopwatch app let hrs = Math.floor(t/3600000); @@ -50,4 +50,5 @@ g.drawImage(icon,w/2-24,h/2-24); g.setFontAlign(0,0); require("Font8x12").add(Graphics); g.setFont("8x12"); -g.drawString("Click button to count.", w/2, h/2+22); +g.drawString("Click button 1 to count.", w/2, h/2+22); + diff --git a/apps/lapcounter/metadata.json b/apps/lapcounter/metadata.json new file mode 100644 index 000000000..3210c6a49 --- /dev/null +++ b/apps/lapcounter/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "lapcounter", + "name": "Lap Counter", + "version": "0.03", + "description": "Click button to count laps. Shows count and total time snapshot (like a stopwatch, but laid back).", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "app", + "tags": "tool,outdoors", + "readme":"README.md", + "supports": ["BANGLEJS", "BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"lapcounter.app.js","url":"app.js"}, + {"name":"lapcounter.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/largeclock/metadata.json b/apps/largeclock/metadata.json new file mode 100644 index 000000000..dde790786 --- /dev/null +++ b/apps/largeclock/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "largeclock", + "name": "Large Clock", + "version": "0.10", + "description": "A readable and informational digital watch, with date, seconds and moon phase", + "icon": "largeclock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "screenshots": [{"url":"bangle1-large-clock-screenshot.png"}], + "storage": [ + {"name":"largeclock.app.js","url":"largeclock.js"}, + {"name":"largeclock.img","url":"largeclock-icon.js","evaluate":true}, + {"name":"largeclock.settings.js","url":"settings.js"} + ], + "data": [{"name":"largeclock.json"}] +} diff --git a/apps/launch/ChangeLog b/apps/launch/ChangeLog index 0b2f134ad..ceb0177da 100644 --- a/apps/launch/ChangeLog +++ b/apps/launch/ChangeLog @@ -9,3 +9,4 @@ 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 +0.11: Merge Bangle.js 1 and 2 launchers, again diff --git a/apps/launch/app-bangle1.js b/apps/launch/app-bangle1.js deleted file mode 100644 index f779f5de4..000000000 --- a/apps/launch/app-bangle1.js +++ /dev/null @@ -1,75 +0,0 @@ -var s = require("Storage"); -var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type)); -apps.sort((a,b)=>{ - var n=(0|a.sortorder)-(0|b.sortorder); - if (n) return n; // do sortorder first - if (a.nameb.name) return 1; - return 0; -}); -var selected = 0; -var menuScroll = 0; -var menuShowing = false; - -function drawMenu() { - g.reset().setFont("6x8",2).setFontAlign(-1,0); - var w = g.getWidth(); - var h = g.getHeight(); - var m = w/2; - var n = Math.floor((h-48)/64); - if (selected>=n+menuScroll) menuScroll = 1+selected-n; - if (selectedn+menuScroll) ? g.theme.fg : g.theme.bg); - g.fillPoly([m,h-7,m-14,h-21,m+14,h-21]); - // draw - g.setColor(g.theme.fg); - for (var i=0;i{ - if (dir) { - selected += dir; - if (selected<0) selected = apps.length-1; - if (selected>=apps.length) selected = 0; - drawMenu(); - } else { - if (!apps[selected].src) return; - if (require("Storage").read(apps[selected].src)===undefined) { - E.showMessage("App Source\nNot found"); - setTimeout(drawMenu, 2000); - } else { - E.showMessage("Loading..."); - load(apps[selected].src); - } - } -}); -Bangle.loadWidgets(); -Bangle.drawWidgets(); -// 10s of inactivity goes back to clock -if (Bangle.setLocked) Bangle.setLocked(false); // unlock initially -var lockTimeout; -Bangle.on('lock', locked => { - if (lockTimeout) clearTimeout(lockTimeout); - lockTimeout = undefined; - if (locked) - lockTimeout = setTimeout(_=>load(), 10000); -}); diff --git a/apps/launch/app-bangle2.js b/apps/launch/app.js similarity index 90% rename from apps/launch/app-bangle2.js rename to apps/launch/app.js index 156eecdf4..42aba1bb9 100644 --- a/apps/launch/app-bangle2.js +++ b/apps/launch/app.js @@ -63,8 +63,11 @@ E.showScroller({ } }); -// pressing button goes back -setWatch(_=>load(), BTN1, {edge:"falling"}); +// on bangle.js 2, the screen is used for navigating, so the single button goes back +// on bangle.js 1, the buttons are used for navigating +if (process.env.HWVERSION==2) { + setWatch(_=>load(), BTN1, {edge:"falling"}); +} // 10s of inactivity goes back to clock Bangle.setLocked(false); // unlock initially diff --git a/apps/launch/metadata.json b/apps/launch/metadata.json new file mode 100644 index 000000000..1701d1f87 --- /dev/null +++ b/apps/launch/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "launch", + "name": "Launcher", + "shortName": "Launcher", + "version": "0.11", + "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", + "tags": "tool,system,launcher", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"launch.app.js","url":"app.js"}, + {"name":"launch.settings.js","url":"settings.js"} + ], + "data": [{"name":"launch.json"}], + "sortorder": -10 +} diff --git a/apps/lazyclock/metadata.json b/apps/lazyclock/metadata.json new file mode 100644 index 000000000..c08485fc7 --- /dev/null +++ b/apps/lazyclock/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "lazyclock", + "name": "Lazy Clock", + "version": "0.03", + "description": "Tells the time, roughly", + "icon": "lazyclock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "screenshots": [{"url":"bangle1-lazy-clock-screenshot.png"}], + "allow_emulator": true, + "storage": [ + {"name":"lazyclock.app.js","url":"lazyclock-app.js"}, + {"name":"lazyclock.img","url":"lazyclock-icon.js","evaluate":true} + ] +} diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index f5d8346da..1abd519ab 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -6,4 +6,9 @@ 0.06: Fix - Alarm disabled, if clock was closed. 0.07: Added settings to adjust data that is shown for each row. 0.08: Support for multiple screens. 24h graph for steps + HRM. Fullscreen Mode. -0.09: Tab anywhere to open the launcher. \ No newline at end of file +0.09: Tab anywhere to open the launcher. +0.10: Removed swipes to be compatible with the Pattern Launcher. Stability improvements. +0.11: Show the gadgetbridge weather temperature (settings). +0.12: Added humidity as an option to display. +0.13: Improved battery visualization. +0.14: Added altitude as an option to display. \ No newline at end of file diff --git a/apps/lcars/README.md b/apps/lcars/README.md index 4bf5218f6..017be246c 100644 --- a/apps/lcars/README.md +++ b/apps/lcars/README.md @@ -4,32 +4,41 @@ A simple LCARS inspired clock. Note: To display the steps, the health app is required. If this app is not installed, the data will not be shown. To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerdavid/BangleApps) +## Control + * Tap left / right to change between screens. + * Tap top / bottom to control the current screen. + ## Features * LCARS Style watch face. - * Full screen mode - widgets are still loaded. - * Supports multiple screens with different data. - * Tab anywhere to open the launcher. - * [Screen 1] Date + Time + Lock status. - * [Screen 1] Shows randomly images of real planets. - * [Screen 1] Shows different states such as (charging, out of battery, GPS on etc.) - * [Screen 1] Swipe up/down to activate an alarm. - * [Screen 1] Shows 3 customizable datapoints on the first screen. - * [Screen 1] The lower orange line indicates the battery level. - * [Screen 2] Display graphs for steps + hrm on the second screen. - * [Screen 2] Switch between day/month via swipe up/down. + * Full screen mode - widgets are still loaded but not shown. + * Tab on left/right to switch between different screens. + * Cusomizable data that is shown on screen 1 (steps, weather etc.) + * Shows random and real images of planets. + * Tap on top/bottom of screen 1 to activate an alarm. + * The lower orange line indicates the battery level. + * Display graphs (day or month) for steps + hrm on the second screen. +## Data that can be configured + * Steps - Steps loaded via the health module + * Battery - Current battery level in % + * VREF - Voltage of battery + * HRM - Last measured HRM + * Temp - Weather temperature loaded via the weather module + gadgetbridge + * Humidity - Humidity loaded via the weather module + gadgetbridge + * Altitude - Shows the altitude in m. + * CoreT - Temperature of device ## Multiple screens support -Access different screens via swipe left/ right +Access different screens via tap on the left/ right side of the screen ![](screenshot.png) ![](screenshot_2.png) -## Icons -
Icons made by Smashicons, Freepik from www.flaticon.com
- +# Ideas +- Tap top / bottom to disable steps (also icon) and start a timer ## Contributors -- Creator: [David Peer](https://github.com/peerdavid). -- Improvements: [Adam Schmalhofer](https://github.com/adamschmalhofer). +- [David Peer](https://github.com/peerdavid). +- [Adam Schmalhofer](https://github.com/adamschmalhofer). +- [Jon Warrington](https://github.com/BartokW). diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 167adad2d..81a501481 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -1,15 +1,11 @@ const SETTINGS_FILE = "lcars.setting.json"; -const Storage = require("Storage"); - - -// ...and overwrite them with any saved values -// This way saved values are preserved if a new version adds more settings +const locale = require('locale'); const storage = require('Storage') let settings = { alarm: -1, - dataRow1: "Battery", - dataRow2: "Steps", - dataRow3: "Temp." + dataRow1: "Steps", + dataRow2: "Temp", + dataRow3: "Battery" }; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; for (const key in saved_settings) { @@ -24,21 +20,19 @@ let cOrange = "#FF9900"; let cPurple = "#FF00DC"; let cWhite = "#FFFFFF"; let cBlack = "#000000"; -let cGrey = "#9E9E9E"; +let cGrey = "#424242"; /* * Global lcars variables */ let lcarsViewPos = 0; -let drag; -let hrmValue = 0; -var connected = NRF.getSecurityStatus().connected; -var plotWeek = false; +// let hrmValue = 0; +var plotMonth = false; /* * Requirements and globals */ -const locale = require('locale'); + var bgLeft = { width : 27, height : 176, bpp : 3, @@ -119,39 +113,19 @@ function queueDraw() { }, 60000 - (Date.now() % 60000)); } - -function printData(key, y, c){ +/** + * This function plots a data row in LCARS style. + * Note: It can be called async and therefore, the text alignment and + * font is set each time the function is called. + */ +function printRow(text, value, y, c){ + g.setFontAntonioMedium(); g.setFontAlign(-1,-1,0); - var text = "ERR"; - var value = "NOT FOUND"; - - if(key == "Battery"){ - text = "BAT"; - value = E.getBattery() + "%"; - - } else if(key == "Steps"){ - text = "STEP"; - value = getSteps(); - - } else if(key == "Temp."){ - text = "TEMP"; - value = Math.floor(E.getTemperature()) + "C"; - - } else if(key == "HRM"){ - text = "HRM"; - value = hrmValue; - - } else if (key == "VREF"){ - text = "VREF"; - value = E.getAnalogVRef().toFixed(2) + "V"; - - } - g.setColor(c); - g.fillRect(79, y-2, 87 ,y+18); + g.fillRect(79, y-2, 85 ,y+18); - g.setFontAlign(1,-1,0); - g.drawString(value, 131, y); + g.setFontAlign(0,-1,0); + g.drawString(value, 110, y); g.setColor(c); g.setFontAlign(-1,-1,0); @@ -161,6 +135,68 @@ function printData(key, y, c){ g.drawString(text, 135, y); } + +function drawData(key, y, c){ + try{ + _drawData(key, y, c); + } catch(ex){ + // Show last error - next try hopefully works. + } +} + + +function _drawData(key, y, c){ + key = key.toUpperCase() + var text = key; + var value = "ERR"; + var should_print= true; + + if(key == "STEPS"){ + text = "STEP"; + value = getSteps(); + + } else if(key == "BATTERY"){ + text = "BAT"; + value = E.getBattery() + "%"; + + } else if (key == "VREF"){ + value = E.getAnalogVRef().toFixed(2) + "V"; + + } else if(key == "HRM"){ + value = Math.round(Bangle.getHealthStatus("day").bpm); + + } else if (key == "TEMP"){ + var weather = getWeather(); + value = weather.temp; + + } else if (key == "HUMIDITY"){ + text = "HUM"; + var weather = getWeather(); + value = weather.hum; + + } else if (key == "ALTITUDE"){ + should_print= false; + text = "ALT"; + + // Immediately print something - avoid that its empty + printRow(text, "", y, c); + Bangle.getPressure().then(function(data){ + if(data && data.altitude){ + value = Math.round(data.altitude); + printRow(text, value, y, c); + } + }) + + } else if(key == "CORET"){ + value = locale.temp(parseInt(E.getTemperature())); + } + + // Print for all datapoints that are not async + if(should_print){ + printRow(text, value, y, c); + } +} + function drawHorizontalBgLine(color, x1, x2, y, h){ g.setColor(color); @@ -170,7 +206,7 @@ function drawHorizontalBgLine(color, x1, x2, y, h){ } -function drawLock(){ +function drawInfo(){ if(lcarsViewPos != 0){ return; } @@ -179,7 +215,8 @@ function drawLock(){ g.setColor(cOrange); g.clearRect(120, 10, g.getWidth(), 75); g.drawString("LCARS", 128, 13); - if(connected){ + + if(NRF.getSecurityStatus().connected){ g.drawString("CONN", 128, 33); } else { g.drawString("NOCON", 128, 33); @@ -239,12 +276,17 @@ function drawPosition0(){ // The last line is a battery indicator too var bat = E.getBattery() / 100.0; - var batX2 = parseInt((172 - 35) * bat + 35); - drawHorizontalBgLine(cOrange, 35, batX2, 171, 5); - drawHorizontalBgLine(cGrey, batX2+10, 172, 171, 5); + var batStart = 19; + var batWidth = 172 - batStart; + var batX2 = parseInt(batWidth * bat + batStart); + drawHorizontalBgLine(cOrange, batStart, batX2, 171, 5); + drawHorizontalBgLine(cGrey, batX2, 172, 171, 5); + for(var i=0; i+batStart<=172; i+=parseInt(batWidth/4)){ + drawHorizontalBgLine(cBlack, batStart+i, batStart+i+3, 168, 8) + } - // Draw logo - drawLock(); + // Draw Infos + drawInfo(); // Write time g.setFontAlign(-1, -1, 0); @@ -252,22 +294,22 @@ function drawPosition0(){ var currentDate = new Date(); var timeStr = locale.time(currentDate,1); g.setFontAntonioLarge(); - g.drawString(timeStr, 29, 10); + g.drawString(timeStr, 27, 10); // Write date g.setColor(cWhite); g.setFontAntonioMedium(); var dayStr = locale.dow(currentDate, true).toUpperCase(); dayStr += " " + currentDate.getDate(); - dayStr += " " + currentDate.getFullYear(); - g.drawString(dayStr, 32, 56); + dayStr += " " + locale.month(currentDate, 1).toUpperCase(); + g.drawString(dayStr, 30, 56); // Draw data g.setFontAlign(-1, -1, 0); g.setColor(cWhite); - printData(settings.dataRow1, 97, cOrange); - printData(settings.dataRow2, 122, cPurple); - printData(settings.dataRow3, 147, cBlue); + drawData(settings.dataRow1, 97, cOrange); + drawData(settings.dataRow2, 122, cPurple); + drawData(settings.dataRow3, 147, cBlue); // Draw state drawState(); @@ -299,7 +341,7 @@ function drawPosition1(){ } // Plot HRM graph - if(plotWeek){ + if(plotMonth){ var data = new Uint16Array(32); var cnt = new Uint8Array(32); health.readDailySummaries(new Date(), h=>{ @@ -336,8 +378,8 @@ function drawPosition1(){ g.setFontAlign(1, 1, 0); g.setFontAntonioMedium(); g.setColor(cWhite); - g.drawString("WEEK HRM", 154, 27); - g.drawString("WEEK STEPS [K]", 154, 115); + g.drawString("M-HRM", 154, 27); + g.drawString("M-STEPS [K]", 154, 115); // Plot day } else { @@ -377,8 +419,8 @@ function drawPosition1(){ g.setFontAlign(1, 1, 0); g.setFontAntonioMedium(); g.setColor(cWhite); - g.drawString("DAY HRM", 154, 27); - g.drawString("DAY STEPS", 154, 115); + g.drawString("D-HRM", 154, 27); + g.drawString("D-STEPS", 154, 115); } } @@ -407,6 +449,7 @@ function draw(){ */ function getSteps() { var steps = 0; + let health; try { health = require("health"); } catch(ex) { @@ -418,6 +461,33 @@ function getSteps() { } +function getWeather(){ + var weather; + + try { + weather = require('weather').get(); + } catch(ex) { + // Return default + } + + if (weather === undefined){ + weather = { + temp: "-", + hum: "-", + txt: "-", + wind: "-", + wdir: "-", + wrose: "-" + }; + } else { + weather.temp = locale.temp(Math.round(weather.temp-273.15)) + weather.hum = weather.hum + "%"; + } + + return weather; +} + + /* * Handle alarm */ @@ -456,7 +526,7 @@ function handleAlarm(){ .then(() => { // Update alarm state to disabled settings.alarm = -1; - Storage.writeJSON(SETTINGS_FILE, settings); + storage.writeJSON(SETTINGS_FILE, settings); }); } @@ -467,34 +537,23 @@ function handleAlarm(){ Bangle.on('lcdPower',on=>{ if (on) { // Whenever we connect to Gadgetbridge, reading data from - // health failed. Therefore, we update and read data from - // health iff the connection state did not change. - if(connected == NRF.getSecurityStatus().connected) { - draw(); - } else { - connected = NRF.getSecurityStatus().connected - drawLock(); - } + // health failed. Therefore, we update only partially... + drawInfo(); + drawState(); } else { // stop draw timer if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; } - - connected = NRF.getSecurityStatus().connected }); Bangle.on('lock', function(isLocked) { - drawLock(); + drawInfo(); }); Bangle.on('charging',function(charging) { drawState(); }); -Bangle.on('HRM', function (hrm) { - hrmValue = hrm.bpm; -}); - function increaseAlarm(){ if(isAlarmEnabled()){ @@ -503,7 +562,7 @@ function increaseAlarm(){ settings.alarm = getCurrentTimeInMinutes() + 5; } - Storage.writeJSON(SETTINGS_FILE, settings); + storage.writeJSON(SETTINGS_FILE, settings); } @@ -514,52 +573,56 @@ function decreaseAlarm(){ settings.alarm = -1; } - Storage.writeJSON(SETTINGS_FILE, settings); + storage.writeJSON(SETTINGS_FILE, settings); } +function feedback(){ + Bangle.buzz(40, 0.3); +} -// Thanks to the app "gbmusic" for this code to detect swipes in all 4 directions. -Bangle.on("drag", e => { - if (!drag) { // start dragging - drag = {x: e.x, y: e.y}; - } else if (!e.b) { // released - const dx = e.x-drag.x, dy = e.y-drag.y; - drag = null; +// Touch gestures to control clock. We don't use swipe to be compatible with the bangle ecosystem +Bangle.on('touch', function(btn, e){ + var left = parseInt(g.getWidth() * 0.2); + var right = g.getWidth() - left; + var upper = parseInt(g.getHeight() * 0.2); + var lower = g.getHeight() - upper; - // Horizontal swipe - if (Math.abs(dx)>Math.abs(dy)+10) { - if(dx > 0){ - lcarsViewPos = 0; - } else { - lcarsViewPos = 1; - } - - // Vertical swipe - } else if (Math.abs(dy)>Math.abs(dx)+10) { - if(lcarsViewPos == 0){ - if(dy > 0){ - decreaseAlarm(); - } else { - increaseAlarm(); - } - - // Only update the state and return to - // avoid a full draw as this is much faster. - drawState(); - return; - } - - if(lcarsViewPos == 1){ - plotWeek = dy < 0 ? true : false; - } - } + var is_left = e.x < left; + var is_right = e.x > right; + var is_upper = e.y < upper; + var is_lower = e.y > lower; + if(is_left && lcarsViewPos == 1){ + feedback(); + lcarsViewPos = 0; draw(); - } -}); + return; -Bangle.on("touch", e => { - Bangle.showLauncher(); + } else if(is_right && lcarsViewPos == 0){ + feedback(); + lcarsViewPos = 1; + draw(); + return; + } + + if(lcarsViewPos == 0){ + if(is_upper){ + feedback(); + increaseAlarm(); + drawState(); + return; + } if(is_lower){ + feedback(); + decreaseAlarm(); + drawState(); + return; + } + } else if (lcarsViewPos == 1 && (is_upper || is_lower) && plotMonth != is_lower){ + feedback(); + plotMonth = is_lower; + draw(); + return; + } }); diff --git a/apps/lcars/lcars.settings.js b/apps/lcars/lcars.settings.js index 0d004b002..076dea4d1 100644 --- a/apps/lcars/lcars.settings.js +++ b/apps/lcars/lcars.settings.js @@ -7,7 +7,7 @@ alarm: -1, dataRow1: "Battery", dataRow2: "Steps", - dataRow3: "Temp." + dataRow3: "Temp" }; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; for (const key in saved_settings) { @@ -18,14 +18,14 @@ storage.write(SETTINGS_FILE, settings) } - var data_options = ["Battery", "Steps", "Temp.", "HRM", "VREF"]; + var data_options = ["Steps", "Battery", "VREF", "HRM", "Temp", "Humidity", "Altitude", "CoreT"]; E.showMenu({ '': { 'title': 'LCARS Clock' }, '< Back': back, 'Row 1': { value: 0 | data_options.indexOf(settings.dataRow1), - min: 0, max: 4, + min: 0, max: 7, format: v => data_options[v], onchange: v => { settings.dataRow1 = data_options[v]; @@ -34,7 +34,7 @@ }, 'Row 2': { value: 0 | data_options.indexOf(settings.dataRow2), - min: 0, max: 4, + min: 0, max: 7, format: v => data_options[v], onchange: v => { settings.dataRow2 = data_options[v]; @@ -43,7 +43,7 @@ }, 'Row 3': { value: 0 | data_options.indexOf(settings.dataRow3), - min: 0, max: 4, + min: 0, max: 7, format: v => data_options[v], onchange: v => { settings.dataRow3 = data_options[v]; diff --git a/apps/lcars/metadata.json b/apps/lcars/metadata.json new file mode 100644 index 000000000..2d04ebdf6 --- /dev/null +++ b/apps/lcars/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "lcars", + "name": "LCARS Clock", + "shortName":"LCARS", + "icon": "lcars.png", + "version":"0.14", + "readme": "README.md", + "supports": ["BANGLEJS2"], + "description": "Library Computer Access Retrieval System (LCARS) clock.", + "type": "clock", + "tags": "clock", + "screenshots": [{"url":"screenshot.png"}], + "storage": [ + {"name":"lcars.app.js","url":"lcars.app.js"}, + {"name":"lcars.img","url":"lcars.icon.js","evaluate":true}, + {"name":"lcars.settings.js","url":"lcars.settings.js"} + ] +} diff --git a/apps/lcars/screenshot.png b/apps/lcars/screenshot.png index fba55a9f7..120229fba 100644 Binary files a/apps/lcars/screenshot.png and b/apps/lcars/screenshot.png differ diff --git a/apps/life/metadata.json b/apps/life/metadata.json new file mode 100644 index 000000000..5a40f0fb1 --- /dev/null +++ b/apps/life/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "life", + "name": "Game of Life", + "version": "0.04", + "description": "Conway's Game of Life - 16x16 board", + "icon": "life.png", + "tags": "game", + "supports": ["BANGLEJS"], + "screenshots": [{"url":"bangle1-game-of-life-screenshot.png"}], + "allow_emulator": true, + "storage": [ + {"name":"life.app.js","url":"life.min.js"}, + {"name":"life.img","url":"life-icon.js","evaluate":true} + ] +} diff --git a/apps/lifeclk/metadata.json b/apps/lifeclk/metadata.json new file mode 100644 index 000000000..6b62860ae --- /dev/null +++ b/apps/lifeclk/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "lifeclk", + "name": "Game of Life Clock", + "shortName": "Conway's Clock", + "version": "0.06", + "description": "Modification and clockification of Conway's Game of Life", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"lifeclk.app.js","url":"app.min.js"}, + {"name":"lifeclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/lightswitch/ChangeLog b/apps/lightswitch/ChangeLog new file mode 100644 index 000000000..7a7ecd027 --- /dev/null +++ b/apps/lightswitch/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! +0.02: Add the option to enable touching the widget only on clock and settings. diff --git a/apps/lightswitch/README.md b/apps/lightswitch/README.md new file mode 100644 index 000000000..d58de7ca4 --- /dev/null +++ b/apps/lightswitch/README.md @@ -0,0 +1,102 @@ +# Light Switch Widget + +Whis this widget I wanted to create a solution to quickly en-/disable the LCD backlight and even change the brightness. +In addition it shows the lock status with the option to personalize the lock icon with a tiny image. + +--- +### Control +--- +* __On / off__ + Single touch the widget to en-/disable the backlight. +* __Change brightness__ _(can be disabled)_ + First touch the widget, then quickly touch the screen again and drag up/down until you reach your wished brigthness. +* __Double tap to flash backlight__ _(can be disabled)_ + By defaut you can double tap on the right side of your bangle to flash the backlight for a short duration. + (While the backlight is active your bangle will be unlocked.) +* __Double tap to unlock__ _(disabled by default)_ + If a side is defined in the app settings, your bangle will be unlocked if you double tap on that side. + +--- +### Settings +--- +#### Widget - Change the apperance of the widget: +* __Bulb col__ + _red_ / _yellow_ / _green_ / __cyan__ / _blue_ / _magenta_ + Define the color used for the lightbulbs inner circle. + The selected color will be dimmed depending on the actual brightness value. +* __Image__ + __default__ / _random_ / _..._ + Set your favourite lock icon image. (If no image file is found _no image_ will be displayed.) + * _random_ -> Select a random image on each time the widget is drawn. + +#### Control - Change when and how to use the widget: +* __Touch__ + _on def clk_ / _on all clk_ / _clk+setting_ / _clk+launch_ / _except apps_ / __always on__ + Select when touching the widget is active to en-/disable the backlight. + * _on def clk_ -> only on your selected main clock face + * _on all clk_ -> on all apps of the type _clock_ + * _clk+setting_ -> on all apps of the type _clock_ and in the settings + * _clk+launch_ -> on all apps of the types _clock_ and _launch_ + * _except apps_ -> on all apps of the types _clock_ and _launch_ and in the settings + * _always on_ -> always enabled when the widget is displayed +* __Drag Delay__ + _off_ / _50ms_ / _100ms_ / _..._ / __500ms__ / _..._ / _1000ms_ + Change the maximum delay between first touch and re-touch/drag to change the brightness or disable changing the brightness completely. +* __Min Value__ + _1%_ / _2%_ / _..._ / __10%__ / _..._ / _100%_ + Set the minimal level of brightness you can change to. + +#### Unlock - Set double tap side to unlock: +* __TapSide__ + __off__ / _left_ / _right_ / _top_ / _bottom_ / _front_ / _back_ + +#### Flash - Change if and how to flash the backlight: +* __TapSide__ + _off_ / _left_ / __right__ / _top_ / _bottom_ / _front_ / _back_ + Set double tap side to flash the backlight or disable completely. +* __Tap__ + _on locked_ / _on unlocked_ / __always on__ + Select when a double tap is recognised. +* __Timeout__ + _0.5s_ / _1s_ / _..._ / __2s__ / _..._ / _10s_ + Change how long the backlight will be activated on a flash. +* __Min Value__ + _1%_ / _2%_ / _..._ / __20%__ / _..._ / _100%_ + Set the minimal level of brightness for the backlight on a flash. + +--- +### Images +--- + +| Lightbulb | Default lock icon | +|:-----------------------------:|:-----------------------:| +| ![](images/lightbulb.png) | ![](images/default.png) | +| ( _full_ / _dimmed_ / _off_ ) | ( _on_ / _off_ ) | + +Examples in default light and dark theme. + +| Lock | Heart | Invader | JS | Smiley | Skull | Storm | +|:----:|:-----:|:-------:|:--:|:------:|:-----:|:-----:| +| ![](images/image_lock.png) | ![](images/image_heart.png) | ![](images/image_invader.png) | ![](images/image_js.png) | ![](images/image_smiley.png) | ![](images/image_skull.png) | ![](images/image_storm.png) | + +This images are stored in a seperate file _(lightswitch.images.json)_. + +--- +### Worth Mentioning +--- +#### To do list +* Catch the touch and draw input related to this widget to prevent actions in the active app. + _(For now I have no idea how to achieve this, help is appreciated)_ +* Manage images for the lock icon through a _Customize and Upload App_ page. + +#### Requests, Bugs and Feedback +Please leave requests and bug reports by raising an issue at [github.com/storm64/BangleApps](https://github.com/storm64/BangleApps) or send me a [mail](mailto:banglejs@storm64.de). + +#### Thanks +Huge thanks to Gordon Williams and all the motivated developers. + +#### Creator +Storm64 ([Mail](mailto:banglejs@storm64.de), [github](https://github.com/storm64)) + +#### License +[MIT License](LICENSE) diff --git a/apps/lightswitch/boot.js b/apps/lightswitch/boot.js new file mode 100644 index 000000000..d57679b56 --- /dev/null +++ b/apps/lightswitch/boot.js @@ -0,0 +1,17 @@ +// load settings +var settings = Object.assign({ + value: 1, + isOn: true +}, require("Storage").readJSON("lightswitch.json", true) || {}); + +// set brightness +Bangle.setLCDBrightness(settings.isOn ? settings.value : 0); + +// remove tap listener to prevent uncertainties +Bangle.removeListener("tap", require("lightswitch.js").tapListener); + +// add tap listener to unlock and/or flash backlight +if (settings.unlockSide || settings.tapSide) Bangle.on("tap", require("lightswitch.js").tapListener); + +// clear variable +settings = undefined; diff --git a/apps/lightswitch/images.json b/apps/lightswitch/images.json new file mode 100644 index 000000000..dcdb72470 --- /dev/null +++ b/apps/lightswitch/images.json @@ -0,0 +1,37 @@ +{ + "lock": { + "str": "BQcBAAEYxiA=", + "x": 9, + "y": 15, + }, + "heart": { + "str": "CQjBAQD4//+chAAACA4Pj+8=", + "x": 7, + "y": 14, + }, + "invader": { + "str": "DQqDASQASQASSEAAECSQEAEASQEkkkAQEgkgkAEkkkkkAgkkkggEEAAEEAAEgkAASQAAASQ=", + "x": 5, + "y": 13, + }, + "js": { + "str": "CAqBAd//2NfZ3tHfX78=", + "x": 7, + "y": 13, + }, + "skull": { + "str": "CQqBAcHAZTKcH/+OfMGfAA==", + "x": 7, + "y": 13, + }, + "smiley": { + "str": "CwqDASQAAASQNtsAQNttsANgMBsBsBgNgNtttsBsNsNgBsANgCBttgCSAAACQA==", + "x": 6, + "y": 13, + }, + "storm": { + "str": "CQmDASAAACBttgBgABgBttgCMAACQNsASRgASSBgCSSACSA=", + "x": 7, + "y": 13, + } +} diff --git a/apps/lightswitch/images/README.md b/apps/lightswitch/images/README.md new file mode 100644 index 000000000..5e2a71ce5 --- /dev/null +++ b/apps/lightswitch/images/README.md @@ -0,0 +1 @@ +# Light Switch Images diff --git a/apps/lightswitch/images/app.png b/apps/lightswitch/images/app.png new file mode 100644 index 000000000..233ef02e2 Binary files /dev/null and b/apps/lightswitch/images/app.png differ diff --git a/apps/lightswitch/images/default.png b/apps/lightswitch/images/default.png new file mode 100644 index 000000000..6fc371687 Binary files /dev/null and b/apps/lightswitch/images/default.png differ diff --git a/apps/lightswitch/images/image_heart.png b/apps/lightswitch/images/image_heart.png new file mode 100644 index 000000000..75935bbf8 Binary files /dev/null and b/apps/lightswitch/images/image_heart.png differ diff --git a/apps/lightswitch/images/image_invader.png b/apps/lightswitch/images/image_invader.png new file mode 100644 index 000000000..31e3e4fb4 Binary files /dev/null and b/apps/lightswitch/images/image_invader.png differ diff --git a/apps/lightswitch/images/image_js.png b/apps/lightswitch/images/image_js.png new file mode 100644 index 000000000..501b15f03 Binary files /dev/null and b/apps/lightswitch/images/image_js.png differ diff --git a/apps/lightswitch/images/image_lock.png b/apps/lightswitch/images/image_lock.png new file mode 100644 index 000000000..4315cd172 Binary files /dev/null and b/apps/lightswitch/images/image_lock.png differ diff --git a/apps/lightswitch/images/image_skull.png b/apps/lightswitch/images/image_skull.png new file mode 100644 index 000000000..0f6065b74 Binary files /dev/null and b/apps/lightswitch/images/image_skull.png differ diff --git a/apps/lightswitch/images/image_smiley.png b/apps/lightswitch/images/image_smiley.png new file mode 100644 index 000000000..f97533f1d Binary files /dev/null and b/apps/lightswitch/images/image_smiley.png differ diff --git a/apps/lightswitch/images/image_storm.png b/apps/lightswitch/images/image_storm.png new file mode 100644 index 000000000..e3bec672b Binary files /dev/null and b/apps/lightswitch/images/image_storm.png differ diff --git a/apps/lightswitch/images/lightbulb.png b/apps/lightswitch/images/lightbulb.png new file mode 100644 index 000000000..ed861ab91 Binary files /dev/null and b/apps/lightswitch/images/lightbulb.png differ diff --git a/apps/lightswitch/images/screenshot_1.png b/apps/lightswitch/images/screenshot_1.png new file mode 100644 index 000000000..3b62dfcd7 Binary files /dev/null and b/apps/lightswitch/images/screenshot_1.png differ diff --git a/apps/lightswitch/images/screenshot_2.png b/apps/lightswitch/images/screenshot_2.png new file mode 100644 index 000000000..f1d69a6ae Binary files /dev/null and b/apps/lightswitch/images/screenshot_2.png differ diff --git a/apps/lightswitch/images/screenshot_3.png b/apps/lightswitch/images/screenshot_3.png new file mode 100644 index 000000000..4523086e4 Binary files /dev/null and b/apps/lightswitch/images/screenshot_3.png differ diff --git a/apps/lightswitch/images/screenshot_4.png b/apps/lightswitch/images/screenshot_4.png new file mode 100644 index 000000000..6e4befd95 Binary files /dev/null and b/apps/lightswitch/images/screenshot_4.png differ diff --git a/apps/lightswitch/lib.js b/apps/lightswitch/lib.js new file mode 100644 index 000000000..eb720e69a --- /dev/null +++ b/apps/lightswitch/lib.js @@ -0,0 +1,124 @@ +// from boot accassible functions +exports = { + // listener function // + // tap listener to flash backlight + tapListener: function(data) { + // check for double tap and direction + if (data.double) { + // setup shortcut to this widget or load from storage + var w = global.WIDGETS ? WIDGETS.lightswitch : Object.assign({ + unlockSide: "", + tapSide: "right", + tapOn: "always", + }, require("Storage").readJSON("lightswitch.json", true) || {}); + + // cache lock status + var locked = Bangle.isLocked(); + + // check to unlock + if (locked && data.dir === w.unlockSide) Bangle.setLocked(); + + // check to flash + if (data.dir === w.tapSide && (w.tapOn === "always" || locked === (w.tapOn === "locked"))) require("lightswitch.js").flash(); + + // clear variables + w = undefined; + locked = undefined; + } + }, + + // external function // + // function to flash backlight + flash: function(tOut) { + // setup shortcut to this widget or load from storage + var w = global.WIDGETS ? WIDGETS.lightswitch : Object.assign({ + tOut: 3000, + minFlash: 0.2, + value: 1, + isOn: true + }, require("Storage").readJSON("lightswitch.json", true) || {}); + + // chack if locked, backlight off or actual value lower then minimal flash value + if (Bangle.isLocked() || !w.isOn || w.value < w.minFlash) { + + // set inner bulb and brightness + var setBrightness = function(w, value) { + if (w.drawInnerBulb) w.drawInnerBulb(value); + Bangle.setLCDBrightness(value); + }; + + // override timeout if defined + if (!tOut) tOut = w.tOut; + + // check lock state + if (Bangle.isLocked()) { + // cache options + var options = Bangle.getOptions(); + // set shortened lock and backlight timeout + Bangle.setOptions({ + lockTimeout: tOut, + backlightTimeout: tOut + }); + // unlock + Bangle.setLocked(false); + // set timeout to reset options + setTimeout(Bangle.setOptions, tOut + 100, options); + + // clear variable + options = undefined; + } else { + // set timeout to reset backlight + setTimeout((w, funct) => { + if (!Bangle.isLocked()) funct(w, w.isOn ? w.value : 0); + }, tOut, w, setBrightness); + } + + // enable backlight + setTimeout((w, funct) => { + funct(w, w.value < w.minFlash ? w.minFlash : w.value); + }, 10, w, setBrightness); + + // clear variable + setBrightness = undefined; + } + + // clear variable + w = undefined; + }, + + // external access to internal function // + // refference to widget function or set backlight and write to storage if not skipped + changeValue: function(value, skipWrite) { + // check if widgets are loaded + if (global.WIDGETS) { + // execute inside widget + WIDGETS.lightswitch.changeValue(value, skipWrite); + } else { + // load settings from storage + var filename = "lightswitch.json"; + var storage = require("Storage"); + var settings = Object.assign({ + value: 1, + isOn: true + }, storage.readJSON(filename, true) || {}); + + // check value + if (value) { + // set new value + settings.value = value; + } else { + // switch backlight status + settings.isOn = !settings.isOn; + } + // set brightness + Bangle.setLCDBrightness(settings.isOn ? settings.value : 0); + // write changes to storage if not skipped + if (!skipWrite) storage.writeJSON(filename, settings); + + // clear variables + filename = undefined; + storage = undefined; + settings = undefined; + } + } +}; diff --git a/apps/lightswitch/metadata.json b/apps/lightswitch/metadata.json new file mode 100644 index 000000000..902b1536b --- /dev/null +++ b/apps/lightswitch/metadata.json @@ -0,0 +1,28 @@ +{ + "id": "lightswitch", + "name": "Light Switch Widget", + "shortName": "Light Switch", + "version": "0.02", + "description": "A fast way to switch LCD backlight on/off, change the brightness and show the lock status. All in one widget.", + "icon": "images/app.png", + "screenshots": [ + {"url": "images/screenshot_1.png"}, + {"url": "images/screenshot_2.png"}, + {"url": "images/screenshot_3.png"}, + {"url": "images/screenshot_4.png"} + ], + "type": "widget", + "tags": "tool,widget,brightness,lock", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name": "lightswitch.boot.js", "url": "boot.js"}, + {"name": "lightswitch.js", "url": "lib.js"}, + {"name": "lightswitch.settings.js", "url": "settings.js"}, + {"name": "lightswitch.wid.js", "url": "widget.js"} + ], + "data": [ + {"name": "lightswitch.json"}, + {"name": "lightswitch.images.json", "url": "images.json"} + ] +} diff --git a/apps/lightswitch/settings.js b/apps/lightswitch/settings.js new file mode 100644 index 000000000..aac159148 --- /dev/null +++ b/apps/lightswitch/settings.js @@ -0,0 +1,155 @@ +(function(back) { + var filename = "lightswitch.json"; + + // set Storage and load settings + var storage = require("Storage"); + var settings = Object.assign({ + colors: "011", + image: "default", + touchOn: "clock,launch", + dragDelay: 500, + minValue: 0.1, + unlockSide: "", + tapSide: "right", + tapOn: "always", + tOut: 2000, + minFlash: 0.2 + }, storage.readJSON(filename, true) || {}); + var images = storage.readJSON(filename.replace(".", ".images."), true) || false; + + // write change to storage and widget + function writeSetting(key, value, drawWidgets) { + // reread settings to only change key + settings = Object.assign(settings, storage.readJSON(filename, true) || {}); + // change the value of key + settings[key] = value; + // write to storage + storage.writeJSON(filename, settings); + // check if widgets are loaded + if (global.WIDGETS) { + // setup shortcut to the widget + var w = WIDGETS.lightswitch; + // assign changes to widget + w = Object.assign(w, settings); + // redraw widgets if neccessary + if (drawWidgets) Bangle.drawWidgets(); + } + } + + // generate entry for circulating values + function getEntry(key) { + var entry = entries[key]; + // check for existing titles to decide value type + if (entry.value) { + // return entry for string value + return { + value: entry.value.indexOf(settings[key]), + format: v => entry.title ? entry.title[v] : entry.value[v], + onchange: function(v) { + this.value = v = v >= entry.value.length ? 0 : v < 0 ? entry.value.length - 1 : v; + writeSetting(key, entry.value[v], entry.drawWidgets); + if (entry.exec) entry.exec(entry.value[v]); + } + }; + } else { + // return entry for numerical value + return { + value: settings[key] * entry.factor, + step: entry.step, + format: v => v > 0 ? v + entry.unit : "off", + onchange: function(v) { + this.value = v = v > entry.max ? entry.min : v < entry.min ? entry.max : v; + writeSetting(key, v / entry.factor, entry.drawWidgets); + }, + }; + } + } + + // define menu entries with circulating values + var entries = { + colors: { + title: ["red", "yellow", "green", "cyan", "blue", "magenta"], + value: ["100", "110", "010", "011", "001", "101"], + drawWidgets: true + }, + image: { + title: images ? undefined : ["no found"], + value: images ? ["default", "random"].concat(Object.keys(images)) : ["default"], + exec: function(value) { + // draw selected image in upper right corner + var x = 152, + y = 26, + i = images ? images[value] : false; + g.reset(); + if (!i) g.setColor(g.theme.bg); + g.drawImage(atob("Dw+BADAYYDDAY//v////////////////////////3/8A"), x + 4, y); + if (i) g.drawImage(atob(i.str), x + i.x, y - 9 + i.y); + i = undefined; + } + }, + touchOn: { + title: ["on def clk", "on all clk", "clk+launch", "clk+setting", "except apps", "always on"], + value: ["", "clock", "clock,setting.app.js", "clock,launch", "clock,setting.app.js,launch", "always"], + drawWidgets: true + }, + dragDelay: { + factor: 1, + unit: "ms", + min: 0, + max: 1000, + step: 50 + }, + minValue: { + factor: 100, + unit: "%", + min: 1, + max: 100, + step: 1 + }, + unlockSide: { + title: ["off", "left", "right", "top", "bottom", "front", "back"], + value: ["", "left", "right", "top", "bottom", "front", "back"] + }, + tapOn: { + title: ["on locked", "on unlocked", "always on"], + value: ["locked", "unlocked", "always"] + }, + tOut: { + factor: 0.001, + unit: "s", + min: 0.5, + max: 10, + step: 0.5 + } + }; + // copy duplicated entries + entries.tapSide = entries.unlockSide; + entries.minFlash = entries.minValue; + + // show main menu + function showMain() { + var mainMenu = E.showMenu({ + "": { + title: "Light Switch" + }, + "< Back": () => back(), + "-- Widget --------": 0, + "Bulb col": getEntry("colors"), + "Image": getEntry("image"), + "-- Control -------": 0, + "Touch": getEntry("touchOn"), + "Drag Delay": getEntry("dragDelay"), + "Min Value": getEntry("minValue"), + "-- Unlock --------": 0, + "TapSide": getEntry("unlockSide"), + "-- Flash ---------": 0, + "TapSide ": getEntry("tapSide"), + "Tap": getEntry("tapOn"), + "Timeout": getEntry("tOut"), + "Min Value ": getEntry("minFlash") + }); + } + + // draw main menu + showMain(); +}) diff --git a/apps/lightswitch/settings.json b/apps/lightswitch/settings.json new file mode 100644 index 000000000..3d88e2282 --- /dev/null +++ b/apps/lightswitch/settings.json @@ -0,0 +1,72 @@ +/*** Available settings for lightswitch *** + + * colors: string // colors used for the bulb + // set with g.setColor(val*col[0], val*col[1], val*col[2]) + "100" -> red + "110" -> yellow + "010" -> green + "011" -> cyan (default) + "001" -> blue + "101" -> magenta + + * image: string // + "default" -> + "random" -> + + * touchOn: string // select when widget touch is active + "" -> only on default clock + "clock" -> on all clocks + "clock,launch" -> on all clocks and lanchers (default) + "always" -> always + + * dragDelay: int // drag listener reset time in ms + // time until a drag is needed to activate backlight changing mode + 0 -> disabled + 500 -> (default) + + * minValue: float // minimal brightness level that can be set by dragging + 0.05 to 1, 0.1 as default + + * unlockSide: string // side of the watch to double tap on to flash backlight + 0/false/undefined -> backlight flash disabled + right/left/up/down/front/back -> side to tap on (default: right) + + * tapSide: string // side of the watch to double tap on to flash backlight + 0/false/undefined -> backlight flash disabled + right/left/up/down/front/back -> side to tap on (default: right) + + * tapOn: string // select when tap to flash backlight is active + "locked" -> only when locked + "unlocked" -> only when unlocked (default) + "always" -> always + + * tOut: int // backlight flash timeout in ms + 3000 (default) + + * minFlash: float // minimal brightness level when + 0.05 to 1, 0.2 as default + + *** Cached values *** + + * value: float // active brightness value (0-1) + 1 (default) + * isOn: bool // active backlight status + true (default) + + */ +{ + // settings + "colors": "011", + "image": "default", + "touchOn": "clock,launch", + "dragDelay": 500, + "minValue": 0.1, + "unlockSide": "", + "tapSide": "right", + "tapOn": "always", + "tOut": 2000, + "minFlash": 0.2, + // cached values + "value": 1, + "isOn": true +} diff --git a/apps/lightswitch/widget.js b/apps/lightswitch/widget.js new file mode 100644 index 000000000..119a114fe --- /dev/null +++ b/apps/lightswitch/widget.js @@ -0,0 +1,255 @@ +(function() { + // load settings + var settings = Object.assign({ + colors: "011", + image: "default", + touchOn: "clock,launch", + dragDelay: 500, + minValue: 0.1, + unlockSide: "", + tapSide: "right", + tapOn: "always", + tOut: 3000, + value: 1, + isOn: true + }, require("Storage").readJSON("lightswitch.json", true) || {}); + + // write widget with loaded settings + WIDGETS.lightswitch = Object.assign(settings, { + + // set area, sortorder, width and dragStatus + area: "tr", + sortorder: 10, + width: 23, + dragStatus: "off", + + // internal function // + // write settings to storage + writeSettings: function(changes) { + // define variables + var filename = "lightswitch.json"; + var storage = require("Storage"); + + // write changes into json file + storage.writeJSON(filename, Object.assign( + storage.readJSON(filename, true) || {}, changes + )); + + // clear variables + filename = undefined; + storage = undefined; + }, + + // internal function // + // draw inner bulb circle + drawInnerBulb: function(value) { + // check if active or value is set + if (value || this.isOn) { + // use set value or load from widget + value = value || this.value; + // calculate color + g.setColor( + value * this.colors[0], + value * this.colors[1], + value * this.colors[2] + ); + } else { + // backlight off + g.setColor(0); + } + // draw circle + g.drawImage(atob("CwuBAB8H8f9/////////f8fwfAA="), this.x + 6, this.y + 6); + }, + + // internal function // + // draw widget icon + drawIcon: function(locked) { + // define icons + var icons = { + bulb: "DxSBAAAAD4BgwYDCAIgAkAEgAkAEgAiAIYDBgwH8A/gH8A/gH8AfABwA", + shine: "FxeBAAgQIAgggBBBABAECAAALAABhAAEAAAAAAAAAAAAAAAHAABwAAAAAAAAAAAAAAAQABDAABoAAAgQBABABACACAIACAA=", + lock: "DxCBAAAAH8B/wMGBgwMGBgwf/H/8+Pnx8/fn78/fn/8f/A==", + image: "DxSBAA/gP+Dg4YDDAYYDDAYYDH/9////////////////////////+//g" + }; + // read images + var images = require("Storage").readJSON("lightswitch.images.json", true) || false; + + // select image if images are found + var image = (!images || image === "default") ? false : + (function(i) { + if (i === "random") { + i = Object.keys(images); + i = i[parseInt(Math.random() * i.length)]; + } + return images[i]; + })(this.image); + + // clear widget area + g.reset().clearRect(this.x, this.y, this.x + this.width, this.y + 24); + + // draw shine if backlight is active + if (this.isOn) g.drawImage(atob(icons.shine), this.x, this.y); + + // draw icon depending on lock status and image + g.drawImage(atob(!locked ? icons.bulb : image ? icons.image : icons.lock), this.x + 4, this.y + 4); + + // draw image on lock + if (locked && image) g.drawImage(atob(image.str), this.x + image.x, this.y + image.y); + + // draw bulb color depending on backlight status + if (!locked) this.drawInnerBulb(); + + // clear variables + icons = undefined; + images = undefined; + image = undefined; + }, + + // internal function // + // change or switch backlight and icon and write to storage if not skipped + changeValue: function(value, skipWrite) { + // check value + if (value) { + // set new value + this.value = value; + // check backlight status + if (this.isOn) { + // redraw only inner bulb circle + this.drawInnerBulb(); + } else { + // activate backlight + this.isOn = true; + // redraw complete widget icon + this.drawIcon(false); + } + } else { + // switch backlight status + this.isOn = !this.isOn; + // redraw widget icon + this.drawIcon(false); + } + // set brightness + Bangle.setLCDBrightness(this.isOn ? this.value : 0); + // write changes to storage if not skipped + if (!skipWrite) this.writeSettings({ + isOn: this.isOn, + value: this.value + }); + }, + + // listener function // + // drag listener for brightness change mode + dragListener: function(event) { + // setup shortcut to this widget + var w = WIDGETS.lightswitch; + + // first drag recognised + if (event.b && typeof w.dragStatus === "number") { + // reset drag timeout + clearTimeout(w.dragStatus); + // change drag status to indicate ongoing drag action + w.dragStatus = "ongoing"; + // feedback for brightness change mode + Bangle.buzz(50); + } + + // read y position, pleasant usable area 20-170 + var y = event.y; + y = y < 20 ? 0 : y > 170 ? 150 : y - 20; + // calculate brightness respecting minimal value in settings + var value = (1 - Math.round(y / 1.5) / 100) * (1 - w.minValue) + w.minValue; + + // change brigthness value, skip write to storage while still touching + w.changeValue(value, event.b); + + // on touch release remove drag listener and reset drag status to indicate stopped drag action + if (!event.b) { + Bangle.removeListener("drag", w.dragListener); + w.dragStatus = "off"; + } + + // clear variables + w = undefined; + y = undefined; + value = undefined; + }, + + // listener function // + // touch listener for light control + touchListener: function(button, cursor) { + // setup shortcut to this widget + var w = WIDGETS.lightswitch; + + // skip all if drag action ongoing + if (w.dragStatus === "off") { + + // check if inside widget area + if (!(!w || cursor.x < w.x || cursor.x > w.x + w.width || + cursor.y < w.y || cursor.y > w.y + 23)) { + // first touch feedback + Bangle.buzz(25); + // check if drag is disabled + if (w.dragDelay) { + // add drag listener + Bangle.on("drag", w.dragListener); + // set drag timeout + w.dragStatus = setTimeout((w) => { + // remove drag listener + Bangle.removeListener("drag", w.dragListener); + // clear drag timeout + if (typeof w.dragStatus === "number") clearTimeout(w.dragStatus); + // reset drag status to indicate stopped drag action + w.dragStatus = "off"; + }, w.dragDelay, w); + } + // switch backlight + w.changeValue(); + } + + } + + // clear variable + w = undefined; + }, + + // main widget function // + // display and setup/reset function + draw: function(locked) { + // setup shortcut to this widget + var w = WIDGETS.lightswitch; + + // set lcd brightness on unlocking + // all other cases are catched by the boot file + if (locked === false) Bangle.setLCDBrightness(w.isOn ? w.value : 0); + + // read lock status + locked = Bangle.isLocked(); + + // remove listeners to prevent uncertainties + Bangle.removeListener("lock", w.draw); + Bangle.removeListener("touch", w.touchListener); + Bangle.removeListener("tap", require("lightswitch.js").tapListener); + + // draw widget icon + w.drawIcon(locked); + + // add lock listener + Bangle.on("lock", w.draw); + + // add touch listener to control the light depending on settings + if (w.touchOn === "always" || !global.__FILE__ || + w.touchOn.includes(__FILE__) || + w.touchOn.includes(require("Storage").readJSON(__FILE__.replace("app.js", "info")).type)) + Bangle.on("touch", w.touchListener); + + // add tap listener to unlock and/or flash backlight + if (w.unlockSide || w.tapSide) Bangle.on("tap", require("lightswitch.js").tapListener); + + // clear variables + w = undefined; + } + }); + + // clear variable + settings = undefined; +})() diff --git a/apps/limelight/ChangeLog b/apps/limelight/ChangeLog new file mode 100644 index 000000000..9db0e26c5 --- /dev/null +++ b/apps/limelight/ChangeLog @@ -0,0 +1 @@ +0.01: first release diff --git a/apps/limelight/README.md b/apps/limelight/README.md new file mode 100644 index 000000000..49b858127 --- /dev/null +++ b/apps/limelight/README.md @@ -0,0 +1,19 @@ +# Limelight + *Simple configurable analogue clock based on the work of @Andreas_Rozek [Simple_Clock](https://github.com/espruino/BangleApps/tree/master/apps/simple_clock)* + +![](screenshot_limelight.png) + +* Selection of different fonts +* Settings menu where you can select font, or switch to Vector font and try a range of sizes +* Reduction by 100 lines of code, demonstrating that there is no need for a custom widget draw method +* Full screen option (widgets are loaded but not displayed) + +![](screenshot_gochihand.png) +![](screenshot_monoton.png) +![](screenshot_grenadier.png) + +Many thanks for @Andreas_Rozek for his pioneering work on building an analogue clock toolkit for the Bangle 2. + +Limelight 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/limelight/limelight.app.js b/apps/limelight/limelight.app.js new file mode 100644 index 000000000..20d79deeb --- /dev/null +++ b/apps/limelight/limelight.app.js @@ -0,0 +1,263 @@ +/* + * Limelight analoguce clock with bolted hands + * Based on the work of @Andreas_Rozek + * [Simple_Clock](https://github.com/espruino/BangleApps/tree/master/apps/simple_clock) + * + * . Demonstrates simpler approach to establishing the available size of the appRect in relation + * to widgets, avoids having to take on the responsibility for managing the widget draw. + * . Demonstrates a settings menu and various configuration options + * . Demonstrates fullscreen verses, widgets and app area. + * + */ + +g.clear(); + +const SETTINGS_FILE = "limelight.json"; +var UPDATE_PERIOD; +var drawTimeout; + +function loadSettings() { + settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; + settings.secondhand = settings.secondhand||false; + settings.font = settings.font||"Limelight"; + settings.vector = settings.vector||false; + settings.fullscreen = settings.fullscreen||false; + settings.vector_size = settings.vector_size||42; + UPDATE_PERIOD = (settings.secondhand ? 1000 : 60000); +} + +loadSettings(); + +// if we are not full screen then load and draw the widgets so that Bangle.appRect gets set +if (!settings.fullscreen) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); +} + +// fonts.google.com +Graphics.prototype.setFontLimelight = function(scale) { + // Actual height 28 (28 - 1) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAeAAAAAD8AAAAAf4AAAAB/gAAAAH+AAAAAf4AAAAB/gAAAAD8AAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAPwAAAAH8AAAAD+AAAAD+AAAAB/AAAAA/gAAAAfwAAAAD4AAAAAMAAAAAAAAAAAAAAAAAAAAA/gAAAA//wAAAP//wAAB///wAAP///gAA///+AAH///8AAf///4AD////gAP///+AA////4AD////gAMAAAGAAwAAAYADAAABgAMAAAGAAwAAAwABgAADAAHAAAYAAOAADgAAeAA8AAAfh/AAAAf/wAAAAHgAAAAAAAAAAGAAAAAAYAAAAABAAAAAAMAAAAAAwAAAAAD///+AAf///4AB////gAH///+AAf///4AD////gAP///+AA////4AH////gAf///+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgABAAAeAAOAAH4AAwAA/gAGAAP+AAYAB/4ADAAf/gAMAD/+AAwAf/4ADAH//gAMA//+AAwH//4ADB//9gAOP//GAA///wYAD//+BgAH//gGAAf/8AYAA//ABgAB/4AGAAD+AAYAADAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAHAAAwAAGAAHAAAMAAYAAAwADAEABgAMAwAGAAwDAAYADAMABgAMAwAGAAwDAAYAD////gAP///+AA////4AD////gAP///8AAf///wAB/7//AAD/H/4AAP4f/AAAPA/4AAAAA+AAAAAAAAAAAAAAAAAAAGAAAAAB8AAAAAPwAAAADzAAAAAcMAAAAHgwAAAA8DAAAAHAMAAAB4AwAAAOADAAADwAMAAAcAAwAAD///+AA////4AD////gAP///+AA////4AD////gAP///+AA////4AD////gAP///+AAAAAMAAAAAAwAAAAADAAAAAAcAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAHAAAH4AOAAP/gAcAA+GAAwADAwABgAMDAAGAAwMAAYADAwABgAMDAAGAAwMAAYADA///gAMD//+AAwP//4ADA///AAMB//8AAwH//wADAP/+AAIAf/wAAAA/+AAAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAD/8AAAA//8AAAH//4AAB///wAAH///gAA////AAH///8AAf///4AD////gAP///+AAwAAAYADAYABgAMBgAGAAwGAAYADAYABgAMBgAGAAwGAAwABgYADAAHAwAYAAMDgHAAAAHh4AAAAP/AAAAAHgAAAAAAAAAAAAAAAA+AAAAAD4AAAAAMAAAAAAwAAAAADAAAAAAMAA/+AAwB//4ADB///gAM///+AA////4AD////gAP///+AA////4AD////gAP///+AA////4AD//+AAAP/wAAAA/wAAAAD4AAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAA/g/wAAH/GDgAA/+wGAAD//AMAAf/4AwAB//gBgAP//AGAAx/+AYADD/4BgAMH/wGAAwP/AYACA/+BgAMB/8GAAwD/wYADAP/jgAMBf/+AAYH//wABgz//AADHH/4AAH4f/gAAOA/8AAAAB/AAAAAAwAAAAAAAAAAAAAAAAAeAAAAAH/AAAAB8eAAAAGAcBgAAwAwHAAGABgMAAYAGAYADAAIBgAMAAwGAAwADAYADAAYBgAMAAgGAAwAAAYAD////gAP///+AA////wAB////AAH///4AAP///AAA///8AAA///AAAB//4AAAB/+AAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAeAAAD8D8AAAf4f4AAB/h/gAAH+H+AAAf4f4AAB/h/gAAD8D8AAAHgHgAAAAAAAAAAAAAAA="), 46, atob("DQ0aExgZHRkbGBsbDQ=="), 40+(scale<<8)+(1<<16)); +} + +// fonts.google.com +Graphics.prototype.setFontGochiHand = function(scale) { + // Actual height 29 (31 - 3) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAB4AAAAAD4AAAAAB4AAAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAH/gAAAD//gAAD///gAD///+AAH///AAAH//gAAAH/wAAAAHwAAAAAAAAwAAAAAP+AAAAA//gAAAB//wAAAD//4AAAD8P4AAAHwD8AAAHgB8AAAPgA8AAAPAA8AAAPAA8AAAPAA8AAAPgA8AAAPgA8AAAPgB8AAAHwB4AAAH4D4AAAD+PwAAAD//gAAAB//gAAAA/+AAAAAP8AAAAAAAAAAAAAAAAAAAcAAAAAA8AAAAAB8AAAAAD4AAAAAD4AAAAAHwAAAAAHgAAAAAPgAAAAAPgAAAAAf/AAAAAf//wAAAP//wAAAH//wAAAAf/wAAAAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAA8AAAeAB8AAA+AD8AAB+AH8AAB4AP8AAD4AP8AADwAf8AADwB+8AAD4D88AAD4H48AAD//w+AAB//g+AAB//A+AAA/8A+AAAPwA+AAAAAA+AAAAAAcAAAAAAIAAAAAAAAAA8AAAAAB8AAAAAB8AAAAAB4AHgAAD4AHwAAD4AH4AADwPH8AADwfB8AADwfA8AAD4fA+AAD4fA+AAB//A+AAB//A+AAA//A8AAA//x8AAAPP/8AAAAH/4AAAAD/wAAAAB/gAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAH8AAAAAP+AAAAA/+AAAAB/+AAAAD8+AAAAP4+AAAB/weAAAD/weAAAD/8eAAAB///AAAA///AAAAH//8AAAAf//AAAAD//AAAAAf/AAAAAf+AAAAAfAAAAAAMAAAAAAAAAAAAAAAAAAPA/gAAAfh/wAAA/x/4AAA/x/4AAB/4/8AAB74B8AAB58A8AAB58A+AAB5+A+AAB4+A+AAB4+A+AAB4fA+AAB4fg+AAB4Pg8AAB4P58AAB4H/4AAB4D/4AAB4D/wAAAQA/gAAAAAAAAAAAAAAAAAAAAAAAAAf8AAAAB/+AAAAD//gAAAH//gAAAPwfwAAAPgf4AAAfAf4AAAeA/8AAAeA98AAAeA88AAAfB48AAAfB48AAAPB48AAAOB48AAAAB98AAAAB/8AAAAA/4AAAAA/wAAAAAfgAAA8AAAAAA8BAAAAA8HgAAAA8HgAAAA8HgAAAA8HgAAAA8HgAAAA+HgAAAA+HgAAAA+HgAAAAfHgAAAAf//+AAAf//+AAAP//+AAAH//8AAAAPwAAAAAHgAAAAAHwAAAAAHwAAAAAHwAAAAADwAAAAADgAAAAAAAAAAAAAAAAAAAB/AAAAP3/wAAAf//wAAA///4AAA//D8AAB9+B8AAB4+A8AAB4+A+AAB4+A+AAB4+A+AAB8+A+AAB8+A+AAA/+A8AAA//A8AAAf/x8AAAP//4AAAH//wAAAAD/gAAAAB/AAAAAAAAAAAAAAAAAAD/AAAAAD/gAAAAH/gAAAAP/wAAAAPHwAAAAPDwAAAAeDwAAAAeDwAAAAeDwAAAAeHwAAAAeHgAAAAePgAAAAefAAAAAf/AAAAAf///gAAf///wAAP///wAAP///gAAH8AAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8BwAAAA8B4AAAA+D4AAAA8B4AAAAcB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="), 46, atob("DQoYERUWFBYVFhcVDQ=="), 42+(scale<<8)+(1<<16)); +} + +// free for commercial use +// https://www.1001fonts.com/search.html?search=Grenadier+NF +Graphics.prototype.setFontGrenadierNF = function(scale) { + // Actual height 39 (39 - 1) + g.setFontCustom(atob("AAAAAAAAAAAAB4AAAAAAPAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAEAAAAAAPgAAAAA/8AAAAB//gAAAD//4AAAP//wAAAf//AAAB//+AAAD//8AAAB//wAAAAP/gAAAAB+AAAAAAMAAAAAAAAAAAAAAAAB4AAAAAD/8AAAAB//4AAAA///wAAAP8D/AAAD8AD8AAA/AAPwAAPgAAfAAD4AAB8AAeAAAHgAHwAAA+AA8AAADwAHgAAAeAB4AAAB4APAAAAPAB4AAAB4APAAAAPAB4AAAB4APAAAAPAB4AAAB4APAAAAPAB4AAAB4APgAAAfAA8AAADwAHwAAA+AAeAAAHgAD4AAB8AAPgAAfAAA+AAHwAAD8AD8AAAP8D/AAAA///wAAAD//8AAAAH/+AAAAAD8AAAAAAAAAAAAAAAAAAABAAAAAAAcAAAAAAHwAAAAAB8AAAAAAfgAAAAAH/////AB/////4AP/////AB/////4AAAAAAAAAAAAAAAAAAAAADAAAAAAA4APAAAAPAB4AAAD4APAAAA/AB4AAAP4APAAAD/AB8AAA/4AHgAAPvAA8AAD54ADwAB+PAAfAAfh4AB8AH4PAAPwD+B4AA///gPAAD//wB4AAH/4APAAAP8AAAAAAAAAAAAAAAAAAAPAAAAHAB4AAAB4APAAAAPAB4PAAB4APB4AAPAB+/gAB4AH/8AAfAAf/wADwAB/fAA+AABD8APgAAAPwH4AAAA//+AAAAD//gAAAAH/4AAAAAP8AAAAAAAAAAAAAAAAAAAAAAYAAAAAAPAAAAAAH4AAAAAD/AAAAAB/4AAAAA//AAAAAf94AAAAH+PAAAAD/B4AAAB/gPAAAA/wB4AAAf8APAAAP////4AH/////AD/////4AAAAAPAAAAAAA4AAAAAAAAAAAAAAAAAAAIAAPAAAPAAB4AAf4AAPAA//gAB4AP/8AAPAB/3gAB4APg8AAfAB4DwADwAPAfAA+AB4B8APgAPAP4H4AB4A//+AAPAB//gAB4AH/4AAAAAP8AAAAAAAAAAAAAB4AAAAAD/4AAAAA//wAAAAf//AAAAP+H8AAAH+AHwAAB/gAfAAA/4AB4AAf+AAPAAP/wAA8AH+eAAHgB/ngAA8APw8AAHgB4HgAA8AMA8AAHgAADwAA8AAAeAAPgAAD4AB4AAAPgAfAAAA+AHwAAAH4D8AAAAf//AAAAB//wAAAAD/8AAAAAH8AAAAAAAAAAAAAAAAAAAAAAAYAAAAAAfAB4AAAP4APAAAH/AB4AAH/gAPAAD/wAB4AD/wAAPAB/4AAB4A/8AAAPA/8AAAB4f+AAAAPP+AAAAB//AAAAAP/gAAAAB/gAAAAAPwAAAAAB4AAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAD/gAAAAB//AAAAAf/+AAAAH//4AAAB+AfgAA+fAB8AAP/wAHwAD/+AAeAA//gAB4APj8AAPAB4PAAB4APB4AAPAB4PAAB4APB4AAPAB8fgAB4AH/8AAPAAf/wADwAB/eAA+AADj4APgAAAPwD8AAAA///AAAAD//wAAAAP/4AAAAAf8AAAAAAAAAAAB4AAAAAB/4AAAAA//wAAAAP//AAAAD+H8AAAA/AHwAAAHgAfAAAB8AB4AAAPAAHgAADwAA8AAAeAAHgBADwAAeA4AeAADwfADwAAeP4AeAAD3+ADwAA9/gAfAAH/wAB4AB/4AAPgAf8AAA+AH+AAAD8B/gAAAP//wAAAA//4AAAAD/8AAAAAD+AAAAAAAAAAAAAAAAAAAAAAAeB4AAAADwPAAAAAeB4AAAAAAAAAAAAAAAA=="), 46, atob("Bg4kChURExEaFBoaBg=="), 45+(scale<<8)+(1<<16)); +} + +// fonts.google.com +Graphics.prototype.setFontMonoton = function(scale) { + // Actual height 38 (37 - 0) + g.setFontCustom(atob("AAAAAAAAAAEkAAAAAbYAAAABtgAAAAG2AAAAAbYAAAABtgAAAAG2AAAAASQAAAAAAAAAAAAAAAAAAAB4AAAAB/gAAAB/gAAAB/h4AAB/h/AAB/h/CAB/h/D4B/h/D/B/j/D/APj/D/AAD/D/AAB/D/AAAPD/AAAAD/AAAAB/AAAAAPAAAAAAAAAAAAAAAAAAAAAD/wAAAB//4AAAfAD4AAHj/x4AA5//5wAHfAB5gAzj/5zAHc//5mAbvDBzcDdz/zmwNu+HzZhs3ADu2G2YAHbYbbAANthtsAA2yG2wABtsbbAAG2xtsAA2yG2wADbYbZgAdthm3ADs2DZv/92wM3P/O7AbvAD3YB3P/87gDvP/HMAHPgD7gAOP/+cAAfH+HgAAfgH4AAAf/+AAAAD+AAAAAAAAAAAAAAAAbYAAAABtgAAAAG2AAAAAbYAAAABt////wG3////AbYAAAABt////wG3////AbYAAAABt////wG3////AbYAAAABt////wEn///+AAAAAAAAAAAAAADQAAAUgdoAAF7BtsAA3sG2wAOewbbAB17BtsAc3sG2wDnewbbAdx7BtsHO3sG2w7newbbPd57Btv3OXsGzc73ewZuPc57A2f3nHsDMc5wewG8POB7Ac/zgHsA4Y8AewB+fABSAB/wAAAAAAAAAAAAAAAAA0AAAAsHbAAAbYbbAANthtsAA22G2wADbYbbJJNthts22W2G2zbZpIbbNtm2xts22bbG2zbZJIbbNttthtv2322Gzfbu7YNuO3HZg3f7P5sDuf2OMwGeHPHmAO/2f8wAccOOOAA/PfvwAA/4f8AAAAAAAAAAAAAAAAABkgAAAA/bAAAAPtsAAAD52wAAA8fbAAAfH9sAAHz42wAB8+fbAAePn9sADjx82wAJ8ePbAAfPj9sADz482wAI+fDbAAfHwNsADx8A2wAM+D/b+APgP9v4D4AA2wAMAD/b+AAAP9v4AAAA2wAAAADSAAAAAAAAAAAAAAAAAAAAMAaf/8AwBt//wJgG2AAA3Abf/8JsBt//w2wG2AABtgbf/822BtgADbYG2NvNtgbY28SSBtjbxtsG2NvG2gbY28SSBtjbzbYG2Nv9tgbY23m2BtjNg2YG2Gz+bAbYZnzcBtgzg5gG2Dn/MASQHHzgAAAPg8AAAAP/gAAAAHwAAAAAAAAAAAAAAAAA//4AAAf//8AAHgAB4AA4//44AGf//5wAzgAB7AGY//52AbP//7MDZwABmwNu//zZhs3//m2G25LTbYbbN7Nthts3sSSG2zexpIbbN7G2xts3sSaG2zezbYbbN7Nthts3v22GbDbezYNsG+HbA3Qbf7sBsB3edgGQDPHuAMAGf9wAQAOOOAAAAfvwAAAAf8AAAAAAAAAAAAAABtgAAAAG2AAAAAbYAAAABtgAAAAG2AAAAAbYAAAMBtgAAPwG2AAP8AbYAf4cBtgf4fwG0f4f4Aaf4f4cAf4f4fwH4f4f4AYf4f4cA/4/w/wHw/w/wAQ/w/wAA/w/wAAHw/wAAAQ/wAAAA/wAAAAHwAAAAAAAAAAAAAAAAAAAA+AfAAAf/P/gADwPwHgA5/OfnAHP+f/OAZwO4HYDM+d/MwNn+3/bBuwZsM2G2e2+bYbb7b9thtskk2yG2zbZtobbNtm2xts22bbG2zbZtsbbNtm2xts22bbG2zbZNsbbNttshtv2322GzfZu7YNuO3HZg3f7v5sBud3OcwHfPvHmAOf3P8wAcAeAOAAf///wAA/4f8AAAAAAAAAAAAAAAAfwAAAAH/wAAAA4DwAIAGfzgAwAz/3AJgGYDsA2AzP2YDsDZz9g2wNszbDNhs3ns22G2zezbYbbN5NthtsTkSSG2xORtMbbE5G2xts3sySG2zezbYbbN7Nths2ABm2Cbf/+3YNmf/nbAzeAB7MBuf/+dgHcP/DuAO///9wAc///OAA8AADwAA///8AAA///AAAAAAAAAAAAAAAAAAAAAAA2xtgAADbG2AAANsbYAAA2xtgAADbG2AAANsbYAAAkhJAAAAAAAAAAAAAAAA"), 46, atob("ChIiERcYGRwfGSAfCw=="), 40+(scale<<8)+(1<<16)); +} + +/* + * If only 1 widget is loaded at the top, then Bangle.appRect changes + * to report as if widgets were loaded at the bottom as well. The + * other option would be for Bangle.appRect to adjust for different + * combinations EG: no widgets, wigets on top, widgets on bottom and + * widgets on top and bottom areas, but it does not at present. + * + * Example of Bangle.appRect with 3 widges on the top, note h = 152, not 176 + * ={ x: 0, y: 24, w: 176, h: 152, x2: 175, y2: 175 } + * + * With the example below we are going assume that the bottom widget + * space is not used. + * + */ +const CenterX = g.getWidth()/2; +const CenterY = (g.getHeight()/2) + (Bangle.appRect.y/2); +const outerRadius = (g.getHeight() - Bangle.appRect.y)/2; + +if (settings.fullscreen) { + Bangle.loadWidgets(); + /* + * We load the widgets as some like widpedom accumualte the step count. + * 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 and change the + * widgets area to the top bar doesn't get cleared. + */ + for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} +} + +function debug(o) { + //console.log(o); +} + +debug("limelight.app.js"); +debug("CenterX=" + CenterX); +debug("CenterY=" + CenterY); +debug("outerRadius=" + outerRadius); +debug("y12=" + (CenterY - outerRadius)); +debug("y6=" + (CenterY + outerRadius)); + +let HourHandLength = outerRadius * 0.5; +let HourHandWidth = 2*5, halfHourHandWidth = HourHandWidth/2; + +let MinuteHandLength = outerRadius * 0.7; +let MinuteHandWidth = 2*3, halfMinuteHandWidth = MinuteHandWidth/2; + +let SecondHandLength = outerRadius * 0.9; +let SecondHandOffset = halfHourHandWidth + 10; + +let outerBoltRadius = halfHourHandWidth + 2, innerBoltRadius = outerBoltRadius - 4; +let HandOffset = outerBoltRadius + 4; + +let twoPi = 2*Math.PI, deg2rad = Math.PI/180; +let Pi = Math.PI; +let halfPi = Math.PI/2; + +let sin = Math.sin, cos = Math.cos; + +let sine = [0, sin(30*deg2rad), sin(60*deg2rad), 1]; + +let HandPolygon = [ + -sine[3],-sine[0], -sine[2],-sine[1], -sine[1],-sine[2], -sine[0],-sine[3], + sine[0],-sine[3], sine[1],-sine[2], sine[2],-sine[1], sine[3],-sine[0], + sine[3], sine[0], sine[2], sine[1], sine[1], sine[2], sine[0], sine[3], + -sine[0], sine[3], -sine[1], sine[2], -sine[2], sine[1], -sine[3], sine[0], +]; + +let HourHandPolygon = new Array(HandPolygon.length); +for (let i = 0, l = HandPolygon.length; i < l; i+=2) { + HourHandPolygon[i] = halfHourHandWidth*HandPolygon[i]; + HourHandPolygon[i+1] = halfHourHandWidth*HandPolygon[i+1]; + if (i < l/2) { HourHandPolygon[i+1] -= HourHandLength; } + if (i > l/2) { HourHandPolygon[i+1] += HandOffset; } +} +let MinuteHandPolygon = new Array(HandPolygon.length); +for (let i = 0, l = HandPolygon.length; i < l; i+=2) { + MinuteHandPolygon[i] = halfMinuteHandWidth*HandPolygon[i]; + MinuteHandPolygon[i+1] = halfMinuteHandWidth*HandPolygon[i+1]; + if (i < l/2) { MinuteHandPolygon[i+1] -= MinuteHandLength; } + if (i > l/2) { MinuteHandPolygon[i+1] += HandOffset; } +} + +/**** transforme polygon ****/ + +let transformedPolygon = new Array(HandPolygon.length); + +function transformPolygon (originalPolygon, OriginX,OriginY, Phi) { + let sPhi = sin(Phi), cPhi = cos(Phi), x,y; + + for (let i = 0, l = originalPolygon.length; i < l; i+=2) { + x = originalPolygon[i]; + y = originalPolygon[i+1]; + + transformedPolygon[i] = OriginX + x*cPhi + y*sPhi; + transformedPolygon[i+1] = OriginY + x*sPhi - y*cPhi; + } +} + +/**** draw clock hands ****/ + +function drawClockHands () { + let now = new Date(); + + let Hours = now.getHours() % 12; + let Minutes = now.getMinutes(); + let Seconds = now.getSeconds(); + + let HoursAngle = (Hours+(Minutes/60))/12 * twoPi - Pi; + let MinutesAngle = (Minutes/60) * twoPi - Pi; + let SecondsAngle = (Seconds/60) * twoPi - Pi; + + g.setColor(g.theme.fg); + + transformPolygon(HourHandPolygon, CenterX,CenterY, HoursAngle); + g.fillPoly(transformedPolygon); + + transformPolygon(MinuteHandPolygon, CenterX,CenterY, MinutesAngle); + g.fillPoly(transformedPolygon); + + let sPhi = Math.sin(SecondsAngle), cPhi = Math.cos(SecondsAngle); + + if (settings.secondhand) { + g.setColor(g.theme.fg2); + g.drawLine( + CenterX + SecondHandOffset*sPhi, + CenterY - SecondHandOffset*cPhi, + CenterX - SecondHandLength*sPhi, + CenterY + SecondHandLength*cPhi + ); + } + + g.setColor(g.theme.fg); + g.fillCircle(CenterX,CenterY, outerBoltRadius); + + g.setColor(g.theme.bg); + g.drawCircle(CenterX,CenterY, outerBoltRadius); + g.fillCircle(CenterX,CenterY, innerBoltRadius); +} + +function setNumbersFont() { + if (settings.vector) { + g.setFont('Vector', settings.vector_size); + return; + } + + if (settings.font == "GochiHand") + g.setFontGochiHand(); + else if (settings.font == "Grenadier") + g.setFontGrenadierNF(); + else if (settings.font == "Monoton") + g.setFontMonoton(); + else + g.setFontLimelight(); +} + +function drawNumbers() { + g.setColor(g.theme.fg); + setNumbersFont(); + + g.setFontAlign(0,-1); + g.drawString('12', CenterX, CenterY - outerRadius); + + g.setFontAlign(1,0); + g.drawString('3', CenterX + outerRadius, CenterY); + + g.setFontAlign(0,1); + g.drawString('6', CenterX, CenterY + outerRadius); + + g.setFontAlign(-1,0); + g.drawString('9', CenterX - outerRadius,CenterY); +} + +function draw() { + g.setColor(g.theme.bg); + g.fillRect(Bangle.appRect); + + drawClockHands(); + drawNumbers(); + queueDraw(); +} + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, UPDATE_PERIOD - (Date.now() % UPDATE_PERIOD)); +} + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +Bangle.setUI('clock'); +draw(); diff --git a/apps/limelight/limelight.icon.js b/apps/limelight/limelight.icon.js new file mode 100644 index 000000000..f7e74db90 --- /dev/null +++ b/apps/limelight/limelight.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("lksgIqngf/wAFC//+AgUch/4AgMBwAQEh/8Dgf/4AKOEAQKCAYUB//gAoU/DQkPBQYVBGx5SDBQIbDBR0GEAlgFYcHGwh4B+CDHRwL04")) \ No newline at end of file diff --git a/apps/limelight/limelight.png b/apps/limelight/limelight.png new file mode 100644 index 000000000..b1744b28e Binary files /dev/null and b/apps/limelight/limelight.png differ diff --git a/apps/limelight/limelight.settings.js b/apps/limelight/limelight.settings.js new file mode 100644 index 000000000..aacea2f86 --- /dev/null +++ b/apps/limelight/limelight.settings.js @@ -0,0 +1,78 @@ +(function(back) { + const SETTINGS_FILE = "limelight.json"; + + // initialize with default settings... + let s = { + 'vector_size': 42, + 'vector': false, + 'font': "Limelight", + 'secondhand': false, + 'fullscreen': false + } + + // ...and overwrite them with any saved values + // This way saved values are preserved if a new version adds more settings + const storage = require('Storage') + let settings = storage.readJSON(SETTINGS_FILE, 1) || {} + const saved = settings || {} + + // copy settings into variable + for (const key in saved) { + s[key] = saved[key] + } + + function save() { + settings = s + storage.write(SETTINGS_FILE, settings) + } + + var font_options = ["Limelight","GochiHand","Grenadier","Monoton"]; + + E.showMenu({ + '': { 'title': 'Limelight Clock' }, + '< Back': back, + 'Full Screen': { + value: s.fullscreen, + format: () => (s.fullscreen ? 'Yes' : 'No'), + onchange: () => { + s.fullscreen = !s.fullscreen; + save(); + }, + }, + 'Font': { + value: 0 | font_options.indexOf(s.font), + min: 0, max: 3, + format: v => font_options[v], + onchange: v => { + s.font = font_options[v]; + save(); + }, + }, + 'Vector Font': { + value: s.vector, + format: () => (s.vector ? 'Yes' : 'No'), + onchange: () => { + s.vector = !s.vector; + save(); + }, + }, + 'Vector Size': { + value: s.vector_size, + min: 24, + max: 56, + step: 6, + onchange: v => { + s.vector_size = v; + save(); + } + }, + 'Second Hand': { + value: s.secondhand, + format: () => (s.secondhand ? 'Yes' : 'No'), + onchange: () => { + s.secondhand = !s.secondhand; + save(); + }, + } + }); +}) diff --git a/apps/limelight/metadata.json b/apps/limelight/metadata.json new file mode 100644 index 000000000..7c3736e1a --- /dev/null +++ b/apps/limelight/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "limelight", + "name": "Limelight", + "version": "0.01", + "description": "Simple analogue clock (with configurable fonts) based on the work of @Andreas_Rozek (Simple_Clock)", + "icon": "limelight.png", + "readme":"README.md", + "screenshots": [{"url":"screenshot_limelight.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"limelight.app.js","url":"limelight.app.js"}, + {"name":"limelight.settings.js","url":"limelight.settings.js"}, + {"name":"limelight.img","url":"limelight.icon.js","evaluate":true} + ] +} diff --git a/apps/limelight/screenshot_gochihand.png b/apps/limelight/screenshot_gochihand.png new file mode 100644 index 000000000..244b008dc Binary files /dev/null and b/apps/limelight/screenshot_gochihand.png differ diff --git a/apps/limelight/screenshot_grenadier.png b/apps/limelight/screenshot_grenadier.png new file mode 100644 index 000000000..a55896297 Binary files /dev/null and b/apps/limelight/screenshot_grenadier.png differ diff --git a/apps/limelight/screenshot_limelight.png b/apps/limelight/screenshot_limelight.png new file mode 100644 index 000000000..7b12e4cc2 Binary files /dev/null and b/apps/limelight/screenshot_limelight.png differ diff --git a/apps/limelight/screenshot_monoton.png b/apps/limelight/screenshot_monoton.png new file mode 100644 index 000000000..e75b11f5d Binary files /dev/null and b/apps/limelight/screenshot_monoton.png differ diff --git a/apps/locale/ChangeLog b/apps/locale/ChangeLog index 448f8119a..39b825e02 100644 --- a/apps/locale/ChangeLog +++ b/apps/locale/ChangeLog @@ -14,3 +14,5 @@ 0.12: Fixed nl_NL formatting, because the full months won't fit on the Bangle.js2's screen 0.13: Now use shorter de_DE date format to more closely match other languages for size 0.14: Added some first translations for Messages in nl_NL +0.15: Fixed sv_SE formatting, long date does not work well for Bangle.js2 + Added Swedish localisation with English text \ No newline at end of file diff --git a/apps/locale/locales.js b/apps/locale/locales.js index cf511c54f..428e0c773 100644 --- a/apps/locale/locales.js +++ b/apps/locale/locales.js @@ -276,13 +276,31 @@ var locales = { temperature: "°C", ampm: { 0: "fm", 1: "em" }, timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, - datePattern: { 0: "%A %B %d %Y", "1": "%Y-%m-%d" }, // söndag 1 mars 2020 // 2020-03-01 + datePattern: { 0: "%b %d %Y", "1": "%Y-%m-%d" }, // feb 1 2020 // 2020-03-01 abmonth: "jan,feb,mars,apr,maj,juni,juli,aug,sep,okt,nov,dec", month: "januari,februari,mars,april,maj,juni,juli,augusti,september,oktober,november,december", abday: "sön,mån,tis,ons,tors,fre,lör", day: "söndag,måndag,tisdag,onsdag,torsdag,fredag,lördag", trans: { yes: "ja", Yes: "Ja", no: "nej", No: "Nej", ok: "ok", on: "on", off: "off" } }, + "en_SE": { // Swedish localisation with English text + lang: "en_SE", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "kr", + int_curr_symbol: "SKR", + speed: 'kmh', + distance: { "0": "m", "1": "km" }, + temperature: '°C', + ampm: { 0: "", 1: "" }, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "%B %d %Y", "1": "%Y-%m-%d" }, // March 1 2020 // 2020-03-01 + 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", + // No translation for english... + }, "en_NZ": { lang: "en_NZ", decimal_point: ".", diff --git a/apps/locale/metadata.json b/apps/locale/metadata.json new file mode 100644 index 000000000..c8908c7a7 --- /dev/null +++ b/apps/locale/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "locale", + "name": "Languages", + "version": "0.15", + "description": "Translations for different countries", + "icon": "locale.png", + "type": "locale", + "tags": "tool,system,locale,translate", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "custom": "locale.html", + "storage": [ + {"name":"locale"} + ], + "sortorder": -10 +} diff --git a/apps/ltherm/metadata.json b/apps/ltherm/metadata.json new file mode 100644 index 000000000..83b295a3d --- /dev/null +++ b/apps/ltherm/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "ltherm", + "name": "Localized Thermometer", + "shortName": "Thermometer", + "version": "0.01", + "description": "Displays the current temperature in localized units.", + "icon": "thermf.png", + "tags": "tool", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "readme": "README.md", + "storage": [ + {"name":"ltherm.app.js","url":"app.js"}, + {"name":"ltherm.img","url":"icon.js","evaluate":true} + ] +} diff --git a/apps/magnav/metadata.json b/apps/magnav/metadata.json new file mode 100644 index 000000000..cba9a1ac3 --- /dev/null +++ b/apps/magnav/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "magnav", + "name": "Navigation Compass", + "version": "0.05", + "description": "Compass with linear display as for GPSNAV. Has Tilt compensation and remembers calibration.", + "screenshots": [{"url":"screenshot-b2.png"},{"url":"screenshot-light-b2.png"}], + "icon": "magnav.png", + "tags": "tool,outdoors", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"magnav.app.js","url":"magnav_b1.js","supports":["BANGLEJS"]}, + {"name":"magnav.app.js","url":"magnav_b2.js","supports":["BANGLEJS2"]}, + {"name":"magnav.img","url":"magnav-icon.js","evaluate":true} + ], + "data": [{"name":"magnav.json"}] +} diff --git a/apps/mandel/metadata.json b/apps/mandel/metadata.json new file mode 100644 index 000000000..da616a38d --- /dev/null +++ b/apps/mandel/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "mandel", + "name": "Mandelbrot", + "shortName": "Mandel", + "version": "0.01", + "description": "Draw a zoomable Mandelbrot set", + "icon": "mandel.png", + "tags": "game", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"mandel.app.js","url":"mandel.min.js"}, + {"name":"mandel.img","url":"mandel-icon.js","evaluate":true} + ] +} diff --git a/apps/mandelbrotclock/metadata.json b/apps/mandelbrotclock/metadata.json new file mode 100644 index 000000000..852855184 --- /dev/null +++ b/apps/mandelbrotclock/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "mandelbrotclock", + "name": "Mandelbrot Clock", + "version": "0.01", + "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": "mandelbrotclock.app.js", "url": "mandelbrotclock.js" }, + { + "name": "mandelbrotclock.img", + "url": "mandelbrotclock-icon.js", + "evaluate": true + } + ] +} diff --git a/apps/marioclock/metadata.json b/apps/marioclock/metadata.json new file mode 100644 index 000000000..a0282405e --- /dev/null +++ b/apps/marioclock/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "marioclock", + "name": "Mario Clock", + "version": "0.15", + "description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.", + "icon": "marioclock.png", + "type": "clock", + "tags": "clock,mario,retro", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": false, + "screenshots": [{"url":"bangle1-mario-clock-screenshot.png"}], + "storage": [ + {"name":"marioclock.app.js","url":"marioclock-app.js"}, + {"name":"marioclock.img","url":"marioclock-icon.js","evaluate":true} + ] +} diff --git a/apps/matrixclock/metadata.json b/apps/matrixclock/metadata.json new file mode 100644 index 000000000..c4a72988a --- /dev/null +++ b/apps/matrixclock/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "matrixclock", + "name": "Matrix Clock", + "version": "0.02", + "description": "inspired by The Matrix, a clock of the same style", + "icon": "matrixclock.png", + "screenshots": [{"url":"screenshot_matrix.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"matrixclock.app.js","url":"matrixclock.js"}, + {"name":"matrixclock.img","url":"matrixclock-icon.js","evaluate":true} + ] +} diff --git a/apps/mclock/metadata.json b/apps/mclock/metadata.json new file mode 100644 index 000000000..513f823a1 --- /dev/null +++ b/apps/mclock/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "mclock", + "name": "Morphing Clock", + "version": "0.07", + "description": "7 segment clock that morphs between minutes and hours", + "icon": "clock-morphing.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "screenshots": [{"url":"bangle1-morphing-clock-screenshot.png"}], + "storage": [ + {"name":"mclock.app.js","url":"clock-morphing.js"}, + {"name":"mclock.img","url":"clock-morphing-icon.js","evaluate":true} + ], + "sortorder": -9 +} diff --git a/apps/mclockplus/metadata.json b/apps/mclockplus/metadata.json new file mode 100644 index 000000000..49cb33f52 --- /dev/null +++ b/apps/mclockplus/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "mclockplus", + "name": "Morph Clock+", + "shortName": "Morph Clock+", + "version": "0.03", + "description": "Morphing Clock with more readable seconds and date and additional stopwatch", + "icon": "mclockplus.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"mclockplus.app.js","url":"mclockplus.app.js"}, + {"name":"mclockplus.img","url":"mclockplus-icon.js","evaluate":true} + ] +} diff --git a/apps/menusmall/metadata.json b/apps/menusmall/metadata.json new file mode 100644 index 000000000..aafb7da28 --- /dev/null +++ b/apps/menusmall/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "menusmall", + "name": "Small Menus", + "version": "0.02", + "description": "Replace Bangle.js 2's menus with a version that contains smaller text", + "icon": "app.png", + "type": "boot", + "tags": "system", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"menusmall.boot.js","url":"boot.js"} + ] +} diff --git a/apps/menuwheel/ChangeLog b/apps/menuwheel/ChangeLog index defdb5049..050cf2049 100644 --- a/apps/menuwheel/ChangeLog +++ b/apps/menuwheel/ChangeLog @@ -1 +1,2 @@ 0.01: New menu! +0.02: Clean up touch handler in setUI diff --git a/apps/menuwheel/boot.js b/apps/menuwheel/boot.js index 3e708e9a8..deb15264d 100644 --- a/apps/menuwheel/boot.js +++ b/apps/menuwheel/boot.js @@ -1,8 +1,5 @@ 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; @@ -206,8 +203,13 @@ E.showMenu = function(items) { if (b===1) back(); } } - // note: backHandler is cleaned up at the top of this file Bangle.on('touch', Bangle.backHandler); } return l; }; +// setUI now also needs to clear up our back button touch handler +Bangle.setUI = (old => function() { + if (Bangle.backHandler) Bangle.removeListener("touch", Bangle.backHandler); + delete Bangle.backHandler; + return old.apply(this, arguments); +})(Bangle.setUI); \ No newline at end of file diff --git a/apps/menuwheel/metadata.json b/apps/menuwheel/metadata.json new file mode 100644 index 000000000..1ad042344 --- /dev/null +++ b/apps/menuwheel/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "menuwheel", + "name": "Wheel Menus", + "version": "0.02", + "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"} + ] +} diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 4f0498e92..522534af0 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -24,3 +24,7 @@ 0.15: Don't buzz when Quiet Mode is active 0.16: Fix text wrapping so it fits the screen even if title is big (fix #1147) 0.17: Fix: Get dynamic dimensions of notify icon, fixed notification font +0.18: Use app-specific icon colors + Spread message action buttons out + Back button now goes back to list of messages + If showMessage called with no message (eg all messages deleted) now return to the clock (fix #1267) diff --git a/apps/messages/app.js b/apps/messages/app.js index e36bb699e..3e692a0cc 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -83,7 +83,7 @@ function getMessageImage(msg) { if (s=="calendar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA=="); if (s=="facebook") return getFBIcon(); if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); - if (s=="instagram") return atob("GBiBAf////////////////wAP/n/n/P/z/f/b/eB7/c87/d+7/d+7/d+7/d+7/c87/eB7/f/7/P/z/n/n/wAP////////////////w=="); + if (s=="instagram") return atob("GBiBAAAAAAAAAAAAAAAAAAP/wAYAYAwAMAgAkAh+EAjDEAiBEAiBEAiBEAiBEAjDEAh+EAgAEAwAMAYAYAP/wAAAAAAAAAAAAAAAAA=="); if (s=="gmail") return getNotificationImage(); if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA=="); if (s=="mail") return getNotificationImage(); @@ -101,6 +101,31 @@ function getMessageImage(msg) { if (msg.id=="back") return getBackImage(); return getNotificationImage(); } +function getMessageImageCol(msg,def) { + return { + // generic colors, using B2-safe colors + "calendar": "#f00", + "mail": "#ff0", + "music": "#f0f", + "phone": "#0f0", + "sms message": "#0ff", + // brands, according to https://www.schemecolor.com/?s (picking one for multicolored logos) + // all dithered on B2, but we only use the color for the icons. (Could maybe pick the closest 3-bit color for B2?) + "facebook": "#4267b2", + "gmail": "#ea4335", + "google home": "#fbbc05", + "hangouts": "#1ba261", + "instagram": "#dd2a7b", + "messenger": "#0078ff", + "outlook mail": "#0072c6", + "skype": "#00aff0", + "slack": "#e51670", + "telegram": "#0088cc", + "twitter": "#1da1f2", + "whatsapp": "#4fce5d", + "wordfeud": "#dcc8bd", + }[(msg.src||"").toLowerCase()]||(def !== undefined?def:g.theme.fg); +} function showMapMessage(msg) { var m; @@ -200,7 +225,7 @@ function showMessageSettings(msg) { function showMessage(msgid) { var msg = MESSAGES.find(m=>m.id==msgid); - if (!msg) return checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0}); // go home if no message found + if (!msg) return checkMessages({clockIfNoMsg:1,clockIfAllRead:0,showMsgIfUnread:0}); // go home if no message found if (msg.src=="Maps") { cancelReloadTimeout(); // don't auto-reload to clock now return showMapMessage(msg); @@ -224,10 +249,11 @@ function showMessage(msgid) { {type:"btn", src:getBackImage(), cb:()=>{ msg.new = false; saveMessages(); // read mail cancelReloadTimeout(); // don't auto-reload to clock now - checkMessages({clockIfNoMsg:1,clockIfAllRead:0,showMsgIfUnread:1}); + checkMessages({clockIfNoMsg:1,clockIfAllRead:0,showMsgIfUnread:0}); }} // back ]; if (msg.positive) { + buttons.push({fillx:1}); buttons.push({type:"btn", src:getPosImage(), cb:()=>{ msg.new = false; saveMessages(); cancelReloadTimeout(); // don't auto-reload to clock now @@ -236,6 +262,7 @@ function showMessage(msgid) { }}); } if (msg.negative) { + buttons.push({fillx:1}); buttons.push({type:"btn", src:getNegImage(), cb:()=>{ msg.new = false; saveMessages(); cancelReloadTimeout(); // don't auto-reload to clock now @@ -248,12 +275,12 @@ function showMessage(msgid) { var body = (lines.length>4) ? lines.slice(0,4).join("\n")+"..." : lines.join("\n"); layout = new Layout({ type:"v", c: [ {type:"h", fillx:1, bgCol:colBg, c: [ - { type:"btn", src:getMessageImage(msg), pad: 3, cb:()=>{ + { type:"btn", src:getMessageImage(msg), col:getMessageImageCol(msg), pad: 3, cb:()=>{ cancelReloadTimeout(); // don't auto-reload to clock now showMessageSettings(msg); }}, { type:"v", fillx:1, c: [ - {type:"txt", font:fontSmall, label:msg.src||"Message", bgCol:colBg, fillx:1, pad:2, halign:1 }, + {type:"txt", font:fontSmall, label:msg.src||/*LANG*/"Message", bgCol:colBg, fillx:1, pad:2, halign:1 }, title?{type:"txt", font:titleFont, label:title, bgCol:colBg, fillx:1, pad:2 }:{}, ]}, ]}, @@ -310,7 +337,9 @@ function checkMessages(options) { body = msg.track; } if (img) { - g.drawImage(img, x+24, r.y+24, {rotate:0}); // force centering + var fg = g.getColor(); + g.setColor(getMessageImageCol(msg,fg)).drawImage(img, x+24, r.y+24, {rotate:0}) // force centering + .setColor(fg); // only color the icon x += 50; } var m = msg.title+"\n"+msg.body; diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json new file mode 100644 index 000000000..901419913 --- /dev/null +++ b/apps/messages/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "messages", + "name": "Messages", + "version": "0.18", + "description": "App to display notifications from iOS and Gadgetbridge", + "icon": "app.png", + "type": "app", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"messages.app.js","url":"app.js"}, + {"name":"messages.settings.js","url":"settings.js"}, + {"name":"messages.img","url":"app-icon.js","evaluate":true}, + {"name":"messages.wid.js","url":"widget.js"}, + {"name":"messages","url":"lib.js"} + ], + "data": [{"name":"messages.json"},{"name":"messages.settings.json"}], + "screenshots": [{"url":"screenshot.png"},{"url":"screenshot-notify.gif"}], + "sortorder": -9 +} diff --git a/apps/messages/settings.js b/apps/messages/settings.js index fd8ce8f39..c865a37fb 100644 --- a/apps/messages/settings.js +++ b/apps/messages/settings.js @@ -12,12 +12,12 @@ require('Storage').writeJSON("messages.settings.json", settings); } - var vibPatterns = ["Off", ".", "-", "--", "-.-", "---"]; + var vibPatterns = [/*LANG*/"Off", ".", "-", "--", "-.-", "---"]; var currentVib = settings().vibrate; var mainmenu = { - "" : { "title" : "Messages" }, + "" : { "title" : /*LANG*/"Messages" }, "< Back" : back, - 'Vibrate': { + /*LANG*/'Vibrate': { value: Math.max(0,vibPatterns.indexOf(settings().vibrate)), min: 0, max: vibPatterns.length, format: v => vibPatterns[v]||"Off", @@ -25,16 +25,16 @@ updateSetting("vibrate", vibPatterns[v]); } }, - 'Repeat': { + /*LANG*/'Repeat': { value: settings().repeat, min: 2, max: 10, format: v => v+"s", onchange: v => updateSetting("repeat", v) }, - 'Unread timer': { + /*LANG*/'Unread timer': { value: settings().unreadTimeout, min: 0, max: 240, step : 10, - format: v => v?v+"s":"Off", + format: v => v?v+"s":/*LANG*/"Off", onchange: v => updateSetting("unreadTimeout", v) }, }; diff --git a/apps/metronome/metadata.json b/apps/metronome/metadata.json new file mode 100644 index 000000000..7f8582ca5 --- /dev/null +++ b/apps/metronome/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "metronome", + "name": "Metronome", + "version": "0.07", + "readme": "README.md", + "description": "Makes the watch blinking and vibrating with a given rate", + "icon": "metronome_icon.png", + "tags": "tool", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "screenshots": [{"url":"bangle1-metronome-screenshot.png"}], + "storage": [ + {"name":"metronome.app.js","url":"metronome.js"}, + {"name":"metronome.img","url":"metronome-icon.js","evaluate":true}, + {"name":"metronome.settings.js","url":"settings.js"} + ] +} diff --git a/apps/miclock/metadata.json b/apps/miclock/metadata.json new file mode 100644 index 000000000..6eece46b0 --- /dev/null +++ b/apps/miclock/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "miclock", + "name": "Mixed Clock", + "version": "0.05", + "description": "A mix of analog and digital Clock", + "icon": "clock-mixed.png", + "type": "clock", + "tags": "clock", + "screenshots": [{"url":"bangle1-mixed-clock-screenshot.png"}], + "supports": ["BANGLEJS"], + "allow_emulator": true, + "storage": [ + {"name":"miclock.app.js","url":"clock-mixed.js"}, + {"name":"miclock.img","url":"clock-mixed-icon.js","evaluate":true} + ] +} diff --git a/apps/miclock2/metadata.json b/apps/miclock2/metadata.json new file mode 100644 index 000000000..dc1b49822 --- /dev/null +++ b/apps/miclock2/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "miclock2", + "name": "Mixed Clock 2", + "version": "0.01", + "description": "White color variant of the Mixed Clock with thicker clock hands for better readability in the bright sunlight, extra space under the clock for widgets and seconds in the digital clock.", + "icon": "clock-mixed.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "screenshots": [{"url":"bangle1-mixed-clock-2-screenshot.png"}], + "allow_emulator": true, + "storage": [ + {"name":"miclock2.app.js","url":"clock-mixed.js"}, + {"name":"miclock2.img","url":"clock-mixed-icon.js","evaluate":true} + ] +} diff --git a/apps/minimal_clock/metadata.json b/apps/minimal_clock/metadata.json new file mode 100644 index 000000000..1702d97a9 --- /dev/null +++ b/apps/minimal_clock/metadata.json @@ -0,0 +1,17 @@ +{ "id": "minimal_clock", + "name": "Minimal Analog Clock", + "shortName":"Minimal Clock", + "version":"0.03", + "description": "a minimal analog clock - just with some hands and no clock face", + "icon": "app-icon.png", + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "allow_emulator": true, + "screenshots": [{"url":"app-screenshot.png"}], + "readme": "README.md", + "storage": [ + {"name":"minimal_clock.app.js","url":"app.js"}, + {"name":"minimal_clock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/minionclk/metadata.json b/apps/minionclk/metadata.json new file mode 100644 index 000000000..44fc2a82d --- /dev/null +++ b/apps/minionclk/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "minionclk", + "name": "Minion clock", + "version": "0.05", + "description": "Minion themed clock.", + "icon": "minionclk.png", + "type": "clock", + "tags": "clock,minion", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "screenshots": [{"url":"bangle1-minion-clock-screenshot.png"}], + "storage": [ + {"name":"minionclk.app.js","url":"app.js"}, + {"name":"minionclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/miplant/metadata.json b/apps/miplant/metadata.json new file mode 100644 index 000000000..a949190c1 --- /dev/null +++ b/apps/miplant/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "miplant", + "name": "Xiaomi Plant Sensor", + "shortName": "Mi Plant", + "version": "0.02", + "description": "Reads and displays data from Xiaomi bluetooth plant moisture sensors", + "icon": "app.png", + "tags": "xiaomi,mi,plant,ble,bluetooth", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"miplant.app.js","url":"app.js"}, + {"name":"miplant.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/mmind/ChangeLog b/apps/mmind/ChangeLog new file mode 100644 index 000000000..939ac3b5d --- /dev/null +++ b/apps/mmind/ChangeLog @@ -0,0 +1 @@ +0.01: First release diff --git a/apps/mmind/README.md b/apps/mmind/README.md new file mode 100644 index 000000000..8060b95f6 --- /dev/null +++ b/apps/mmind/README.md @@ -0,0 +1,31 @@ +# Mastermind + +Play the classic mind game mastermind on your Bangle 2. + +![](screenshot_mmind.png) + + +## Game +The game will start when run. +Four colors pins are randomly chosen and kept secret. +You need to find the secret by scoring your choice within 6 turns. +The game makes use of touch features. + + +## Play +Select one of the dots, the color menu will show, select a colour for the pin. +If all pins are chosen with a color the red button will turn green. +Hit the green button and your play will be scored and listed from the top. +The first digit shows the number of pins with the correct color and in the right place. +The second digit gives the number of pins with the correct color but in the wrong place. +There are six turns to get the correct secret. +The blue button will start a new game. + + +## Requests +This is the first version, things to add are: +Add a menu to change game options like the number of colors, allow double colors, 5 pins per row. Add feature to drag screen up and down to see more scores. Timer and high score. +Any other fearures or remarks, let me know @psbest. + +## Creator +This game is created by Peter Slendebroek. diff --git a/apps/mmind/metadata.json b/apps/mmind/metadata.json new file mode 100644 index 000000000..c2ed474b6 --- /dev/null +++ b/apps/mmind/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "mmind", + "name": "Classic Mind Game", + "shortName":"Master Mind", + "icon": "mmind.png", + "version":"0.01", + "description": "This is the classic game for masterminds", + "screenshots": [{"url":"screenshot_mmind.png"}], + "type": "app", + "tags": "game", + "readme":"README.md", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"mmind.app.js","url":"mmind.app.js"}, + {"name":"mmind.img","url":"mmind.icon.js","evaluate":true} + ] +} diff --git a/apps/mmind/mmind.app.js b/apps/mmind/mmind.app.js new file mode 100644 index 000000000..e7def025d --- /dev/null +++ b/apps/mmind/mmind.app.js @@ -0,0 +1,198 @@ +//MMind + +//set vars +const H = g.getWidth(); +const W = g.getHeight(); +var touch_actions = []; +var cols = ["#FF0000","#00FF00","#0000FF", "#FF00FF", "#FFFF00", "#00FFFF", "#000000","#FFFFFF"]; +var turn = 0; +var col_menu = false; +//pinsRow = 6; +//pinsThick = 10; +//pinsRow = 5; +//pinsThick = 10; +var pinsRow = 4; +var pinsThick = 10; +var play = [-1, -1, -1, -1]; + +var pinsCol = 5; +var playx = -1; +var sx = (W - 30 )/pinsRow; +var sy = (H - 20 )/7; +var touch_actions = []; +var secret = []; +var secret_no_dub = true; +var endgame = false; + +g.clear(); +g.setColor("#FFFFFF"); +g.fillRect(0, 0, H, W); +g.setFont("Vector12",45); + +function draw() { + touch_actions = []; + g.clear(); + g.setColor("#FFFFFF"); + g.fillRect(0, 0, H, W); + g.setColor("#000000"); + //draw scores + for (y=0;y= 0) s = Math.round(Math.random()*pinsCol); + secret[i]= s; + } + } + +function score() { + bScore = 0; + wScore = 0; + for (i=0; i touch_actions[i][0][0] && e.x < touch_actions[i][0][2] && + e.y > touch_actions[i][0][1] && e.y < touch_actions[i][0][3]) { + // a action is hit, add acctions here, todo: start, stop, new, etc. + switch (touch_actions[i][1][0]) { + case 1: + //get pins col menu + col_menu = 1; + playx = touch_actions[i][1][1]; + break; + case 2: + //copy choice col to play + play[playx] = touch_actions[i][1][1]; + col_menu = 0; + break; + case 3: + //score play + var sc; + sc = score(); + game.push([play, sc]); + play = [-1,-1,-1,-1]; + turn+=1; + if (turn==6 || sc[0]==pinsRow) { + play = secret; + col_menu = 0; + endgame = true; + } + break; + case 4: + //new game + play = [-1,-1,-1,-1]; + game = []; + endgame=false; + break; + } + } + } + //console.log(touch_actions[i][1][0], touch_actions[i][1][1]); + + draw(); + } +); + + +game = []; +get_secret(); +draw(); +//Bangle.loadWidgets(); +//Bangle.drawWidgets(); + + + + + diff --git a/apps/mmind/mmind.icon.js b/apps/mmind/mmind.icon.js new file mode 100644 index 000000000..17c28ba0f --- /dev/null +++ b/apps/mmind/mmind.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+64A/AEOBq2sBAusqwJHCaQFDAYlP2m0yGBCIkSj0eiWHBIkDgsFgYTE01v3O5t4mC1krgAEBq0ACYQuCAANsHIcxFwIwCEocsFwIwCBIYuCAANQF4QwBOgQABAgNIF4ZgELwQvCHIcCF4cEKwYvEt45DF4QwCL5YvFL5ITDF6OstheCvTjEjAuBjDJFX4UEq4TEyguBygTEF4dWBIeskkkqwQDDgUGgwaEBIUBgITHkslCYeBd4MrqwDBAgIuBcwRVGNIVs0oJEv3S6V+CYmIisjkcVZAYpBgDyBAAJFBFwTlGZIolDqouBGAQJDFwQABmRfCFAICCGwXXhgvDMAheCfI1UF4eoKwYvEiovHSoJfLF4pfJCYYvN1gwBAYMSLwVcbQmQFwOQZIq/C1GACYkcFwMcCYQoCLYNWF4KPBDgNWmIkEBIVPp5TDBIdWqoTHmUyCYlWRQTwCD4wA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHmy2QJH6PRBI/Q6AkOCAIAFBINDjwABGInR3+53O/GIu72gABGJnQCAQAE69oFwQABCYfFFwIwCBIfCDIe7FIus1gvXLwQACLw4aCAAkAgAvcL4gvLq1WF5uyFwdoCYfLF4fLDpHCX6owBtFoxoUF6PF4ruFDwPC4XJFxbSCAAwVNAH4ARA")) diff --git a/apps/mmind/mmind.info b/apps/mmind/mmind.info new file mode 100644 index 000000000..2e79822b1 --- /dev/null +++ b/apps/mmind/mmind.info @@ -0,0 +1,17 @@ + { + "id": "mmind", + "name": "Classic Mind Game", + "shortName":"Master Mind", + "icon": "mmind.png", + "version":"0.01", + "description": "This is the classic game for masterminds", + "type": "game", + "tags": "mastermind, game, classic", + "readme":"README.md", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"mmind.app.js","url":"mmind.app.js"}, + {"name":"mmind.img","url":"mmind.icon.js","evaluate":true} + ] + } diff --git a/apps/mmind/mmind.png b/apps/mmind/mmind.png new file mode 100644 index 000000000..14a3ef7c6 Binary files /dev/null and b/apps/mmind/mmind.png differ diff --git a/apps/mmind/screenshot_mmind.png b/apps/mmind/screenshot_mmind.png new file mode 100644 index 000000000..5c886e7e8 Binary files /dev/null and b/apps/mmind/screenshot_mmind.png differ diff --git a/apps/mmonday/metadata.json b/apps/mmonday/metadata.json new file mode 100644 index 000000000..bc101a84c --- /dev/null +++ b/apps/mmonday/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "mmonday", + "name": "Manic Monday Tone", + "version": "0.02", + "description": "The Bangles make a comeback", + "icon": "manic-monday-icon.png", + "tags": "sound", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"mmonday.app.js","url":"manic-monday.js"}, + {"name":"mmonday.img","url":"manic-monday-icon.js","evaluate":true} + ] +} diff --git a/apps/moonphase/metadata.json b/apps/moonphase/metadata.json new file mode 100644 index 000000000..548518338 --- /dev/null +++ b/apps/moonphase/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "moonphase", + "name": "Moonphase", + "version": "0.02", + "description": "Shows current moon phase. Now with GPS function.", + "icon": "app.png", + "tags": "", + "supports": ["BANGLEJS"], + "screenshots": [{"url":"bangle1-moon-phase-screenshot.png"}], + "allow_emulator": true, + "storage": [ + {"name":"moonphase.app.js","url":"app.js"}, + {"name":"moonphase.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/morse/metadata.json b/apps/morse/metadata.json new file mode 100644 index 000000000..17b73b9cf --- /dev/null +++ b/apps/morse/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "morse", + "name": "Morse Code", + "version": "0.01", + "description": "Learn morse code by hearing/seeing/feeling the code. Tap to toggle buzz!", + "icon": "morse-code.png", + "type": "app", + "tags": "morse,sound,visual,input", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"morse.app.js","url":"morse-code.js"}, + {"name":"morse.img","url":"morse-code-icon.js","evaluate":true} + ] +} diff --git a/apps/multiclock/metadata.json b/apps/multiclock/metadata.json new file mode 100644 index 000000000..197e6631c --- /dev/null +++ b/apps/multiclock/metadata.json @@ -0,0 +1,23 @@ +{ + "id": "multiclock", + "name": "Multi Clock", + "version": "0.09", + "description": "Clock with multiple faces. Switch between faces with BTN1 & BTN3 (Bangle 2 touch top-right, bottom right). For best display set theme Background 2 to cyan or some other bright colour in settings.", + "screenshots": [{"url":"screen-ana.png"},{"url":"screen-big.png"},{"url":"screen-td.png"},{"url":"screen-nifty.png"},{"url":"screen-word.png"},{"url":"screen-sec.png"}], + "icon": "multiclock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"multiclock.app.js","url":"multiclock.app.js"}, + {"name":"big.face.js","url":"big.face.js"}, + {"name":"ana.face.js","url":"ana.face.js"}, + {"name":"digi.face.js","url":"digi.face.js"}, + {"name":"txt.face.js","url":"txt.face.js"}, + {"name":"dk.face.js","url":"dk.face.js"}, + {"name":"nifty.face.js","url":"nifty.face.js"}, + {"name":"multiclock.img","url":"multiclock-icon.js","evaluate":true} + ] +} diff --git a/apps/mylocation/metadata.json b/apps/mylocation/metadata.json new file mode 100644 index 000000000..b26a97290 --- /dev/null +++ b/apps/mylocation/metadata.json @@ -0,0 +1,19 @@ +{ "id": "mylocation", + "name": "My Location", + "shortName":"My Location", + "icon": "mylocation.png", + "type": "app", + "screenshots": [{"url":"screenshot_1.png"}], + "version":"0.02", + "description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README", + "readme": "README.md", + "tags": "tool,utility", + "supports": ["BANGLEJS", "BANGLEJS2"], + "storage": [ + {"name":"mylocation.app.js","url":"mylocation.app.js"}, + {"name":"mylocation.img","url":"mylocation.icon.js","evaluate": true } + ], + "data": [ + {"name":"mylocation.json"} + ] +} diff --git a/apps/mysticclock/metadata.json b/apps/mysticclock/metadata.json new file mode 100644 index 000000000..571a55ecd --- /dev/null +++ b/apps/mysticclock/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "mysticclock", + "name": "Mystic Clock", + "version": "1.01", + "description": "A retro-inspired watchface featuring time, date, and an interactive data display line.", + "icon": "mystic-clock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "screenshots": [{"url":"bangle1-mystic-clock-screenshot.png"}], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"mysticclock.app.js","url":"mystic-clock-app.js"}, + {"name":"mysticclock.settings.js","url":"mystic-clock-settings.js"}, + {"name":"mysticclock.img","url":"mystic-clock-icon.js","evaluate":true} + ] +} diff --git a/apps/mysticdock/ChangeLog b/apps/mysticdock/ChangeLog index 34fe53627..eacafb944 100644 --- a/apps/mysticdock/ChangeLog +++ b/apps/mysticdock/ChangeLog @@ -1 +1 @@ -1.00: First published version. +0.01: First published version. diff --git a/apps/mysticdock/metadata.json b/apps/mysticdock/metadata.json new file mode 100644 index 000000000..54ebedd93 --- /dev/null +++ b/apps/mysticdock/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "mysticdock", + "name": "Mystic Dock", + "version": "0.01", + "description": "A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.", + "icon": "mystic-dock.png", + "type": "dock", + "tags": "dock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"mysticdock.app.js","url":"mystic-dock-app.js"}, + {"name":"mysticdock.boot.js","url":"mystic-dock-boot.js"}, + {"name":"mysticdock.settings.js","url":"mystic-dock-settings.js"}, + {"name":"mysticdock.img","url":"mystic-dock-icon.js","evaluate":true} + ] +} diff --git a/apps/mywelcome/metadata.json b/apps/mywelcome/metadata.json new file mode 100644 index 000000000..b6d37d2e1 --- /dev/null +++ b/apps/mywelcome/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "mywelcome", + "name": "Customised Welcome", + "shortName": "My Welcome", + "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","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-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} + ], + "data": [{"name":"mywelcome.json"}] +} diff --git a/apps/nato/metadata.json b/apps/nato/metadata.json new file mode 100644 index 000000000..49366e6e7 --- /dev/null +++ b/apps/nato/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "nato", + "name": "NATO Alphabet", + "shortName": "NATOAlphabet", + "version": "0.01", + "description": "Learn the NATO Phonetic alphabet plus some numbers.", + "icon": "nato.png", + "type": "app", + "tags": "app,learn,visual", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "screenshots": [{"url":"bangle1-NATO-alphabet-screenshot.png"},{"url":"bangle1-NATO-alphabet-screenshot2.png"}], + "storage": [ + {"name":"nato.app.js","url":"nato.js"}, + {"name":"nato.img","url":"nato-icon.js","evaluate":true} + ] +} diff --git a/apps/ncfrun/metadata.json b/apps/ncfrun/metadata.json new file mode 100644 index 000000000..831ae3d4e --- /dev/null +++ b/apps/ncfrun/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "ncfrun", + "name": "NCEU 5K Fun Run", + "version": "0.01", + "description": "Display a map of the NodeConf EU 2019 5K Fun Run route and your location on it", + "icon": "nceu-funrun.png", + "tags": "health", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"ncfrun.app.js","url":"nceu-funrun.js"}, + {"name":"ncfrun.img","url":"nceu-funrun-icon.js","evaluate":true} + ] +} diff --git a/apps/ncrclk/metadata.json b/apps/ncrclk/metadata.json new file mode 100644 index 000000000..b50b554e1 --- /dev/null +++ b/apps/ncrclk/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "ncrclk", + "name": "NCR Clock", + "shortName": "NCR Clock", + "version": "0.02", + "description": "NodeConf Remote clock", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"ncrclk.app.js","url":"app.js"}, + {"name":"ncrclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/ncstart/metadata.json b/apps/ncstart/metadata.json new file mode 100644 index 000000000..d2b3e2196 --- /dev/null +++ b/apps/ncstart/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "ncstart", + "name": "NCEU Startup", + "version": "0.06", + "description": "NodeConfEU 2019 'First Start' Sequence", + "icon": "start.png", + "tags": "start,welcome", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"ncstart.app.js","url":"start.js"}, + {"name":"ncstart.boot.js","url":"boot.js"}, + {"name":"ncstart.settings.js","url":"settings.js"}, + {"name":"ncstart.img","url":"start-icon.js","evaluate":true}, + {"name":"nc-bangle.img","url":"start-bangle.js","evaluate":true}, + {"name":"nc-nceu.img","url":"start-nceu.js","evaluate":true}, + {"name":"nc-nfr.img","url":"start-nfr.js","evaluate":true}, + {"name":"nc-nodew.img","url":"start-nodew.js","evaluate":true}, + {"name":"nc-tf.img","url":"start-tf.js","evaluate":true} + ], + "data": [{"name":"ncstart.json"}] +} diff --git a/apps/nixie/metadata.json b/apps/nixie/metadata.json new file mode 100644 index 000000000..50f02712b --- /dev/null +++ b/apps/nixie/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "nixie", + "name": "Nixie Clock", + "shortName": "Nixie", + "version": "0.01", + "description": "A nixie tube clock for both Bangle 1 and 2.", + "icon": "nixie.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"nixie.app.js","url":"app.js"}, + {"name":"nixie.img","url":"app-icon.js","evaluate":true}, + {"name":"m_vatch.js","url":"m_vatch.js"} + ] +} diff --git a/apps/notanalog/ChangeLog b/apps/notanalog/ChangeLog new file mode 100644 index 000000000..574d46609 --- /dev/null +++ b/apps/notanalog/ChangeLog @@ -0,0 +1,3 @@ +0.01: Launch app. +0.02: 12k steps are 360 degrees - improves readability of steps. +0.03: Battery improvements through sleep (no minute updates) and partial updates of drawing. \ No newline at end of file diff --git a/apps/notanalog/README.md b/apps/notanalog/README.md new file mode 100644 index 000000000..2928fd959 --- /dev/null +++ b/apps/notanalog/README.md @@ -0,0 +1,33 @@ +# Not Analog +An analog watch face for people (like me) that can not read analog watch faces. +It looks like an analog clock, but its not! It shows the time digital - check the +4 numbers on the watch face ;) + +The red hand shows the number of steps (12k steps = 360 degrees) and the +black one the battery level (100% = 360 degrees). +The selected theme is also respected. Note that this watch face is in fullscreen +mode, but widgets are still loaded in background. + +## Other features +- Set a timer - simply touch top (+5min.) or bottom (-5 min.). +- If the weather is available through the weather app, the outside temp. will be shown. +- Sleep modus at midnight to save more battery (no minute updates). +- Icons for charging and GPS. +- If you have done more than 10k steps, the red hand and icon will turn green. +- Shows current lock status of your bangle va a colored dot in the middle. + + +## Screenshots +![](screenshot_1.png) +![](screenshot_2.png) +![](screenshot_3.png) + + +# Thanks +Thanks to the multiclock from https://github.com/jeffmer/BangleApps/ +which helped a lot for this development. + +Icons from by Freepik - Flaticon + +## Contributors +- [David Peer](https://github.com/peerdavid). \ No newline at end of file diff --git a/apps/notanalog/metadata.json b/apps/notanalog/metadata.json new file mode 100644 index 000000000..5efb6bccf --- /dev/null +++ b/apps/notanalog/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "notanalog", + "name": "Not Analog", + "shortName":"Not Analog", + "icon": "notanalog.png", + "version":"0.03", + "readme": "README.md", + "supports": ["BANGLEJS2"], + "description": "An analog watch face for people that can not read analog watch faces.", + "type": "clock", + "tags": "clock", + "screenshots": [ + {"url":"screenshot_1.png"}, + {"url":"screenshot_2.png"} + ], + "storage": [ + {"name":"notanalog.app.js","url":"notanalog.app.js"}, + {"name":"notanalog.img","url":"notanalog.icon.js","evaluate":true} + ] +} diff --git a/apps/notanalog/notanalog.app.js b/apps/notanalog/notanalog.app.js new file mode 100644 index 000000000..cea8072b8 --- /dev/null +++ b/apps/notanalog/notanalog.app.js @@ -0,0 +1,474 @@ +/** + * NOT ANALOG CLOCK + */ + +const locale = require('locale'); +const storage = require('Storage') +const SETTINGS_FILE = "notanalog.setting.json"; +let settings = { + alarm: -1, +}; +let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; +for (const key in saved_settings) { + settings[key] = saved_settings[key] +} + +/* + * Set some important constants such as width, height and center + */ +var W = g.getWidth(),R=W/2; +var H = g.getHeight(); +var cx = W/2; +var cy = H/2; +var drawTimeout; + +var state = { + color: "#ff0000", + steps: 0, + maxSteps: 10000, + bat: 0, + has_weather: false, + temp: "-", + sleep: false, +} + +var chargeImg = { + width : 32, height : 32, bpp : 1, + transparent : 0, + buffer : E.toArrayBuffer(atob("AAAMAAAAHgAAADMAAABjAAAAxgAAD44AAB8cAAA7uAAAcfAMAODgPgDAcHMBgDjjAYAdxgGBj4wBg8cYAYZjsAGGYeABg8DgAQGAYAMAAOAHgAHAB8ADgAzgBwAYc/4AGD/4AAw4AAAOcAAAH8AAADmAAABwAAAA4AAAAMAAAAA=")) +}; + +var alarmImg = { + width : 32, height : 32, bpp : 1, + transparent : 0, + buffer : E.toArrayBuffer(atob("AA/wAAAP8AAAD/AAAAGAAAABgAAAA8AABh/4YAd//uAH+B/gA+AHwAOAAcAHAPDgDgD4cA4A/HAcAP44HAD+OBwA/zgYAP8YGAD/GBj//xgc//84HH/+OBx//jgOP/xwDh/4cAcP8OAHg8HgA8ADwAHwD4AA//8AAD/8AAAP8AA=")) +}; + +var stepsImg = { + width : 32, height : 32, bpp : 1, + transparent : 0, + buffer : E.toArrayBuffer(atob("AcAAAAPwAAAH8AAAB/gAAAf4AAAH/AAAD/wAAAf8AAAH/AfAB/wP4Af8H+AH/B/gB/wf4AP8P+AD+D/gAfg/4AGAP+AAPD/gAPw/4AD+P+AAfj/AAH4/wAB+H8AAPAeAAAAwAAAAPgAAAH8AAAB/AAAAfgAAAH4AAAA8AAAAOAA=")) +}; + +var gpsImg = { + width : 32, height : 32, bpp : 1, + transparent : 0, + buffer : E.toArrayBuffer(atob("AAAMAAAAD4AAAAHAAAAA4AAADjABAA8YAYADmAPAAcwD4DzMB/B8zAf4fAAH/HwAB/74AAf/wAAH/4AAB//AAAP/4AAD//AAA//4AAH//AAA//4AAH//AAA//4ABH/4AAYP4AAHgAAAB/AAAA/4AAAP+AAAD/gAAP//gAD//4AA=")) +}; + +var sleepImg = { + width : 128, height : 128, bpp : 1, + transparent : 0, + buffer : require("heatshrink").decompress(atob("ABk//+AB5l///AB5wfDh4kIF4s/8AgIj4ED//wB5E+AYUB//8B5F8AYUD+F+B5H4AYUH8E/Bw8BHIcHwEfMA4PEh4RBQo8DNIYPBIIIPGDAkeEwJGDAAaZEB4MAOAisB+COEngCBOAn///4NAgPCMAgfCZ4gPCaIpWBd4l4QQZtFD4gPCgYPEQw3wRo41FgHxfw5tEB4sHfg7DC8IPDFQb8DB4XgB4ZDDWosD4DNCbAbsEB4zRDB5bRDfghKDB4bRCRwwPBuAFCbISOCgP/EYMPK4kPDgKOCgbiBDIJLDEoIYBRwQPD//DD4hQBbgPgF4QCB84PDBgICCDgJTBEQP/B4QFCwAIDKYIRB/84bQX/x+AD4YPCwF+nguC+B9FMYJuBngPBIgKmCeQoPEg5dBB4ryBB4kPPoMfdohRCB4McSYPAg5dBeQoPCjxOBCIIPBcQYUBL4N4j0B/hQBAATPBV4RnB/EegYFB//AbYYPCgfh+EeZgJNDAYYWBCQUedgN/NoUD/xhDEwUOj67BBQd/IAIFEh8+gZ3CNQMfSQkMBQN8g/wMATKBCQIAEh/4IAMPdoQlCB4vwn7sC/5OBSIQPE8F+KoRoBfIIPFPwP8cASyBQoIPG4JABJQUHAoJwEBAODIAUBAIIlBOAg/BgfgcAMDBYN+A4IPFC4I+BB4U/wKAFh8PwJ5BB4SFBB40fFANggPAg5nBSAsPzwwBDIRGB+F8L4v+NAIZCh8B+E8B4v8RAN4AwMOgH4jwPEY4M+gEwB4d8UA34E4sAn0PA4pHGgEeWApHBfA8HB4vgQ4oPBw4PF8IPGbALQEgfB8IXF4/DB4vD8YHG4LgEEwPDA4oPIA4w3BA4pWBF4poGdAJOEAAQPFQwyoDB4q2GB6VwB5twvAFDhwPIvAPFhwPNjwPTgaSDBwgPBj//wH//6qCnAPI4IPEvgPY4APEngPGjxPOL5KvER4gPFV5IPKZ4gPEZ4oPJd5QPF+APEg+AB5kHB5+HB40B8APFwfBVgIPCgeB8K0CB4fDB4kH4YXCLQfDB4oHBB43B8ZABB4UB4/DKgYPCCwRPDHAIPEKwgPDh+HB434B4yIDQwbGCB4ceB434ngPFnzIDewc+gEwB4MEgF8j4PFA4V4B4MOE4MeB4s8h+AB4QsBG4YADI4PA+APCgfwvgPFj8D8FwB4L2B8BnCAAcPwKQBL4UPEoIPFFwP8B4cfCwQPGvwPDv42BB4oHBn+AB4MB/gXBB4sB/Ef8BPC/B2BB4sADIP8B4M/8CeGAAN+gP/4fB//AWwIAGn5LB/4ABEwIPHj/Aj4OB/BGBB46ZBgYPBKAJ+GOAQZBj4sBEoIPHgP+Aod/Nw4KCDQQUFKAw6Ch5eIKAX/FYP/JxArCPwQSCABM/BwI+KGAYuLEAYeGA=")) +}; + + +/* + * Based on the great multi clock from https://github.com/jeffmer/BangleApps/ + */ +Graphics.prototype.drawRotRect = function(w, r1, r2, angle) { + angle = angle % 360; + var w2=w/2, h=r2-r1, theta=angle*Math.PI/180; + return this.fillPoly(this.transformVertices([-w2,0,-w2,-h,w2,-h,w2,0], + {x:cx+r1*Math.sin(theta),y:cy-r1*Math.cos(theta),rotate:theta})); +}; + +// The following font was used: +// +Graphics.prototype.setTimeFont = function(scale) { + // Actual height 26 (26 - 1) + this.setFontCustom(atob("AAAAAAAAAD4AAAAD4AAAAD4AAAAD4AAAAB4AAAAAAAAAAAAAAAAD4AAAD/4AAD//4AD///4Af///gAf//gAAf/gAAAfwAAAAQAAAAAAAAAAAAAAAAAB//+AAH///gAP///wAP///wAfwAP4AfAAD4AfAAD4AfAAD4AfAAD4AfAAD4AfgAH4AP///wAP///wAH///gAB//+AAAP/wAAAAAAAAAAAAAAf///4Af///4Af///4Af///4Af///4AAAAAAAAAAAAAAAAAAAB+AD4AH+AP4AP+Af4AP+B/4AfwD/4AfAH/4AfAf74AfA/z4AfD/j4Af/+D4AP/8D4AH/wD4AD/gD4AB+AD4AAAAAAAB8B8AAD8B/AAH8B/gAP8B/wAf8A/4AfAAD4AfB8D4AfB8D4AfD8D4Af3/P4AP///wAP///wAH///gAB/H+AAAAAAAAAAAAAAAAB+AAAAP+AAAA/+AAAD/+AAAP/+AAA/8+AAD/w+AAf/A+AAf///4Af///4Af///4Af///4AD///4AAAA+AAAAA+AAAAAAAAAAAYAAf/8/AAf/8/gAf/8/wAf/8/wAfD4H4AfD4D4AfD4D4AfD4D4AfD8H4AfD//4AfB//wAfA//gAeAf/AAAAH8AAAAAAAAAAAAAAB//+AAD///AAH///gAP///wAf///4AfD4D4AfD4D4AfD4D4AfD4D4Afz+P4AP5//wAP5//wAH4//gAB4P+AAAAAAAAAAAAAAfAAAAAfAAAAAfAAAIAfAAB4AfAAP4AfAB/4AfAP/4AfA//4AfH//AAf//4AAf/+AAAf/wAAAf+AAAAfwAAAAAAAAAAAAAAAAB8P+AAH///gAP///wAP///wAf/+P4AfH4D4AfD4D4AfD4D4Afn8D4Af///4AP///wAH///gAD/f/AAA4P+AAAAAAAAAAAQAAB/w+AAH/4/gAP/8/wAP/+/wAfh+P4AfA/D4AfAfD4AfAfD4AfA+H4Af///4AP///wAH///gAD///AAA//8AAAAAAAAAAAAAAAB8D4AAB8D4AAB8D4AAB8D4AAA8B4AAAAAAA=="), 46, atob("BwsSCBAQEBAQEBAQBw=="), 36+(scale<<8)+(1<<16)); + return this; +}; + +Graphics.prototype.setNormalFont = function(scale) { + // Actual height 19 (18 - 0) + this.setFontCustom(atob("AAAAAAAAAAAAAAD/5wP/3A/+cAAAAPwAA/AADAAAAgAA/AAD8AAIAAAAAAAxgADGAA/+AD/4ADGAAMYAD/4AP/gAMYAAxgAB84AP7wD3jwPHPA+f8A+/AB54AAAAB+AAP8BAwwcDnHwP8/AfPwAD8AA/AAPxwB+fgPh/A4GMCAfwAA+AAAAA+/AH/+A//8DjhwOOHA4/8Dj/wAP/AA4AADgAAAAAAAAD8AAPwAAwAAAAAAB/4Af/4D//wPAPA4AcDgBwAAAAAAADgBwOAHA//8B//gD/8AD/AAoAAGwAA/gAD+AAHwAAbAAAAAAAAAAAAAABgAAGAAAYAA/+AD/4AAYAABgAAGAAAYAAAByAAH4AAfAAAAAGAAA4AADgAAOAAA4AAAAAAAAAAAAAABwAAHAAAcAAAAAA/AD/8D//gP+AA8AAAAAAD/8Af/4D//wOAHA4AcDgBwPAPA//8B//gB/4AAAAAAAAP//A//8D//wAAAADAMA8DwHw/A+H8Dg/wOP3A/8cB/hwD4HAAAAAYEAHw8AfD4D4HwOOHA44cD//wH/+APvwAAAAAB4AAfgAH+AB/4AfjgD//wP//A//8AAOAAA4AAAAD/vAP++A/58DnBwOcHA5/8Dj/gOH8AAHAA//AH/+A//8DnhwOcHA548D7/wHv+AOPgAAAAOAAA4AADgBwOA/A4f8Dv/AP/gA/wAD4AAAAAAACAA9/AH/+A//8DnBwOcHA//8B//gD38AAHAA/HAH++A/98DhzwOHHA4c8D//wH/+AP/wAAAAAAAAA4cADhwAOHAA4cgDh+AOHwAAAABgAAPAAB8AAH4AA5wAHDgAYGAAAQAAAAAGYAAZgABmAAGYAAZgABmAAGYAAZgABmAAAAAAAQAGDgAcOAA5wAB+AAHwAAPAAAYAAAAADwAAfgAD8AAOD3A4fcDz9wP+AAfwAAcAAAAAAP/wB//gP//A4A8Dn5wOf3A5/cD/9wH/3AP+cAABwAAAAAH8AP/wP//A/84D/zgP//AH/8AAfwAABAAAAD//wP//A//8DjhwOOHA488D//wH/+APngA//AH/+A//8DgBwOAHA8A8D8PwHw+AHDgAAAAAAAA//8D//wP//A4AcDgBwPAPA//8B//gD/4AAAAD//wP//A//8DjhwOOHA44cDjhwOOHAAAAD//wP//A//8DjgAOOAA44ADjgAOOAAP/wB//gP//A4AcDhxwOHPA/f8B9/gDn4AAAAAAAAP//A//8D//wAOAAA4AADgAP//A//8D//wAAAA//8D//wP//AAAAAAPAAA+AAD8AABwAAHAAA8D//wP/+A//gAAAAAAAA//8D//wP//AD8AA/8AH/8A+H8DgHwIAHAAAAD//wP//A//8AABwAAHAAAcAABwAAHAAAAD//wP//A//8D8AAD4AAPgAB8AAP//A//8D//wAAAAAAAD//wP//Af/8Af4AAf4A//4D//wP//AAAAA//AH/+A//8DgBwOAHA4A8D//wH/+AP/wAAAAAAAA//8D//wP//A4cADhwAOPAA/8AB/gAD4AAP/wB//gP//A4AcDgBwPAPA//8B//4D//gAAEAAAAP//A//8D//wOOAA44ADjwAP//Af/8A+fwAAAAPjwB/PgP+/A48cDhxwPn/Afv4B+fgBw4AAAADgAAOAAA4AAD//wP//A//8DgAAOAAA4AAD//AP/+A//8AABwAAHAAA8D//wP/+A//wAAAAPAAA/4AD//gA//AAP8Af/wP/8A/4ADgAAAAAA4AAD/gAP//AH/8AB/wP//A//AD//gB//AAP8D//wP/4A/gACAAAOAHA/D8D//wB/4AH/gD//wPx/A4AcAAAAMAAA+AAD/AAD//AB/8B//wP8AA+AADAAAOAfA4H8Dh/wOf3A/8cD/BwPwHA8AcAAAAP//A//8D//wOAHA4AcDgBwAAAAAAAD8AAP/wAf/8AD/wAAPAAAAAAAAOAHA4AcDgBwP//A//8D//wA=="), 32, atob("AwQJCggPCwUHBwgKBAcEBwsFCgoKCgoKCgoEBAkKCQoMCQoKCgkJCgoFCgoJDAoKCgoLCgkKCg4JCQkHBwc="), 22+(scale<<8)+(1<<16)); + return this; +}; + + + +function getSteps() { + try{ + if (WIDGETS.wpedom !== undefined) { + return WIDGETS.wpedom.getSteps(); + } else if (WIDGETS.activepedom !== undefined) { + return WIDGETS.activepedom.getSteps(); + } + } catch(ex) { + // In case we failed, we can only show 0 steps. + } + + return 0; + } + + +function drawBackground() { + g.setFontAlign(0,0,0); + g.setNormalFont(); + + g.setColor(g.theme.fg); + for (let a=0;a<360;a+=6){ + if (a % 30 == 0 || (a > 345 || a < 15) || (a > 90-15 && a < 90+15) || (a > 180-15 && a < 180+15) || (a > 270-15 && a < 270+15)) { + continue; + } + + var theta=a*Math.PI/180; + g.drawLine(cx,cy,cx+125*Math.sin(theta),cy-125*Math.cos(theta)); + } + + g.clearRect(10,10,W-10,H-10); + for (let a=0;a<360;a+=30){ + if(a == 0 || a == 90 || a == 180 || a == 270){ + continue; + } + g.drawRotRect(6,R-80,125,a); + } + + g.clearRect(16,16,W-16,H-16); +} + + +function drawState(){ + g.setFontAlign(1,0,0); + + // Draw alarm + var highPrioImg = isAlarmEnabled() ? alarmImg : + Bangle.isCharging() ? chargeImg : + Bangle.isGPSOn() ? gpsImg : + undefined; + + var imgColor = isAlarmEnabled() ? state.color : + Bangle.isCharging() ? g.theme.fg : + Bangle.isGPSOn() ? g.theme.fg : + state.color; + + // As default, we draw weather if available, otherwise the steps symbol is shown. + if(!highPrioImg && state.has_weather){ + g.setColor(g.theme.fg); + g.drawString(state.temp, cx+cx/2+15, cy+cy/2+10); + } else { + g.setColor(imgColor); + var img = highPrioImg ? highPrioImg : stepsImg; + g.drawImage(img, cx+cx/2 - img.width/2 + 5, cy+cy/2 - img.height/2+5); + } +} + +function drawData() { + g.setFontAlign(0,0,0); + g.setNormalFont(); + + // Set hand functions + var drawBatteryHand = g.drawRotRect.bind(g,6,12,R-38); + var drawDataHand = g.drawRotRect.bind(g,5,12,R-24); + + // Draw battery hand + g.setColor(g.theme.fg); + g.setFontAlign(0,0,0); + drawBatteryHand(parseInt(state.bat*360/100)); + + // Draw data hand - depending on state + g.setColor(state.color); + if(isAlarmEnabled()){ + var alrm = getAlarmMinutes(); + drawDataHand(parseInt(alrm*360/60)); + return; + } + + // Default are the steps + drawDataHand(parseInt(state.steps*360/12000)); +} + +function drawTextCleared(s, x, y){ + g.clearRect(x-15, y-22, x+15, y+15); + g.drawString(s, x, y); +} + + +function drawTime(){ + g.setTimeFont(); + g.setFontAlign(0,0,0); + g.setColor(g.theme.fg); + + var posX = 14; + var posY = 14; + + // Hour + var h = state.currentDate.getHours(); + var h1 = parseInt(h / 10); + var h2 = h < 10 ? h : h - h1*10; + drawTextCleared(h1, cx, posY+8); + drawTextCleared(h2, W-posX, cy+5); + + // Minutes + var m = state.currentDate.getMinutes(); + var m1 = parseInt(m / 10); + var m2 = m < 10 ? m : m - m1*10; + drawTextCleared(m2, cx, H-posY); + drawTextCleared(m1, posX-1, cy+5); +} + + +function drawDate(){ + // Date + g.setFontAlign(-1,0,0); + g.setNormalFont(); + g.setColor(g.theme.fg); + var dayStr = locale.dow(state.currentDate, true).toUpperCase(); + g.drawString(dayStr, cx/2-15, cy/2-5); + g.drawString(state.currentDate.getDate(), cx/2-15, cy/2+17); +} + + +function drawLock(){ + g.setColor(g.theme.fg); + g.fillCircle(cx, cy, 7); + + var c = Bangle.isLocked() ? state.color : g.theme.bg; + g.setColor(c); + g.fillCircle(cx, cy, 4); +} + + +function handleState(fastUpdate){ + state.currentDate = new Date(); + + /* + * Sleep modus + */ + var minutes = state.currentDate.getMinutes(); + var hours = state.currentDate.getHours(); + if(!isAlarmEnabled() && fastUpdate && hours == 00 && minutes == 01){ + state.sleep = true; + return; + } + + // Set steps + state.steps = getSteps(); + + // Color based on state + state.color = isAlarmEnabled() ? "#FF6A00" : + state.steps > state.maxSteps ? "#00ff00" : + "#ff0000"; + + /* + * 5 Minute updates + */ + if(minutes % 5 == 0 && fastUpdate){ + return; + } + + // Set battery + state.bat = E.getBattery(); + + // Set weather + state.has_weather = true; + try { + weather = require('weather').get(); + if (weather === undefined){ + state.has_weather = false; + state.temp = "-"; + } else { + state.temp = locale.temp(Math.round(weather.temp-273.15)); + } + } catch(ex) { + state.has_weather = false; + } +} + + +function drawSleep(){ + g.reset(); + g.clearRect(0, 0, g.getWidth(), g.getHeight()); + drawBackground(); + + g.setColor(1,1,1); + g.drawImage(sleepImg, cx - sleepImg.width/2, cy- sleepImg.height/2); +} + + +function draw(fastUpdate){ + // Execute handlers + handleState(fastUpdate); + handleAlarm(); + + if(state.sleep){ + drawSleep(); + // We don't queue draw again - so its sleeping until + // the user presses the btn again. + return; + } + + // Clear watch face + if(fastUpdate){ + var innerRect = 20; + g.clearRect(innerRect, innerRect, g.getWidth()-innerRect, g.getHeight()-innerRect); + } else { + g.reset(); + g.clearRect(0, 0, g.getWidth(), g.getHeight()); + } + + // Draw again + g.setColor(1,1,1); + + if(!fastUpdate){ + drawBackground(); + } + + drawDate(); + drawLock(); + drawState(); + drawTime(); + drawData(); + + // Queue draw in one minute + queueDraw(); +} + + +/* + * Listeners + */ +Bangle.on('lcdPower',on=>{ + if (on) { + draw(true); + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +Bangle.on('charging',function(charging) { + draw(true); +}); + +Bangle.on('lock', function(isLocked) { + if(state.sleep){ + state.sleep=false; + draw(false); + } else { + drawLock(); + } +}); + +Bangle.on('touch', function(btn, e){ + var upper = parseInt(g.getHeight() * 0.2); + var lower = g.getHeight() - upper; + + var is_upper = e.y < upper; + var is_lower = e.y > lower; + + if(is_upper){ + feedback(); + increaseAlarm(); + draw(true); + } + + if(is_lower){ + feedback(); + decreaseAlarm(); + draw(true); + } +}); + + +/* + * Some helpers + */ +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(true); + }, 60000 - (Date.now() % 60000)); +} + + +/* + * Handle alarm + */ +function getCurrentTimeInMinutes(){ + return Math.floor(Date.now() / (1000*60)); +} + +function isAlarmEnabled(){ + return settings.alarm >= 0; +} + +function getAlarmMinutes(){ + var currentTime = getCurrentTimeInMinutes(); + return settings.alarm - currentTime; +} + +function handleAlarm(){ + if(!isAlarmEnabled()){ + return; + } + + if(getAlarmMinutes() > 0){ + return; + } + + // Alarm + var t = 300; + Bangle.buzz(t, 1) + .then(() => new Promise(resolve => setTimeout(resolve, t))) + .then(() => Bangle.buzz(t, 1)) + .then(() => new Promise(resolve => setTimeout(resolve, t))) + .then(() => Bangle.buzz(t, 1)) + .then(() => new Promise(resolve => setTimeout(resolve, t))) + .then(() => Bangle.buzz(t, 1)) + .then(() => new Promise(resolve => setTimeout(resolve, 5E3))) + .then(() => { + // Update alarm state to disabled + settings.alarm = -1; + storage.writeJSON(SETTINGS_FILE, settings); + }); +} + + +function increaseAlarm(){ + if(isAlarmEnabled()){ + settings.alarm += 5; + } else { + settings.alarm = getCurrentTimeInMinutes() + 5; + } + + storage.writeJSON(SETTINGS_FILE, settings); +} + + +function decreaseAlarm(){ + if(isAlarmEnabled() && (settings.alarm-5 > getCurrentTimeInMinutes())){ + settings.alarm -= 5; + } else { + settings.alarm = -1; + } + + storage.writeJSON(SETTINGS_FILE, settings); +} + +function feedback(){ + Bangle.buzz(40, 0.6); +} + +/* + * Lets start widgets, listen for btn etc. + */ +// Show launcher when middle button pressed +Bangle.setUI("clock"); +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 and change the + * area to the top bar doesn't get cleared. + */ +for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} + +// Clear the screen once, at startup and draw clock +// g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear(); +draw(false); + +// After drawing the watch face, we can draw the widgets +// Bangle.drawWidgets(); diff --git a/apps/notanalog/notanalog.icon.js b/apps/notanalog/notanalog.icon.js new file mode 100644 index 000000000..2af96aef9 --- /dev/null +++ b/apps/notanalog/notanalog.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkE/4AM+cikMRkU/CZoWDkMAgMQgESDB4WBgMSkcykMQDB8xgMjAwcyiETFxoPHD4IwMBxAgBG4gLFCYMgHxExgQXI+USEoMvBhBIJ+URmQMJERQKBiI8BmQZHKRJTBgETmURC48xC5PxgERaBPxga9KgDnJ+KQJKYJFIQoQXKOhAvK+cRgBeBC5ZfF+QVBAAacKBQgWGAALNIX4iJCAA0Bd5kwCw4ABWw3ygJrC+YWJAAJeGRwboBIQhMFj5GFLwcgCAoeFW4kxIwf/IAoXGgARCmQuEUgwXHiczCwMCFwfwC5sBfIMRYwilGC5MSkaTEagwXImbbGC54WGRwwXIbIwXh+YXVh6YHC453GN4IwFO5AXGJAIwFgQXHHwwwHgYXH+AXGGAxnBAAyfHGAwdBAAyfHCQaOKAAMgGBEhOxRIKGYoAJC5YWKVJClLbRjsJAAvyC48vC5v/mJ0RYgyiCiU/CyAASA==")) \ No newline at end of file diff --git a/apps/notanalog/notanalog.png b/apps/notanalog/notanalog.png new file mode 100644 index 000000000..117f19506 Binary files /dev/null and b/apps/notanalog/notanalog.png differ diff --git a/apps/notanalog/screenshot_1.png b/apps/notanalog/screenshot_1.png new file mode 100644 index 000000000..de7d9e61f Binary files /dev/null and b/apps/notanalog/screenshot_1.png differ diff --git a/apps/notanalog/screenshot_2.png b/apps/notanalog/screenshot_2.png new file mode 100644 index 000000000..8515542d4 Binary files /dev/null and b/apps/notanalog/screenshot_2.png differ diff --git a/apps/notanalog/screenshot_3.png b/apps/notanalog/screenshot_3.png new file mode 100644 index 000000000..b638862af Binary files /dev/null and b/apps/notanalog/screenshot_3.png differ diff --git a/apps/notify/metadata.json b/apps/notify/metadata.json new file mode 100644 index 000000000..e92d5e0e4 --- /dev/null +++ b/apps/notify/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "notify", + "name": "Notifications (default)", + "shortName": "Notifications", + "version": "0.11", + "description": "Provides the default `notify` module used by applications to display notifications in a bar at the top of the screen. This module is installed by default by client applications such as the Gadgetbridge app. Installing `Fullscreen Notifications` replaces this module with a version that displays the notifications using the full screen", + "icon": "notify.png", + "type": "notify", + "tags": "widget", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"notify","url":"notify.js"} + ] +} diff --git a/apps/notifyfs/metadata.json b/apps/notifyfs/metadata.json new file mode 100644 index 000000000..dea8cb022 --- /dev/null +++ b/apps/notifyfs/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "notifyfs", + "name": "Fullscreen Notifications", + "shortName": "Notifications", + "version": "0.12", + "description": "Provides a replacement for the `Notifications (default)` `notify` module. This version is used by applications to display notifications fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notify module.", + "icon": "notify.png", + "type": "notify", + "tags": "widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"notify","url":"notify.js"} + ] +} diff --git a/apps/numerals/metadata.json b/apps/numerals/metadata.json new file mode 100644 index 000000000..dcb86da9a --- /dev/null +++ b/apps/numerals/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "numerals", + "name": "Numerals Clock", + "shortName": "Numerals Clock", + "version": "0.10", + "description": "A simple big numerals clock", + "icon": "numerals.png", + "type": "clock", + "tags": "numerals,clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "screenshots": [{"url":"bangle1-numerals-screenshot.png"}], + "storage": [ + {"name":"numerals.app.js","url":"numerals.app.js"}, + {"name":"numerals.img","url":"numerals-icon.js","evaluate":true}, + {"name":"numerals.settings.js","url":"numerals.settings.js"} + ], + "data": [{"name":"numerals.json"}] +} diff --git a/apps/oblique/metadata.json b/apps/oblique/metadata.json new file mode 100644 index 000000000..048c00a38 --- /dev/null +++ b/apps/oblique/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "oblique", + "name": "Oblique Strategies", + "version": "0.01", + "description": "Oblique Strategies for creativity. Copied from Brian Eno.", + "icon": "eno.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"oblique.app.js","url":"app.js"}, + {"name":"oblique.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/omnitrix/metadata.json b/apps/omnitrix/metadata.json new file mode 100644 index 000000000..0c198e6f5 --- /dev/null +++ b/apps/omnitrix/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "omnitrix", + "name": "Omnitrix", + "version": "0.01", + "description": "An Omnitrix Showpiece", + "icon": "omnitrix.png", + "screenshots": [{"url":"screenshot.png"}], + "tags": "game", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"omnitrix.app.js","url":"omnitrix.app.js"}, + {"name":"omnitrix.img","url":"omnitrix.icon.js","evaluate":true} + ] +} diff --git a/apps/openloc/metadata.json b/apps/openloc/metadata.json new file mode 100644 index 000000000..e3043eb8d --- /dev/null +++ b/apps/openloc/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "openloc", + "name": "Open Location / Plus Codes", + "shortName": "Open Location", + "version": "0.01", + "description": "Convert your current GPS location to a series of characters", + "icon": "app.png", + "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"openloc.app.js","url":"app.js"}, + {"name":"openloc.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/openseizure/metadata.json b/apps/openseizure/metadata.json new file mode 100644 index 000000000..d884c48b3 --- /dev/null +++ b/apps/openseizure/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "openseizure", + "name": "OpenSeizureDetector Widget", + "shortName": "Short Name", + "version": "0.01", + "description": "[BETA!] A widget to work alongside [OpenSeizureDetector](https://www.openseizuredetector.org.uk/)", + "icon": "widget.png", + "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"openseizure.wid.js","url":"widget.js"} + ] +} diff --git a/apps/openstmap/custom.html b/apps/openstmap/custom.html index 56dea1188..6e79a6e9a 100644 --- a/apps/openstmap/custom.html +++ b/apps/openstmap/custom.html @@ -2,6 +2,7 @@ +