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 79bd0b046..537a4f697 100644 --- a/apps.json +++ b/apps.json @@ -1,5645 +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.03", - "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.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 - }, - { "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} - ] - }, - { - "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.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 - }, - { - "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 - }, - { - "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.41", - "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.15", - "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.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 - }, - { - "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,gadgetbridge", - "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": "tool,debug,gadgetbridge", - "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.07", - "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.04", - "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"], - "sortorder": -1, - "storage": [ - {"name":"widlock.wid.js","url":"widget.js"} - ] - }, - { - "id": "widbatpc", - "name": "Battery Level Widget (with percentage)", - "shortName": "Battery Widget", - "version": "0.16", - "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.07", - "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.03", - "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"} - ] - }, - { - "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.04", - "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": "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"} - ] - }, - { - "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.09", - "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.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": [] - }, - { - "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.22", - "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.09", - "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", "BANGLEJS2"], - "readme": "README.md", - "allow_emulator": true, - "screenshots": [{"url":"bangle1-workout-HRM-screenshot.png"}], - "storage": [ - {"name":"wohrm.app.js","url":"app.js"}, - {"name":"wohrm.settings.js","url":"settings.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.02", - "description": "On Bangle.js 1 swipe your wrist over the watch face to start your personal Bangle.js 1 hand wash timer. On Bangle.js2 the Pattern Launcher is recommended to start the timer. Start washing after the short buzz and stop after the long buzz 35sec. later.", - "icon": "widget.png", - "type": "widget", - "tags": "widget,tool", - "allow_emulator": true, - "supports": ["BANGLEJS", "BANGLEJS2"], - "storage": [ - {"name":"widhwt.app.js","url":"app.js"}, - {"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.04", - "description": "Chronometer (timer) which runs as widget.", - "icon": "app.png", - "tags": "tool,widget", - "supports": ["BANGLEJS","BANGLEJS2"], - "screenshots": [{"url":"screenshot.png"}], - "readme": "README.md", - "storage": [ - {"name":"chronowid.wid.js","url":"widget.js"}, - {"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.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} - ] - }, - { - "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.04", - "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": "run", - "name": "Run", - "version":"0.01", - "description": "Displays distance, time, steps, cadence, pace and more for runners.", - "icon": "app.png", - "tags": "run,running,fitness,outdoors,gps", - "supports" : ["BANGLEJS","BANGLEJS2"], - "screenshots": [{"url":"screenshot.png"}], - "readme": "README.md", - "storage": [ - {"name":"run.app.js","url":"app.js"}, - {"name":"run.img","url":"app-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.03", - "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","BANGLEJS2"], - "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": "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} - ] - }, - { - "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.05", - "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.07", - "description": "Automatically turn Quiet Mode on or off at set times, change theme and 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.02", - "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.11", - "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.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"}] - }, - { - "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.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"}] - }, - { - "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.04", - "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.03", - "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.13", - "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.03", - "description": "TimeCal shows the Time along with a 3 week calendar", - "tags": "clock", - "readme":"README.md", - "type": "clock", - "supports":["BANGLEJS2"], - "storage": [ - {"name":"timecal.app.js","url":"timecal.app.js"}, - {"name":"timecal.settings.js","url":"timecal.settings.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.04", - "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"], - "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.04", - "description": "A clock which displays current weather conditions (requires Gadgetbridge and Weather apps).", - "icon": "app.png", - "screenshots": [{"url":"screens/screen1.png"}], - "type": "clock", - "tags": "clock, weather", - "supports": ["BANGLEJS","BANGLEJS2"], - "allow_emulator": true, - "readme": "README.md", - "storage": [ - {"name":"weatherClock.app.js","url":"app.js"}, - {"name":"weatherClock.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "menuwheel", - "name": "Wheel Menus", - "version": "0.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"} - ] - }, - { "id": "widChargingStatus", - "name": "Charging Status", - "shortName":"ChargingStatus", - "icon": "widget.png", - "version":"0.1", - "type": "widget", - "description": "A simple widget that shows a yellow lightning icon to indicate whenever the watch is charging. This way one can see the charging status at a glance, no matter which battery widget is being used.", - "tags": "widget", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"widChargingStatus.wid.js","url":"widget.js"} - ] - }, - { - "id": "flow", - "name": "FLOW", - "shortName": "FLOW", - "version": "0.01", - "description": "A game where you have to help a flow avoid white obstacles thing by tapping! This is a demake of an app which I forgot the name of. Press BTN(1) to restart. See if you can get to 2500 score!", - "icon": "app.png", - "tags": "game", - "supports" : ["BANGLEJS", "BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name": "flow.app.js", "url": "app.js" }, - {"name": "flow.img", "url": "app-icon.js","evaluate": true } - ] - }, - { "id": "scribble", - "name": "Scribble", - "shortName":"Scribble", - "version":"0.01", - "type": "app", - "description": "A keyboard on your wrist! Swipe right for space, left for delete.", - "icon": "app.png", - "allow_emulator": true, - "tags": "tools, keyboard, text, scribble", - "supports" : ["BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"scribble.app.js","url":"app.js"}, - {"name":"scribble.img","url":"app-icon.js","evaluate":true} - ], - "screenshots":[ - { "url":"screenshot.png" } - ] - }, - { - "id": "ptlaunch", - "name": "Pattern Launcher", - "shortName": "Pattern Launcher", - "version": "0.11", - "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": "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", - "allow_emulator": true, - "version":"0.01", - "description": "Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awair device.", - "tags": "tool,health", - "readme":"README.md", - "supports":["BANGLEJS2"], - "storage": [ - {"name":"awairmonitor.app.js","url":"app.js"}, - {"name":"awairmonitor.img","url":"app-icon.js","evaluate":true} - ] - }, - { "id": "pooqround", - "name": "pooq Round watch face", - "shortName":"pooq Round", - "version":"0.02", - "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.03", - "description": "Display CoreTemp device sensor data", - "icon": "coretemp.png", - "type": "app", - "tags": "health", - "readme": "README.md", - "supports": ["BANGLEJS","BANGLEJS2"], - "storage": [ - {"name":"coretemp.boot.js","url":"boot.js"}, - {"name":"coretemp.app.js","url":"coretemp.js"}, - {"name":"coretemp.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"}] - }, - { - "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.02", - "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.05", - "description": "A clock with circles for different data at the bottom in a probably familiar style", - "icon": "app.png", - "screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.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": "ftclock", - "name": "Four Twenty Clock", - "version": "0.02", - "description": "A clock that tells when and where it's going to be 4:20 next", - "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot1.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"ftclock.app.js","url":"app.js"}, - {"name":"fourTwenty","url":"fourTwenty.js"}, - {"name":"fourTwentyTz","url":"fourTwentyTz.js"}, - {"name":"ftclock.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "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} - ] - }, - { - "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.02", - "description": "Replace the built in menu function. Supports Bangle.js 1 and Bangle.js 2.", - "icon": "icon.png", - "type": "boot", - "tags": "system", - "supports": ["BANGLEJS","BANGLEJS2"], - "screenshots": [{"url":"pro-menu-screenshot.png"}], - "storage": [ - {"name":"promenu.boot.js","url":"boot.js","supports": ["BANGLEJS"]}, - {"name":"promenu.boot.js","url":"bootb2.js","supports": ["BANGLEJS2"]}, - {"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.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} - ] - }, - { "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.01", - "description": "A classic sonic clock featuring run, stop and wait animations.", - "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS2"], - "allow_emulator": true, - "readme": "README.md", - "storage": [ - {"name":"sonicclk.app.js","url":"app.js"}, - {"name":"sonicclk.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "touchmenu", - "name": "TouchMenu", - "version": "0.01", - "description": "Redesigned menu that uses the full touchscreen on the Bangle.js 2", - "screenshots": [{"url":"touchmenu.gif"}], - "icon": "touchmenu.png", - "type": "bootloader", - "tags": "tool", - "supports": ["BANGLEJS2"], - "storage": [ - {"name":"touchmenu.boot.js","url":"touchmenu.boot.js"} - ] - }, - { - "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"}] - }, - { - "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} - ] - }, - { "id": "ruuviwatch", - "name": "Ruuvi Watch", - "shortName":"Ruuvi Watch", - "icon": "ruuviwatch.png", - "version":"1.01", - "description": "Keep an eye on RuuviTag devices (https://ruuvi.com). Only shows RuuviTags using the v5 format.", - "readme":"README.md", - "tags": "bluetooth", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"ruuviwatch.app.js","url":"ruuviwatch.app.js"}, - {"name":"ruuviwatch.img","url":"ruuviwatch.app-icon.js","evaluate":true} - ] - }, - { - "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} - ] - }, - { "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"} - ] - }, - { - "id": "widpa", - "name": "Simple Pedometer", - "shortName":"Simple Pedometer", - "icon": "screenshot_widpa.png", - "screenshots": [{"url":"screenshot_widpa.png"}], - "version":"0.02", - "type": "widget", - "supports": ["BANGLEJS", "BANGLEJS2"], - "readme": "README.md", - "description": "Displays the current step count from `Bangle.getHealthStatus(\"day\").steps` in 12x16 font, requires firmware v2.11.21 or later", - "tags": "widget,battery", - "storage": [ - {"name":"widpa.wid.js","url":"widpa.wid.js"} - ] - }, - { - "id": "widpb", - "name": "Lato Pedometer", - "shortName":"Lato Pedometer", - "icon": "screenshot_widpb.png", - "screenshots": [{"url":"screenshot_widpb.png"}], - "version":"0.02", - "type": "widget", - "supports": ["BANGLEJS", "BANGLEJS2"], - "readme": "README.md", - "description": "Displays the current step count from `Bangle.getHealthStatus(\"day\").steps` in the Lato font, requires firmware v2.11.21 or later", - "tags": "widget,battery", - "storage": [ - {"name":"widpb.wid.js","url":"widpb.wid.js"} - ] - }, - { - "id": "timeandlife", - "name": "Time and Life", - "shortName":"Time and Lfie", - "icon": "app.png", - "version":"0.1", - "description": "A simple watchface which displays the time when the screen is tapped and decays according to the rules of Conway's game of life.", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS2"], - "allow_emulator":true, - "readme": "README.md", - "storage": [ - {"name":"timeandlife.app.js","url":"app.js"}, - {"name":"timeandlife.img","url":"app-icon.js","evaluate":true} - ] - }, - { "id": "acmaze", - "name": "AccelaMaze", - "shortName":"AccelaMaze", - "version":"0.01", - "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} - ] - } + +{%- 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 index cc8e21d1f..f2aa79920 100644 --- a/apps/ac_ac/Customizer.html +++ b/apps/ac_ac/Customizer.html @@ -101,8 +101,8 @@ 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 414b9d9fb..5bb531bc7 100644 --- a/apps/gpsinfo/ChangeLog +++ b/apps/gpsinfo/ChangeLog @@ -6,3 +6,4 @@ 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 f4521f265..0eca2ccf5 100644 --- a/apps/gpsinfo/gps-info.js +++ b/apps/gpsinfo/gps-info.js @@ -16,9 +16,8 @@ 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) { @@ -63,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" }, @@ -92,11 +91,12 @@ function onGPS(fix) { 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; @@ -115,27 +115,31 @@ function onGPS(fix) { layout.sat.label = fix.satellites; layout.render(layout.sat); } - if (SATinView != lastFix.SATinView) { + if (SATinView != lastSATinView) { + if (!leaveNofixLayout) SATinView = -1; + lastSATinView = SATinView; layout.clear(layout.progress); - layout.progress.label = "in view: " + SATinView; + 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; - lastFix.SATinView = SATinView; } function onGPSraw(nmea) { - if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13)); - if (nmea.slice(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13)); - SATinView = nofBD + nofGP; + if (nmea.slice(3,6) == "GSV") { + // console.log(nmea.slice(1,3) + " " + nmea.slice(11,13)); + if (nmea.slice(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13)); + 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; + } } 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/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/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 146ff1b05..2893d3193 100644 --- a/apps/lapcounter/ChangeLog +++ b/apps/lapcounter/ChangeLog @@ -1,2 +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.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 dba05915b..1abd519ab 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -9,5 +9,6 @@ 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 to data. -0.13: Improved battery visualization. \ No newline at end of file +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 46e134f78..017be246c 100644 --- a/apps/lcars/README.md +++ b/apps/lcars/README.md @@ -13,10 +13,10 @@ To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerda * 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 images of real planets. + * 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 for steps + hrm on the second screen. + * Display graphs (day or month) for steps + hrm on the second screen. ## Data that can be configured * Steps - Steps loaded via the health module @@ -25,16 +25,20 @@ To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerda * 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) +# Ideas +- Tap top / bottom to disable steps (also icon) and start a timer + ## Contributors -- Initial creation and improvements: [David Peer](https://github.com/peerdavid). -- Improvements: [Adam Schmalhofer](https://github.com/adamschmalhofer). -- Improvements: [Jon Warrington](https://github.com/BartokW). +- [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 3b9745a7a..81a501481 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -26,10 +26,8 @@ let cGrey = "#424242"; * Global lcars variables */ let lcarsViewPos = 0; -let drag; -let hrmValue = 0; +// let hrmValue = 0; var plotMonth = false; -var disableInfoUpdate = true; // When gadgetbridge connects, step infos cannot be loaded /* * Requirements and globals @@ -115,12 +113,43 @@ 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); + g.setColor(c); + g.fillRect(79, y-2, 85 ,y+18); + + g.setFontAlign(0,-1,0); + g.drawString(value, 110, y); + + g.setColor(c); + g.setFontAlign(-1,-1,0); + g.fillRect(133, y-2, 165 ,y+18); + g.fillCircle(161, y+8, 10); + g.setColor(cBlack); + 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"; @@ -134,7 +163,7 @@ function printData(key, y, c){ value = E.getAnalogVRef().toFixed(2) + "V"; } else if(key == "HRM"){ - value = hrmValue; + value = Math.round(Bangle.getHealthStatus("day").bpm); } else if (key == "TEMP"){ var weather = getWeather(); @@ -143,24 +172,29 @@ function printData(key, y, c){ } else if (key == "HUMIDITY"){ text = "HUM"; var weather = getWeather(); - value = weather.hum + "%"; + 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())); } - g.setColor(c); - g.fillRect(79, y-2, 85 ,y+18); - - g.setFontAlign(0,-1,0); - g.drawString(value, 110, y); - - g.setColor(c); - g.setFontAlign(-1,-1,0); - g.fillRect(133, y-2, 165 ,y+18); - g.fillCircle(161, y+8, 10); - g.setColor(cBlack); - g.drawString(text, 135, y); + // Print for all datapoints that are not async + if(should_print){ + printRow(text, value, y, c); + } } function drawHorizontalBgLine(color, x1, x2, y, h){ @@ -273,9 +307,9 @@ function drawPosition0(){ // 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(); @@ -446,7 +480,8 @@ function getWeather(){ wrose: "-" }; } else { - weather.temp = locale.temp(parseInt(weather.temp-273.15)) + weather.temp = locale.temp(Math.round(weather.temp-273.15)) + weather.hum = weather.hum + "%"; } return weather; @@ -519,10 +554,6 @@ Bangle.on('charging',function(charging) { drawState(); }); -Bangle.on('HRM', function (hrm) { - hrmValue = hrm.bpm; -}); - function increaseAlarm(){ if(isAlarmEnabled()){ diff --git a/apps/lcars/lcars.settings.js b/apps/lcars/lcars.settings.js index ba630799a..076dea4d1 100644 --- a/apps/lcars/lcars.settings.js +++ b/apps/lcars/lcars.settings.js @@ -18,14 +18,14 @@ storage.write(SETTINGS_FILE, settings) } - var data_options = ["Steps", "Battery", "VREF", "HRM", "Temp", "Humidity", "CoreT"]; + 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: 6, + 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: 6, + 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: 6, + 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/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/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/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/ChangeLog b/apps/matrixclock/ChangeLog index 7cc9144b1..23819e26c 100644 --- a/apps/matrixclock/ChangeLog +++ b/apps/matrixclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: Initial Release 0.02: Support for Bangle 2 +0.03: Keep the date from being overwritten, use correct colour from theme for clearing diff --git a/apps/matrixclock/matrixclock.js b/apps/matrixclock/matrixclock.js index ab18c13b8..e116280af 100644 --- a/apps/matrixclock/matrixclock.js +++ b/apps/matrixclock/matrixclock.js @@ -147,7 +147,7 @@ function draw_clock(){ g.setFont("Vector", g.getWidth() / 5); g.setFontAlign(0,-1); if(last_draw_time == null || now.getMinutes() != last_draw_time.getMinutes()){ - g.setColor(g.theme.fg); + g.setColor(g.theme.bg); g.drawString(timeStr, w/2, TIME_Y_COORD); timeStr = format_time(now); } @@ -158,12 +158,12 @@ function draw_clock(){ g.setFont("Vector",15); g.setFontAlign(0,-1,0); if(last_draw_time == null || now.getDate() != last_draw_time.getDate()){ - g.setColor(g.theme.fg); + g.setColor(g.theme.bg); g.drawString(dateStr, w/2, DATE_Y_COORD); dateStr = format_date(now); - g.setColor(SHARD_COLOR[0], SHARD_COLOR[1], SHARD_COLOR[2]); - g.drawString(dateStr, w/2, DATE_Y_COORD); } + g.setColor(SHARD_COLOR[0], SHARD_COLOR[1], SHARD_COLOR[2]); + g.drawString(dateStr, w/2, DATE_Y_COORD); last_draw_time = now; } diff --git a/apps/matrixclock/metadata.json b/apps/matrixclock/metadata.json new file mode 100644 index 000000000..5e05698a5 --- /dev/null +++ b/apps/matrixclock/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "matrixclock", + "name": "Matrix Clock", + "version": "0.03", + "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/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/app.js b/apps/messages/app.js index 80e4a3244..3e692a0cc 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -280,7 +280,7 @@ function showMessage(msgid) { 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 }:{}, ]}, ]}, 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/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/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/ChangeLog b/apps/morse/ChangeLog index 5560f00bc..f426dbd5c 100644 --- a/apps/morse/ChangeLog +++ b/apps/morse/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Fixed font name diff --git a/apps/morse/metadata.json b/apps/morse/metadata.json new file mode 100644 index 000000000..baa998aa3 --- /dev/null +++ b/apps/morse/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "morse", + "name": "Morse Code", + "version": "0.02", + "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/morse/morse-code.js b/apps/morse/morse-code.js index 227aeed81..7fd22d32d 100644 --- a/apps/morse/morse-code.js +++ b/apps/morse/morse-code.js @@ -4,7 +4,7 @@ /** * Constants */ -const FONT_NAME = 'Vector12'; +const FONT_NAME = 'Vector'; const FONT_SIZE = 80; const SCREEN_PIXELS = 240; const UNIT = 100; @@ -144,4 +144,4 @@ setWatch(step(true), BTN1, { repeat: true }); setWatch(step(false), BTN3, { repeat: true }); // Toggle buzzing/beeping with the touchscreen setWatch(toggleBuzzing, BTN4, { repeat: true }); -setWatch(toggleBuzzing, BTN5, { repeat: true }); \ No newline at end of file +setWatch(toggleBuzzing, BTN5, { repeat: 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 80ab29c56..6e79a6e9a 100644 --- a/apps/openstmap/custom.html +++ b/apps/openstmap/custom.html @@ -116,8 +116,33 @@ TODO: if (document.getElementById("3bit").checked) { options = { compression:false, output:"raw", - mode:"3bit", brightness:-64, + mode:"3bit", }; + /* If in 3 bit mode, go through all the data beforehand and + turn the saturation up to maximum, so when thresholded it + works a lot better */ + var imageData = ctx.getImageData(0,0,width,height); + var rgba = imageData.data; + var l = width*height*4; + for (var i=0;i240 || d<8) { // white or grey + rgba[i+0]=255; + rgba[i+1]=255; + rgba[i+2]=255; + } else { // another colour - use max saturation + rgba[i+0] = (rgba[i+0]-min) * 255 / d; + rgba[i+1] = (rgba[i+1]-min) * 255 / d; + rgba[i+2] = (rgba[i+2]-min) * 255 / d; + } + } + ctx.putImageData(imageData,0,0); } console.log("Compression options", options); var w = Math.round(width / TILESIZE); @@ -131,7 +156,7 @@ TODO: options.width = TILESIZE; options.height = TILESIZE; var imgstr = imageconverter.RGBAtoString(rgba, options); - ctx.putImageData(imageData,x*TILESIZE, y*TILESIZE); + ctx.putImageData(imageData,x*TILESIZE, y*TILESIZE); // write preview /*var compress = 'require("heatshrink").decompress(' if (!imgstr.startsWith(compress)) throw "Data in wrong format"; imgstr = imgstr.slice(compress.length,-1);*/ diff --git a/apps/openstmap/metadata.json b/apps/openstmap/metadata.json new file mode 100644 index 000000000..2dc9bd427 --- /dev/null +++ b/apps/openstmap/metadata.json @@ -0,0 +1,18 @@ +{ + "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} + ] +} diff --git a/apps/osgridref/metadata.json b/apps/osgridref/metadata.json new file mode 100644 index 000000000..fe44e4d4c --- /dev/null +++ b/apps/osgridref/metadata.json @@ -0,0 +1,14 @@ +{ + "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} + ] +} diff --git a/apps/osmpoi/metadata.json b/apps/osmpoi/metadata.json new file mode 100644 index 000000000..4abb07548 --- /dev/null +++ b/apps/osmpoi/metadata.json @@ -0,0 +1,15 @@ +{ + "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} + ] +} diff --git a/apps/pastel/ChangeLog b/apps/pastel/ChangeLog index 00090fcd1..c81d0cacc 100644 --- a/apps/pastel/ChangeLog +++ b/apps/pastel/ChangeLog @@ -9,3 +9,5 @@ 0.09: Added dependancy on Pedometer Widget 0.10: Added Weather line, fixed issues on a Bangle 1, update every minute 0.11: Changed cycle on minute to prevInfo to avoid the 2nd one being the blank line +0.12: Removed dependancy on widpedom, now uses Bangle.getHealthStatus("day").steps + which requires 2.11.27 firmware to reset at midnight diff --git a/apps/pastel/metadata.json b/apps/pastel/metadata.json new file mode 100644 index 000000000..1a311599c --- /dev/null +++ b/apps/pastel/metadata.json @@ -0,0 +1,28 @@ +{ + "id": "pastel", + "name": "Pastel Clock", + "shortName": "Pastel", + "version": "0.12", + "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. Requires firmware 2.11.27", + "icon": "pastel.png", + "dependencies": {"mylocation":"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"}] +} diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 3e64cdd9c..d9dfb0da5 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -64,13 +64,15 @@ function loadFonts() { require("f_lato").add(Graphics); } -function stepsWidget() { - if (WIDGETS.activepedom !== undefined) { - return WIDGETS.activepedom; - } else if (WIDGETS.wpedom !== undefined) { - return WIDGETS.wpedom; +function getSteps() { + try { + return Bangle.getHealthStatus("day").steps; + } catch (e) { + if (WIDGETS.wpedom !== undefined) + return WIDGETS.wpedom.getSteps(); + else + return '???' } - return undefined; } const infoData = { @@ -79,7 +81,7 @@ const infoData = { ID_DAY: { calc: () => {var d = require("locale").dow(new Date).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} }, ID_SR: { calc: () => 'Sunrise: ' + sunRise }, ID_SS: { calc: () => 'Sunset: ' + sunSet }, - ID_STEP: { calc: () => 'Steps: ' + stepsWidget().getSteps() }, + ID_STEP: { calc: () => 'Steps: ' + getSteps() }, ID_BATT: { calc: () => 'Battery: ' + E.getBattery() + '%' }, ID_MEM: { calc: () => {var val = process.memory(); return 'Ram: ' + Math.round(val.usage*100/val.total) + '%';} }, ID_ID: { calc: () => {var val = NRF.getAddress().split(':'); return 'Id: ' + val[4] + val[5];} }, diff --git a/apps/pebble/metadata.json b/apps/pebble/metadata.json new file mode 100644 index 000000000..4295d7507 --- /dev/null +++ b/apps/pebble/metadata.json @@ -0,0 +1,19 @@ +{ + "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} + ] +} diff --git a/apps/pebbled/ChangeLog b/apps/pebbled/ChangeLog index a678cffdb..9db0e26c5 100644 --- a/apps/pebbled/ChangeLog +++ b/apps/pebbled/ChangeLog @@ -1 +1 @@ -0.1: first release +0.01: first release diff --git a/apps/pebbled/metadata.json b/apps/pebbled/metadata.json new file mode 100644 index 000000000..c16025f6f --- /dev/null +++ b/apps/pebbled/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "pebbled", + "name": "Pebble Clock with distance", + "shortName": "Pebble + distance", + "version": "0.01", + "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} + ] +} diff --git a/apps/petrock/metadata.json b/apps/petrock/metadata.json new file mode 100644 index 000000000..bb9cea555 --- /dev/null +++ b/apps/petrock/metadata.json @@ -0,0 +1,14 @@ +{ + "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} + ] +} diff --git a/apps/pipboy/metadata.json b/apps/pipboy/metadata.json new file mode 100644 index 000000000..34a6cb0ac --- /dev/null +++ b/apps/pipboy/metadata.json @@ -0,0 +1,16 @@ +{ + "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} + ] +} diff --git a/apps/pizzatimer/metadata.json b/apps/pizzatimer/metadata.json new file mode 100644 index 000000000..e960ecacc --- /dev/null +++ b/apps/pizzatimer/metadata.json @@ -0,0 +1,15 @@ +{ + "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} + ] +} diff --git a/apps/planetarium/metadata.json b/apps/planetarium/metadata.json new file mode 100644 index 000000000..00e998634 --- /dev/null +++ b/apps/planetarium/metadata.json @@ -0,0 +1,20 @@ +{ + "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"}] +} diff --git a/apps/pomodo/metadata.json b/apps/pomodo/metadata.json new file mode 100644 index 000000000..a60009555 --- /dev/null +++ b/apps/pomodo/metadata.json @@ -0,0 +1,16 @@ +{ + "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} + ] +} diff --git a/apps/pong/metadata.json b/apps/pong/metadata.json new file mode 100644 index 000000000..192af8ee6 --- /dev/null +++ b/apps/pong/metadata.json @@ -0,0 +1,18 @@ +{ + "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} + ] +} diff --git a/apps/pooqroman/metadata.json b/apps/pooqroman/metadata.json new file mode 100644 index 000000000..8cdbea728 --- /dev/null +++ b/apps/pooqroman/metadata.json @@ -0,0 +1,19 @@ +{ "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"} + ] +} diff --git a/apps/pooqround/metadata.json b/apps/pooqround/metadata.json new file mode 100644 index 000000000..27f98a7b6 --- /dev/null +++ b/apps/pooqround/metadata.json @@ -0,0 +1,19 @@ +{ "id": "pooqround", + "name": "pooq Round watch face", + "shortName":"pooq Round", + "version":"0.02", + "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"} + ] +} diff --git a/apps/poweroff/metadata.json b/apps/poweroff/metadata.json new file mode 100644 index 000000000..80e71a1d2 --- /dev/null +++ b/apps/poweroff/metadata.json @@ -0,0 +1,15 @@ +{ "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} +] +} diff --git a/apps/pparrot/ChangeLog b/apps/pparrot/ChangeLog index 5560f00bc..a7262b0c9 100644 --- a/apps/pparrot/ChangeLog +++ b/apps/pparrot/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Bangle.js 2 compatibility diff --git a/apps/pparrot/metadata.json b/apps/pparrot/metadata.json new file mode 100644 index 000000000..9aaeb94ac --- /dev/null +++ b/apps/pparrot/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "pparrot", + "name": "Party Parrot", + "version": "0.02", + "description": "Party with a parrot on your wrist", + "icon": "party-parrot.png", + "type": "app", + "tags": "party,parrot,lol", + "supports": ["BANGLEJS", "BANGLEJS2"], + "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} + ] +} diff --git a/apps/pparrot/party-parrot.js b/apps/pparrot/party-parrot.js index a62b88bc9..4d5dcf9b5 100644 --- a/apps/pparrot/party-parrot.js +++ b/apps/pparrot/party-parrot.js @@ -5,16 +5,17 @@ var imgs = [ atob("qE5xH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AdwOBlcAAAsrq2BJn+BqxMHKX5NFJhgAFqxO5TYcruug0HLAAoIBvdQCIZN11icDqBLHAA+gMYRQ0TgcrvhNOAAaiCeWWBTgZNSKAuBJ17rDvZOVeQK4C1hOxdaYAFvbxvdgZOGbgMlLCF8DwSgrJxSKCKALvRUFmslbsJJ4YMG0F1qElld10ATGgGBJ9BOCvaLHHYgNEIoqsBKAIJFUFDtCurbIvhPHcgcrAAL9DBQclUFDtCGQIAJIIUAcYQHDq2ArmAqxsDfIL2BKFAxCvhPK0F7uoODSYVWrgACwBXCLwYQDlaekE4ROKAA97CwJODAAJuCfwYABuqglwKeNAA9QMoJPFrjwDAAjxBlesd0hOSHgeAJwjwDWRCgh1guBqBPTHYKfHOBIIBqxPhEgN7J6yfFJ5VQBILwgwJPWWwJPK0DwpJ66LBd6OgJ8TvXuoXBJ6HLBINWJ8VQJ6d8HYROEwD5BkpP/bYpPFrgIBuoUHqEAlZPiZxAAMHYWAdw18CY91BYOsJ8WgJ6d7UAzuBN5JPCwJPivagUC4MrJwoeJJ4VcJ72BJ4UrJ6igCKALtCqASJqDvhJ4bwVHodWTwQcJ0AQCJz3+QASCMABclDYd1MBmBJ76ABkrYCvZPUR4QABBxInCT0DuCJYJ3CeKt1NJZdClesd0RKBFIbxVqEldtusdwJJDvgqBqDxVTxjtgdwd8Fgd7KC77MT0H+TwLQGKAT4CJ/9WTwxQGupPbd4QABldWq2B1hPbTwwvDVYJRCUbRxDAAhTCJyusSRowEktQAAKhXvd1qBSHKKmBdxIwFuotFUjQjBvgkElZPWlaOCABRPFChwAPEYpPSqy9GAGlWJ6JO7UJgA=="), atob("qE5xH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AtwNWlcAAAUrq2BJP5MJAApS/JoRMJAApR7JotPumfAA10VQlWJ2+AHwZMB4nDABHEz9PeoesJ2icDp5NLKIpkCKGesG4UlJoJOBKJQJBBYT1BeQRQwwJOCTgI/Bb4V0KAxIBp8llb+BCYlWJ2V0IYbzCgBCBJwgKDBoefkoFCUFuBGISWEIglPBI8rAAIYDCYeAJ2hPJcgmArmAqylGJ9ZODz5OEJ5IICq1cAAZQGlZOuJoiWFVQIICcgRPFrhLCuklAgOBJ0+AlZOJJAYABAwgUBJwtcDwMr4itCeE+sJwV0dgoAKU4UrwBPFeARuBUAMr1jslJyhPBIAMlJ5auCeEotCJyRPBMwRPLeAVWJ0eAE4NPJqLvR4iglwIlBFgQATH4ROFJ4qgmFYUAdqRPSWASghTwclJ6qPDJ4srBIIQGwCejT63DksAlZPHp4iD4gQCT0SfXb4ZPFA4N0CIl0eECeCZgSfWeA4hCz4QHJ7usEANPOgQtFeCagDOYV0OIpPCeDruCugkCZoqgUlbtDkpwGB4UAJ7mATYXEQoNPeC3EXYVWTwS/HJ8ErD4IlBeDXDEAQABNxBPfPQTqCEoSgXIIhtJf4ZOawIeBkomDUDYbCNhJPCwBOZwB6Hz8lK4oAT4lPNZRcCwJOheL10DJHEO4LuZdgUAugoHPAQ2JADDubToZOHAATVBBpb6YgGsJy1WdhJQIlYQMTylWJ05QEUQOfejd0EAOBJzElRh/EFwRRDAATuWkrBBJysrbaufKIhqCd1ydCJyZREQYIACDizuWJzLUDdoLyBDSq9CJ6eBJzYAbJ4WsTyuf4gAzT6usCoKfBp4AzlY4BwBPRCoQA5qxPRJ3ZQMA==") ]; +var scale = g.getWidth()<200 ? 2 : 3; function drawImg (i) { g.drawImage({ width: 80, height: 57, bpp: 8, buffer: require("heatshrink").decompress(imgs[i]) - }, 0, 0, {scale: 3}); + }, (g.getWidth()-80*scale)/2, (g.getHeight()-57*scale)/2, {scale: scale}); } var currImg = 0; -g.clear(); +g.setBgColor(0).clear(); drawImg(currImg); setInterval(function() { currImg = (currImg + 1) % imgs.length; diff --git a/apps/presentor/ChangeLog b/apps/presentor/ChangeLog index 00833cf91..807f41e79 100644 --- a/apps/presentor/ChangeLog +++ b/apps/presentor/ChangeLog @@ -1,8 +1,8 @@ -0.1: Start of app. -0.5: BLE keyboard functionality. -1.0: BLE mouse functionality to scroll back/forward. -1.5: Added accelerator style mouse. -2.0: Added touchpad style mouse. -2.1: Initial internal git(hub) release. Added icon and such. -2.2: Begin work on presentation parts. -3.0: Presentation parts! \ No newline at end of file +0.01: Start of app. +0.02: BLE keyboard functionality. +0.03: BLE mouse functionality to scroll back/forward. +0.04: Added accelerator style mouse. +0.05: Added touchpad style mouse. +0.06: Initial internal git(hub) release. Added icon and such. +0.07: Begin work on presentation parts. +0.08: Presentation parts! diff --git a/apps/presentor/metadata.json b/apps/presentor/metadata.json new file mode 100644 index 000000000..e5b5e289f --- /dev/null +++ b/apps/presentor/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "presentor", + "name": "Presentor", + "version": "0.08", + "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"} + ] +} diff --git a/apps/promenu/metadata.json b/apps/promenu/metadata.json new file mode 100644 index 000000000..d70f36b0a --- /dev/null +++ b/apps/promenu/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "promenu", + "name": "Pro Menu", + "version": "0.02", + "description": "Replace the built in menu function. Supports Bangle.js 1 and Bangle.js 2.", + "icon": "icon.png", + "type": "boot", + "tags": "system", + "supports": ["BANGLEJS","BANGLEJS2"], + "screenshots": [{"url":"pro-menu-screenshot.png"}], + "storage": [ + {"name":"promenu.boot.js","url":"boot.js","supports": ["BANGLEJS"]}, + {"name":"promenu.boot.js","url":"bootb2.js","supports": ["BANGLEJS2"]}, + {"name":"promenu.img","url":"promenuIcon.js","evaluate":true} + ] +} diff --git a/apps/ptlaunch/metadata.json b/apps/ptlaunch/metadata.json new file mode 100644 index 000000000..6c3870d24 --- /dev/null +++ b/apps/ptlaunch/metadata.json @@ -0,0 +1,18 @@ +{ + "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"}] +} diff --git a/apps/puzzle15/metadata.json b/apps/puzzle15/metadata.json new file mode 100644 index 000000000..b895e0e12 --- /dev/null +++ b/apps/puzzle15/metadata.json @@ -0,0 +1,19 @@ +{ + "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"}] +} diff --git a/apps/qalarm/metadata.json b/apps/qalarm/metadata.json new file mode 100644 index 000000000..326ba33a7 --- /dev/null +++ b/apps/qalarm/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "qalarm", + "name": "Q Alarm and Timer", + "shortName": "Q Alarm", + "icon": "app.png", + "version": "0.04", + "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" }] +} diff --git a/apps/qmsched/metadata.json b/apps/qmsched/metadata.json new file mode 100644 index 000000000..daeaad624 --- /dev/null +++ b/apps/qmsched/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "qmsched", + "name": "Quiet Mode Schedule and Widget", + "shortName": "Quiet Mode", + "version": "0.07", + "description": "Automatically turn Quiet Mode on or off at set times, change theme and 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"}] +} diff --git a/apps/qrcode/metadata.json b/apps/qrcode/metadata.json new file mode 100644 index 000000000..22f8f7b53 --- /dev/null +++ b/apps/qrcode/metadata.json @@ -0,0 +1,15 @@ +{ + "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} + ] +} diff --git a/apps/rclock/metadata.json b/apps/rclock/metadata.json new file mode 100644 index 000000000..77a036481 --- /dev/null +++ b/apps/rclock/metadata.json @@ -0,0 +1,15 @@ +{ + "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} + ] +} diff --git a/apps/rebble/metadata.json b/apps/rebble/metadata.json new file mode 100644 index 000000000..212a7b5b3 --- /dev/null +++ b/apps/rebble/metadata.json @@ -0,0 +1,19 @@ +{ + "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} + ] +} diff --git a/apps/recorder/metadata.json b/apps/recorder/metadata.json new file mode 100644 index 000000000..815a7db40 --- /dev/null +++ b/apps/recorder/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "recorder", + "name": "Recorder (BETA)", + "shortName": "Recorder", + "version": "0.07", + "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}] +} diff --git a/apps/rndmclk/metadata.json b/apps/rndmclk/metadata.json new file mode 100644 index 000000000..e837c4bce --- /dev/null +++ b/apps/rndmclk/metadata.json @@ -0,0 +1,14 @@ +{ + "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"} + ] +} diff --git a/apps/route/metadata.json b/apps/route/metadata.json new file mode 100644 index 000000000..bc1962868 --- /dev/null +++ b/apps/route/metadata.json @@ -0,0 +1,14 @@ +{ + "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} + ] +} diff --git a/apps/rpgdice/metadata.json b/apps/rpgdice/metadata.json new file mode 100644 index 000000000..5a37a1f43 --- /dev/null +++ b/apps/rpgdice/metadata.json @@ -0,0 +1,16 @@ +{ + "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} + ] +} diff --git a/apps/rtorch/metadata.json b/apps/rtorch/metadata.json new file mode 100644 index 000000000..ee056ac57 --- /dev/null +++ b/apps/rtorch/metadata.json @@ -0,0 +1,16 @@ +{ + "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} + ] +} diff --git a/apps/run/ChangeLog b/apps/run/ChangeLog index 5560f00bc..11d63b402 100644 --- a/apps/run/ChangeLog +++ b/apps/run/ChangeLog @@ -1 +1,6 @@ 0.01: New App! +0.02: Set pace format to mm:ss, time format to h:mm:ss, + added settings to opt out of GPS and HRM +0.03: Fixed distance calculation, tested against Garmin Etrex, Amazfit GTS 2 +0.04: Use the exstats module, and make what is displayed configurable +0.05: exstats updated so update 'distance' label is updated, option for 'speed' diff --git a/apps/run/README.md b/apps/run/README.md index c094d4873..10f6bafdb 100644 --- a/apps/run/README.md +++ b/apps/run/README.md @@ -28,7 +28,24 @@ so if you have no GPS lock you just need to wait. However you can just install the `Recorder` app, turn recording on in that, and then start the `Run` app. +## Settings + +Under `Settings` -> `App` -> `Run` you can change settings for this app. + +* `Pace` is the distance that pace should be shown over - 1km, 1 mile, 1/2 Marathon or 1 Marathon +* `Box 1/2/3/4/5/6` are what should be shown in each of the 6 boxes on the display. From the top left, down. + If you set it to `-` nothing will be displayed, so you can display only 4 boxes of information + if you wish by setting the last 2 boxes to `-`. + ## TODO * Allow this app to trigger the `Recorder` app on and off directly. * Keep a log of each run's stats (distance/steps/etc) + +## Development + +This app uses the [`exstats` module](/modules/exstats.js). When uploaded via the +app loader, the module is automatically included in the app's source. However +when developing via the IDE the module won't get pulled in by default. + +There are some options to fix this easily - please check out the [modules README.md file](/modules/README.md) diff --git a/apps/run/app.js b/apps/run/app.js index 44c42711c..9e313ea2a 100644 --- a/apps/run/app.js +++ b/apps/run/app.js @@ -1,63 +1,37 @@ +var ExStats = require("exstats"); var B2 = process.env.HWVERSION==2; var Layout = require("Layout"); -var locale = require("locale") +var locale = require("locale"); var fontHeading = "6x8:2"; var fontValue = B2 ? "6x15:2" : "6x8:3"; var headingCol = "#888"; -var running = false; -var startTime; -var startSteps; -// This & previous GPS readings -var lastGPS, thisGPS; -var distance = 0; ///< distance in meters -var startSteps = Bangle.getStepCount(); ///< number of steps when we started -var lastStepCount = startSteps; // last time 'step' was called -var stepHistory = new Uint8Array(60); // steps each second for the last minute (0 = current minute) +var fixCount = 0; g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); // --------------------------- - -function formatTime(ms) { - var s = Math.round(ms/1000); - var min = Math.floor(s/60).toString(); - s = (s%60).toString(); - return min.padStart(2,0)+":"+s.padStart(2,0); -} - -// Format speed in meters/second -function formatPace(speed) { - if (speed < 0.1667) { - return `__'__"`; - } - const pace = Math.round(1000 / speed); // seconds for 1km - const min = Math.floor(pace / 60); // minutes for 1km - const sec = pace % 60; - return ('0' + min).substr(-2) + `'` + ('0' + sec).substr(-2) + `"`; -} - +let settings = Object.assign({ + B1 : "dist", + B2 : "time", + B3 : "pacea", + B4 : "bpm", + B5 : "step", + B6 : "caden", + paceLength : 1000 +}, require("Storage").readJSON("run.json", 1) || {}); +var statIDs = [settings.B1,settings.B2,settings.B3,settings.B4,settings.B5,settings.B6].filter(s=>s!=""); +var exs = ExStats.getStats(statIDs, settings); // --------------------------- -function clearState() { - distance = 0; - startSteps = Bangle.getStepCount(); - stepHistory.fill(0); - layout.dist.label=locale.distance(distance); - layout.time.label="00:00"; - layout.pace.label=formatPace(0); - layout.hrm.label="--"; - layout.steps.label=0; - layout.cadence.label= "0"; - layout.status.bgCol = "#f00"; -} - +// Called to start/stop running function onStartStop() { - running = !running; + var running = !exs.state.active; if (running) { - clearState(); - startTime = Date.now(); + exs.start(); + } else { + exs.stop(); } layout.button.label = running ? "STOP" : "START"; layout.status.label = running ? "RUN" : "STOP"; @@ -67,92 +41,44 @@ function onStartStop() { layout.render(); } +var lc = []; +// Load stats in pair by pair +for (var i=0;ilayout[e.id].label = e.getString()); + sb.on('changed', e=>layout[e.id].label = e.getString()); +} +// At the bottom put time/GPS state/etc +lc.push({ type:"h", filly:1, c:[ + {type:"txt", font:fontHeading, label:"GPS", id:"gps", fillx:1, bgCol:"#f00" }, + {type:"txt", font:fontHeading, label:"00:00", id:"clock", fillx:1, bgCol:g.theme.fg, col:g.theme.bg }, + {type:"txt", font:fontHeading, label:"STOP", id:"status", fillx:1 } +]}); +// Now calculate the layout var layout = new Layout( { - type:"v", c: [ - { type:"h", filly:1, c:[ - {type:"txt", font:fontHeading, label:"DIST", fillx:1, col:headingCol }, - {type:"txt", font:fontHeading, label:"TIME", fillx:1, col:headingCol } - ]}, { type:"h", filly:1, c:[ - {type:"txt", font:fontValue, label:"0.00", id:"dist", fillx:1 }, - {type:"txt", font:fontValue, label:"00:00", id:"time", fillx:1 } - ]}, { type:"h", filly:1, c:[ - {type:"txt", font:fontHeading, label:"PACE", fillx:1, col:headingCol }, - {type:"txt", font:fontHeading, label:"HEART", fillx:1, col:headingCol } - ]}, { type:"h", filly:1, c:[ - {type:"txt", font:fontValue, label:`__'__"`, id:"pace", fillx:1 }, - {type:"txt", font:fontValue, label:"--", id:"hrm", fillx:1 } - ]}, { type:"h", filly:1, c:[ - {type:"txt", font:fontHeading, label:"STEPS", fillx:1, col:headingCol }, - {type:"txt", font:fontHeading, label:"CADENCE", fillx:1, col:headingCol } - ]}, { type:"h", filly:1, c:[ - {type:"txt", font:fontValue, label:"0", id:"steps", fillx:1 }, - {type:"txt", font:fontValue, label:"0", id:"cadence", fillx:1 } - ]}, { type:"h", filly:1, c:[ - {type:"txt", font:fontHeading, label:"GPS", id:"gps", fillx:1, bgCol:"#f00" }, - {type:"txt", font:fontHeading, label:"00:00", id:"clock", fillx:1, bgCol:g.theme.fg, col:g.theme.bg }, - {type:"txt", font:fontHeading, label:"STOP", id:"status", fillx:1 } - ]}, - - ] + type:"v", c: lc },{lazy:true, btns:[{ label:"START", cb: onStartStop, id:"button"}]}); -clearState(); +delete lc; layout.render(); - - -function onTimer() { - layout.clock.label = locale.time(new Date(),1); - if (!running) { - layout.render(); - return; - } - // called once a second - var duration = Date.now() - startTime; // in ms - // set cadence based on steps over last minute - var stepsInMinute = E.sum(stepHistory); - var cadence = 60000 * stepsInMinute / Math.min(duration,60000); - // update layout - layout.time.label = formatTime(duration); - layout.steps.label = Bangle.getStepCount()-startSteps; - layout.cadence.label = Math.round(cadence); - layout.render(); - // move step history onwards - stepHistory.set(stepHistory,1); - stepHistory[0]=0; -} - +// Handle GPS state change for icon Bangle.on("GPS", function(fix) { layout.gps.bgCol = fix.fix ? "#0f0" : "#f00"; - lastGPS = thisGPS; - thisGPS = fix; - if (running && fix.fix && lastGPS.fix) { - // work out distance - moving from a to b - var a = Bangle.project(lastGPS); - var b = Bangle.project(thisGPS); - var dx = a.x-b.x, dy = a.y-b.y; - var d = Math.sqrt(dx*dx+dy*dy); // this should be the distance in meters - distance += d; - layout.dist.label=locale.distance(distance); - var duration = Date.now() - startTime; // in ms - var speed = distance * 1000 / duration; // meters/sec - layout.pace.label = formatPace(speed); + if (!fix.fix) return; // only process actual fixes + if (fixCount++ == 0) { + Bangle.buzz(); // first fix, does not need to respect quiet mode } }); -Bangle.on("HRM", function(h) { - layout.hrm.label = h.bpm; -}); -Bangle.on("step", function(steps) { - if (running) { - layout.steps.label = steps-Bangle.getStepCount(); - stepHistory[0] += steps-lastStepCount; - } - lastStepCount = steps; -}); - -// We always call ourselves once a second, if only to update the time -setInterval(onTimer, 1000); - -/* Turn GPS and HRM on right at the start to ensure -we get the highest chance of a lock. */ -Bangle.setHRMPower(true,"app"); -Bangle.setGPSPower(true,"app"); +// We always call ourselves once a second to update +setInterval(function() { + layout.clock.label = locale.time(new Date(),1); + layout.render(); +}, 1000); diff --git a/apps/run/metadata.json b/apps/run/metadata.json new file mode 100644 index 000000000..0d718a1b3 --- /dev/null +++ b/apps/run/metadata.json @@ -0,0 +1,16 @@ +{ "id": "run", + "name": "Run", + "version":"0.05", + "description": "Displays distance, time, steps, cadence, pace and more for runners.", + "icon": "app.png", + "tags": "run,running,fitness,outdoors,gps", + "supports" : ["BANGLEJS","BANGLEJS2"], + "screenshots": [{"url":"screenshot.png"}], + "readme": "README.md", + "storage": [ + {"name":"run.app.js","url":"app.js"}, + {"name":"run.img","url":"app-icon.js","evaluate":true}, + {"name":"run.settings.js","url":"settings.js"} + ], + "data": [{"name":"run.json"}] +} diff --git a/apps/run/settings.js b/apps/run/settings.js new file mode 100644 index 000000000..91a8b4333 --- /dev/null +++ b/apps/run/settings.js @@ -0,0 +1,50 @@ +(function(back) { + const SETTINGS_FILE = "run.json"; + var ExStats = require("exstats"); + var statsList = ExStats.getList(); + statsList.unshift({name:"-",id:""}); // add blank menu item + var statsIDs = statsList.map(s=>s.id); + + // ...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 = Object.assign({ + B1 : "dist", + B2 : "time", + B3 : "pacea", + B4 : "bpm", + B5 : "step", + B6 : "caden", + paceLength : 1000 + }, storage.readJSON(SETTINGS_FILE, 1) || {}); + function saveSettings() { + storage.write(SETTINGS_FILE, settings) + } + + function getBoxChooser(boxID) { + return { + min :0, max: statsIDs.length-1, + value: Math.max(statsIDs.indexOf(settings[boxID]),0), + format: v => statsList[v].name, + onchange: v => { + settings[boxID] = statsIDs[v]; + saveSettings(); + }, + } + } + + var menu = { + '': { 'title': 'Run' }, + '< Back': back + }; + ExStats.appendMenuItems(menu, settings, saveSettings); + Object.assign(menu,{ + 'Box 1': getBoxChooser("B1"), + 'Box 2': getBoxChooser("B2"), + 'Box 3': getBoxChooser("B3"), + 'Box 4': getBoxChooser("B4"), + 'Box 5': getBoxChooser("B5"), + 'Box 6': getBoxChooser("B6"), + }); + E.showMenu(menu); +}) diff --git a/apps/ruuviwatch/ChangeLog b/apps/ruuviwatch/ChangeLog index ebde871fa..15ff601f0 100644 --- a/apps/ruuviwatch/ChangeLog +++ b/apps/ruuviwatch/ChangeLog @@ -1,2 +1,3 @@ -1.00: Hello Ruuvi Watch! -1.01: Clear gfx on startup. \ No newline at end of file +0.01: Hello Ruuvi Watch! +0.02: Clear gfx on startup. +0.03: Improve design and code, reduce flicker. diff --git a/apps/ruuviwatch/README.md b/apps/ruuviwatch/README.md index bf4358267..c96e032d7 100644 --- a/apps/ruuviwatch/README.md +++ b/apps/ruuviwatch/README.md @@ -1,25 +1,26 @@ -# Ruuvi Watch +# Ruuvi Watch -Watch the status of [RuuviTags](https://ruuvi.com) in range. +Watch the status of [RuuviTags](https://ruuvi.com) in range. - - Id - - Temperature (°C) - - Humidity (%) - - Pressure (hPa) - - Battery voltage +![Ruuvi Watch in action](/BangleApps/apps/ruuviwatch/ruuviwatch-in-action.jpg) - Also shows how "fresh" the data is (age of reading). +- Id +- Temperature (°C) +- Humidity (%) +- Pressure (hPa) +- Battery voltage - ## Usage - - - Scans for devices when launched and every N seconds. - - Page trough devices with BTN1/BTN3. - - Trigger scan with BTN2. +Also shows how "fresh" the data is (age of reading). + +## Usage + +- Scans for devices when launched and every N seconds. +- Page trough devices with BTN1/BTN3. +- Trigger scan with BTN2. ## Todo / ideas - - Allow to "name" known devices - - Prevent flicker when updating - - Include more data - - Support older Ruuvi protocols - +- Settings for scan frequency, units +- Allow to "name" known devices +- Include more data +- Support older Ruuvi protocols diff --git a/apps/ruuviwatch/metadata.json b/apps/ruuviwatch/metadata.json new file mode 100644 index 000000000..413d96153 --- /dev/null +++ b/apps/ruuviwatch/metadata.json @@ -0,0 +1,14 @@ +{ "id": "ruuviwatch", + "name": "Ruuvi Watch", + "shortName":"Ruuvi Watch", + "icon": "ruuviwatch.png", + "version":"0.03", + "description": "Keep an eye on RuuviTag devices (https://ruuvi.com). For RuuviTags using the v5 format.", + "readme":"README.md", + "tags": "bluetooth", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"ruuviwatch.app.js","url":"ruuviwatch.app.js"}, + {"name":"ruuviwatch.img","url":"ruuviwatch.app-icon.js","evaluate":true} + ] +} diff --git a/apps/ruuviwatch/ruuviwatch-in-action.jpg b/apps/ruuviwatch/ruuviwatch-in-action.jpg new file mode 100644 index 000000000..08b391e84 Binary files /dev/null and b/apps/ruuviwatch/ruuviwatch-in-action.jpg differ diff --git a/apps/ruuviwatch/ruuviwatch.app.js b/apps/ruuviwatch/ruuviwatch.app.js index 46218a323..674319dd8 100644 --- a/apps/ruuviwatch/ruuviwatch.app.js +++ b/apps/ruuviwatch/ruuviwatch.app.js @@ -1,151 +1,252 @@ -require("Storage").write("ruuviwatch.info",{ - "id":"ruuviwatch", - "name":"Ruuvi Watch", - "src":"ruuviwatch.app.js", - "icon":"ruuviwatch.img" - }); - - const lookup = {}; - const ruuvis = []; - let current = 0; - - function int2Hex (str) { - return ('0' + str.toString(16).toUpperCase()).slice(-2); +require("Storage").write("ruuviwatch.info", { + id: "ruuviwatch", + name: "Ruuvi Watch", + src: "ruuviwatch.app.js", + icon: "ruuviwatch.img", +}); + +const lookup = {}; +const ruuvis = []; +let current = 0; +let scanning = false; + +const SCAN_FREQ = 1000 * 30; + +// Fonts +const FONT_L = "Vector:60"; +const FONT_M = "Vector:20"; +const FONT_S = "Vector:16"; + +// "layout" +const CENTER = g.getWidth() / 2; +const MIDDLE = g.getHeight() / 2; + +const PAGING_Y = 25; +const NAME_Y = PAGING_Y + 25; + +const TEMP_Y = MIDDLE; +const HUMID_PRESSURE_Y = MIDDLE + 50; + +const VOLT_Y = g.getHeight() - 15; +const SCANNING_Y = VOLT_Y - 25; + +function int2Hex(str) { + return ("0" + str.toString(16).toUpperCase()).slice(-2); +} + +function p(data) { + const OFFSET = 7; // 0-4 header, 5-6 Ruuvi id + const robject = {}; + robject.version = data[OFFSET]; + + let temperature = (data[OFFSET + 1] << 8) | (data[OFFSET + 2] & 0xff); + if (temperature > 32767) { + temperature -= 65534; } - - function p(data) { - const OFFSET = 7; // 0-4 header, 5-6 Ruuvi id - const robject = {}; - robject.version = data[OFFSET]; - - let temperature = (data[OFFSET+1] << 8) | (data[OFFSET+2] & 0xff); - if (temperature > 32767) { - temperature -= 65534; - } - robject.temperature = temperature / 200.0; - - robject.humidity = (((data[OFFSET+3] & 0xff) << 8) | (data[OFFSET+4] & 0xff)) / 400.0; - robject.pressure = ((((data[OFFSET+5] & 0xff) << 8) | (data[OFFSET+6] & 0xff)) + 50000) / 100.0; - - let accelerationX = (data[OFFSET+7] << 8) | (data[OFFSET+8] & 0xff); - if (accelerationX > 32767) accelerationX -= 65536; // two's complement - robject.accelerationX = accelerationX / 1000.0; - - let accelerationY = (data[OFFSET+9] << 8) | (data[OFFSET+10] & 0xff); - if (accelerationY > 32767) accelerationY -= 65536; // two's complement - robject.accelerationY = accelerationY / 1000.0; - - let accelerationZ = (data[OFFSET+11] << 8) | (data[OFFSET+12] & 0xff); - if (accelerationZ > 32767) accelerationZ -= 65536; // two's complement - robject.accelerationZ = accelerationZ / 1000.0; - - const powerInfo = ((data[OFFSET+13] & 0xff) << 8) | (data[OFFSET+14] & 0xff); - robject.battery = ((powerInfo >>> 5) + 1600) / 1000.0; - robject.txPower = (powerInfo & 0b11111) * 2 - 40; - robject.movementCounter = data[OFFSET+15] & 0xff; - robject.measurementSequenceNumber = ((data[OFFSET+16] & 0xff) << 8) | (data[OFFSET+17] & 0xff); - - robject.mac = [ - int2Hex(data[OFFSET+18]), - int2Hex(data[OFFSET+19]), - int2Hex(data[OFFSET+20]), - int2Hex(data[OFFSET+21]), - int2Hex(data[OFFSET+22]), - int2Hex(data[OFFSET+23]) - ].join(':'); - - robject.name = "Ruuvi " + int2Hex(data[OFFSET+22]) + int2Hex(data[OFFSET+23]); - return robject; - } - - function getAge(created) { - const now = new Date().getTime(); - const ago = ((now - created) / 1000).toFixed(0); - return ago > 0 ? ago + "s ago" : "now"; - } - - function redraw() { - if (ruuvis.length > 0 && ruuvis[current]) { - const ruuvi = ruuvis[current]; - g.clear(); - g.setFontAlign(0,0); - g.setFont("Vector",12); - g.drawString(" (" + (current+1) + "/" + ruuvis.length + ")", g.getWidth()/2, 10); - g.setFont("Vector",20); - g.drawString(ruuvi.name, g.getWidth()/2, 30); - g.setFont("Vector",12); - const age = getAge(ruuvi.time); - if(age > (5*60)) { - g.setColor("#ff0000"); - } else if (age > 60) { - g.setColor("#f39c12"); - } else { - g.setColor("#2ecc71"); - } - g.drawString(age, g.getWidth()/2, 50); - g.setColor("#ffffff"); - g.setFont("Vector",60); - g.drawString(ruuvi.temperature.toFixed(2) + "°c", g.getWidth()/2, g.getHeight()/2); - g.setFontAlign(0,1); - g.setFont("Vector",20); - g.drawString(ruuvi.humidity + "% " + ruuvi.pressure + "hPa ", g.getWidth()/2, g.getHeight()-30); - g.setFont("Vector",12); - g.drawString(ruuvi.battery + "v", g.getWidth()/2, g.getHeight()-10); + robject.temperature = temperature / 200.0; + + robject.humidity = + (((data[OFFSET + 3] & 0xff) << 8) | (data[OFFSET + 4] & 0xff)) / 400.0; + robject.pressure = + ((((data[OFFSET + 5] & 0xff) << 8) | (data[OFFSET + 6] & 0xff)) + 50000) / + 100.0; + + let accelerationX = (data[OFFSET + 7] << 8) | (data[OFFSET + 8] & 0xff); + if (accelerationX > 32767) accelerationX -= 65536; // two's complement + robject.accelerationX = accelerationX / 1000.0; + + let accelerationY = (data[OFFSET + 9] << 8) | (data[OFFSET + 10] & 0xff); + if (accelerationY > 32767) accelerationY -= 65536; // two's complement + robject.accelerationY = accelerationY / 1000.0; + + let accelerationZ = (data[OFFSET + 11] << 8) | (data[OFFSET + 12] & 0xff); + if (accelerationZ > 32767) accelerationZ -= 65536; // two's complement + robject.accelerationZ = accelerationZ / 1000.0; + + const powerInfo = + ((data[OFFSET + 13] & 0xff) << 8) | (data[OFFSET + 14] & 0xff); + robject.battery = ((powerInfo >>> 5) + 1600) / 1000.0; + robject.txPower = (powerInfo & 0b11111) * 2 - 40; + robject.movementCounter = data[OFFSET + 15] & 0xff; + robject.measurementSequenceNumber = + ((data[OFFSET + 16] & 0xff) << 8) | (data[OFFSET + 17] & 0xff); + + robject.mac = [ + int2Hex(data[OFFSET + 18]), + int2Hex(data[OFFSET + 19]), + int2Hex(data[OFFSET + 20]), + int2Hex(data[OFFSET + 21]), + int2Hex(data[OFFSET + 22]), + int2Hex(data[OFFSET + 23]), + ].join(":"); + + robject.name = + "Ruuvi " + int2Hex(data[OFFSET + 22]) + int2Hex(data[OFFSET + 23]); + return robject; +} + +function drawAge() { + const ruuvi = ruuvis[current]; + const created = ruuvi.time; + const now = new Date().getTime(); + const agoMs = now - created; + let age = ""; + if (agoMs > SCAN_FREQ) { + const agoS = agoMs / 1000; + // not seen since last scan; indicate age + if (agoS < 60) { + // seconds + age = agoS.toFixed(0) + "s ago"; + } else if (agoS < 60 * 60) { + // minutes + age = (agoS / 60).toFixed(0) + "m ago"; } else { - g.clear(); - g.drawImage(require("Storage").read("ruuviwatch.img"), g.getWidth()/2-24, g.getHeight()/2-24); - g.setFontAlign(0,0); - g.setFont("Vector",16); - g.drawString("Looking for Ruuvi...", g.getWidth()/2, g.getHeight()/2 + 50); + // hours + age = (agoS / 60 / 60).toFixed(0) + "h ago"; } } - - function scan() { - NRF.findDevices(function(devices) { - let foundNew = false; - devices.forEach(device => { - const data = p(device.data); - data.time = new Date().getTime(); - const idx = lookup[data.name]; - if (idx !== undefined) { - ruuvis[idx] = data; - } else { - lookup[data.name] = ruuvis.push(data)-1; - foundNew = true; - } - }); - redraw(); - if (foundNew) { - Bangle.buzz(); - g.flip(); - } - - }, {timeout : 2000, filters : [{ manufacturerData:{0x0499:{}} }] }); + if (agoMs > 5 * SCAN_FREQ) { + // old + g.setColor("#ff0000"); } - + g.setFont(FONT_S); + g.drawString(age, CENTER, SCANNING_Y); +} + +function redrawAge() { + const originalColor = g.getColor(); + g.clearRect(0, SCANNING_Y - 10, g.getWidth(), SCANNING_Y + 10); + g.setFont(FONT_S); + g.setColor("#666666"); + if (scanning) { + g.drawString("Scanning...", CENTER, SCANNING_Y); + } else if (ruuvis.length > 0 && ruuvis[current]) { + drawAge(); + } else { + g.drawString("No tags in sight", CENTER, SCANNING_Y); + } + g.setColor(originalColor); +} + +function redraw() { g.clear(); - g.drawImage(require("Storage").read("ruuviwatch.img"), g.getWidth()/2-24, g.getHeight()/2-24); - - var drawInterval = setInterval(redraw, 1000); - var scanInterval = setInterval(scan, 10000); - setWatch(() => { + g.setColor("#ffffff"); + + if (ruuvis.length > 0 && ruuvis[current]) { + const ruuvi = ruuvis[current]; + + // page + g.setFont(FONT_S); + g.drawString( + " (" + (current + 1) + "/" + ruuvis.length + ")", + CENTER, + PAGING_Y + ); + + // name + g.setFont(FONT_M); + g.drawString(ruuvi.name, CENTER, NAME_Y); + + // age + redrawAge(); + + // temp + g.setFont(FONT_L); + g.drawString(ruuvi.temperature.toFixed(2) + "°c", CENTER, TEMP_Y); + + // humid & pressure + g.setFont(FONT_M); + g.drawString( + ruuvi.humidity.toFixed(2) + "% " + ruuvi.pressure.toFixed(2) + "hPa ", + CENTER, + HUMID_PRESSURE_Y + ); + + // battery + g.setFont(FONT_S); + g.drawString(ruuvi.battery + "v", CENTER, VOLT_Y); + } else { + // no ruuvis + g.drawImage( + require("Storage").read("ruuviwatch.img"), + CENTER - 24, + MIDDLE - 24 + ); + } +} + +function scan() { + if (scanning) return; + scanning = true; + NRF.findDevices( + function (devices) { + let foundNew = false; + devices.forEach((device) => { + const data = p(device.data); + data.time = new Date().getTime(); + const idx = lookup[data.name]; + if (idx !== undefined) { + ruuvis[idx] = data; + } else { + lookup[data.name] = ruuvis.push(data) - 1; + foundNew = true; + } + }); + scanning = false; + redraw(); + if (foundNew) { + Bangle.buzz(); + } + }, + { timeout: 2000, filters: [{ manufacturerData: { 0x0499: {} } }] } + ); +} + +// START +// Button 1 pages up +setWatch( + () => { current--; if (current < 0) { - current = ruuvis.length-1; + current = ruuvis.length - 1; } redraw(); - }, BTN1, {repeat:true}); - - setWatch(() => { + }, + BTN1, + { repeat: true } +); +// button triggers scan +setWatch( + () => { scan(); - }, BTN2, {repeat:true}); - - setWatch(() => { + }, + BTN2, + { repeat: true } +); +// button 3 pages down +setWatch( + () => { current++; if (current >= ruuvis.length) { current = 0; } redraw(); - }, BTN3, {repeat:true}); - - scan(); \ No newline at end of file + }, + BTN3, + { repeat: true } +); + +g.setFontAlign(0, 0); +g.clear(); +g.drawImage( + require("Storage").read("ruuviwatch.img"), + CENTER - 24, + MIDDLE - 24 +); + +g.setFont(FONT_M); +g.drawString("Ruuvi Watch", CENTER, HUMID_PRESSURE_Y); + +var ageInterval = setInterval(redrawAge, 1000); +var scanInterval = setInterval(scan, SCAN_FREQ); +scan(); diff --git a/apps/s7clk/metadata.json b/apps/s7clk/metadata.json new file mode 100644 index 000000000..19aa0479d --- /dev/null +++ b/apps/s7clk/metadata.json @@ -0,0 +1,17 @@ +{ + "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} + ] +} diff --git a/apps/schoolCalendar/metadata.json b/apps/schoolCalendar/metadata.json new file mode 100644 index 000000000..e77956bb2 --- /dev/null +++ b/apps/schoolCalendar/metadata.json @@ -0,0 +1,19 @@ +{ "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"} + ] +} diff --git a/apps/sclock/metadata.json b/apps/sclock/metadata.json new file mode 100644 index 000000000..97f5baf3d --- /dev/null +++ b/apps/sclock/metadata.json @@ -0,0 +1,16 @@ +{ + "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} + ] +} diff --git a/apps/scolor/metadata.json b/apps/scolor/metadata.json new file mode 100644 index 000000000..c171aead5 --- /dev/null +++ b/apps/scolor/metadata.json @@ -0,0 +1,16 @@ +{ + "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} + ] +} diff --git a/apps/score/metadata.json b/apps/score/metadata.json new file mode 100644 index 000000000..fd72e197d --- /dev/null +++ b/apps/score/metadata.json @@ -0,0 +1,18 @@ +{ + "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"}] +} diff --git a/apps/scribble/metadata.json b/apps/scribble/metadata.json new file mode 100644 index 000000000..eaec185ee --- /dev/null +++ b/apps/scribble/metadata.json @@ -0,0 +1,19 @@ +{ "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" } + ] +} diff --git a/apps/sensible/ChangeLog b/apps/sensible/ChangeLog index 33e44b70c..0699954d7 100644 --- a/apps/sensible/ChangeLog +++ b/apps/sensible/ChangeLog @@ -2,4 +2,5 @@ 0.02: Corrected variable initialisation 0.03: Advertise app name, added screenshots 0.04: Advertise bar, GPS, HRM and mag services -0.05: Refactored for efficiency, corrected sensor value inaccuracies \ No newline at end of file +0.05: Refactored for efficiency, corrected sensor value inaccuracies +0.06: User settings are written to persistent storage, loaded on app start \ No newline at end of file diff --git a/apps/sensible/README.md b/apps/sensible/README.md index fcff3b0f9..8bf21ae8a 100644 --- a/apps/sensible/README.md +++ b/apps/sensible/README.md @@ -5,7 +5,9 @@ Collect all the sensor data from the Bangle.js 2, display the live readings in m ## Usage -The advertising packets will be recognised by [Pareto Anywhere](https://www.reelyactive.com/pareto/anywhere/) open source middleware and any other program which observes the standard packet types. Also convenient for testing individual sensors of the Bangle.js 2 via the menu interface. +The advertising packets will be recognised by [Pareto Anywhere](https://www.reelyactive.com/pareto/anywhere/) open source middleware and any other program which observes the standard packet types. See our [Bangle.js Development Guide](https://reelyactive.github.io/diy/banglejs-dev/) for details. Also convenient for testing individual sensors of the Bangle.js 2 via the menu interface. + +![SensiBLE in Pareto Anywhere](/BangleApps/apps/sensible/screenshot-pareto-anywhere.png) ## Features @@ -22,7 +24,7 @@ in the menu display, and broadcasts all sensor data readings _except_ accelerati ## Controls -Browse and control sensors using the standard Espruino menu interface. +Browse and control sensors using the standard Espruino menu interface. By default, all sensors _except_ the accelerometer are disabled. Sensors can be individually enabled/disabled via the menu. These settings are written to persistent storage (flash) and will be applied each time the SensiBLE app is loaded. ## Requests diff --git a/apps/sensible/metadata.json b/apps/sensible/metadata.json new file mode 100644 index 000000000..6715e2538 --- /dev/null +++ b/apps/sensible/metadata.json @@ -0,0 +1,28 @@ +{ + "id": "sensible", + "name": "SensiBLE", + "shortName": "SensiBLE", + "version": "0.06", + "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,bluetooth", + "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 } + ], + "data": [ + { "name": "sensible.data.json", "url": "settings.json", "storageFile": true } + ] +} diff --git a/apps/sensible/screenshot-pareto-anywhere.png b/apps/sensible/screenshot-pareto-anywhere.png new file mode 100644 index 000000000..c411bca8a Binary files /dev/null and b/apps/sensible/screenshot-pareto-anywhere.png differ diff --git a/apps/sensible/sensible.js b/apps/sensible/sensible.js index 73c348556..8ec7d93d4 100644 --- a/apps/sensible/sensible.js +++ b/apps/sensible/sensible.js @@ -1,5 +1,5 @@ /** - * Copyright reelyActive 2021 + * Copyright reelyActive 2021-2022 * We believe in an open Internet of Things */ @@ -7,6 +7,8 @@ // Non-user-configurable constants const APP_ID = 'sensible'; const ESPRUINO_COMPANY_CODE = 0x0590; +const SETTINGS_FILENAME = 'sensible.data.json'; +const UPDATE_MILLISECONDS = 1000; const APP_ADVERTISING_DATA = [ 0x12, 0xff, 0x90, 0x05, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x7d ]; @@ -19,16 +21,12 @@ let isBarMenu = false; let isGpsMenu = false; let isHrmMenu = false; let isMagMenu = false; -let isBarEnabled = true; -let isGpsEnabled = true; -let isHrmEnabled = true; -let isMagEnabled = true; let isNewAccData = false; let isNewBarData = false; let isNewGpsData = false; let isNewHrmData = false; let isNewMagData = false; - +let settings = require('Storage').readJSON(SETTINGS_FILENAME); // Menus @@ -51,9 +49,9 @@ let accMenu = { let barMenu = { "": { "title" : "- Barometer -" }, "State": { - value: isBarEnabled, + value: settings.isBarEnabled, format: v => v ? "On" : "Off", - onchange: v => { isBarEnabled = v; Bangle.setBarometerPower(v, APP_ID); } + onchange: v => { updateSetting('isBarEnabled', v); } }, "Altitude": { value: null }, "Press": { value: null }, @@ -63,9 +61,9 @@ let barMenu = { let gpsMenu = { "": { "title" : "- GPS -" }, "State": { - value: isGpsEnabled, + value: settings.isGpsEnabled, format: v => v ? "On" : "Off", - onchange: v => { isGpsEnabled = v; Bangle.setGPSPower(v, APP_ID); } + onchange: v => { updateSetting('isGpsEnabled', v); } }, "Lat": { value: null }, "Lon": { value: null }, @@ -77,9 +75,9 @@ let gpsMenu = { let hrmMenu = { "": { "title" : "- Heart Rate -" }, "State": { - value: isHrmEnabled, + value: settings.isHrmEnabled, format: v => v ? "On" : "Off", - onchange: v => { isHrmEnabled = v; Bangle.setHRMPower(v, APP_ID); } + onchange: v => { updateSetting('isHrmEnabled', v); } }, "BPM": { value: null }, "Confidence": { value: null }, @@ -88,9 +86,9 @@ let hrmMenu = { let magMenu = { "": { "title" : "- Magnetometer -" }, "State": { - value: isMagEnabled, + value: settings.isMagEnabled, format: v => v ? "On" : "Off", - onchange: v => { isMagEnabled = v; Bangle.setCompassPower(v, APP_ID); } + onchange: v => { updateSetting('isMagEnabled', v); } }, "x": { value: null }, "y": { value: null }, @@ -124,7 +122,7 @@ function transmitUpdatedSensorData() { isNewMagData = false; } - let interval = 1000 / data.length; + let interval = UPDATE_MILLISECONDS / data.length; NRF.setAdvertising(data, { showName: false, interval: interval }); } @@ -190,6 +188,23 @@ function toByteArray(value, numberOfBytes, isSigned) { } +// Enable the sensors as per the current settings +function enableSensors() { + Bangle.setBarometerPower(settings.isBarEnabled, APP_ID); + Bangle.setGPSPower(settings.isGpsEnabled, APP_ID); + Bangle.setHRMPower(settings.isHrmEnabled, APP_ID); + Bangle.setCompassPower(settings.isMagEnabled, APP_ID); +} + + +// Update the given setting and write to persistent storage +function updateSetting(name, value) { + settings[name] = value; + require('Storage').writeJSON(SETTINGS_FILENAME, settings); + enableSensors(); +} + + // Update acceleration Bangle.on('accel', function(newAcc) { acc = newAcc; @@ -260,9 +275,6 @@ Bangle.on('mag', function(newMag) { // On start: enable sensors and display main menu g.clear(); -Bangle.setBarometerPower(isBarEnabled, APP_ID); -Bangle.setGPSPower(isGpsEnabled, APP_ID); -Bangle.setHRMPower(isHrmEnabled, APP_ID); -Bangle.setCompassPower(isMagEnabled, APP_ID); +enableSensors(); E.showMenu(mainMenu); -setInterval(transmitUpdatedSensorData, 1000); \ No newline at end of file +setInterval(transmitUpdatedSensorData, UPDATE_MILLISECONDS); \ No newline at end of file diff --git a/apps/sensible/settings.json b/apps/sensible/settings.json new file mode 100644 index 000000000..90340a201 --- /dev/null +++ b/apps/sensible/settings.json @@ -0,0 +1,6 @@ +{ + "isBarEnabled": false, + "isGpsEnabled": false, + "isHrmEnabled": false, + "isMagEnabled": false +} \ No newline at end of file diff --git a/apps/setting/metadata.json b/apps/setting/metadata.json new file mode 100644 index 000000000..1e82f97b4 --- /dev/null +++ b/apps/setting/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "setting", + "name": "Settings", + "version": "0.41", + "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 +} diff --git a/apps/setting/settings.js b/apps/setting/settings.js index a32b83d3c..65e076753 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -221,7 +221,7 @@ function showThemeMenu() { m.draw(); } var m = E.showMenu({ - '':{title:'Theme'}, + '':{title:/*LANG*/'Theme'}, '< Back': ()=>showSystemMenu(), /*LANG*/'Dark BW': ()=>{ upd({ diff --git a/apps/shortcuts/metadata.json b/apps/shortcuts/metadata.json new file mode 100644 index 000000000..2351a102f --- /dev/null +++ b/apps/shortcuts/metadata.json @@ -0,0 +1,17 @@ +{ + "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"}] +} diff --git a/apps/showimg/ChangeLog b/apps/showimg/ChangeLog index 296bc78d0..e8d890f83 100644 --- a/apps/showimg/ChangeLog +++ b/apps/showimg/ChangeLog @@ -1,2 +1,2 @@ -0.1: Initial release -0.2: Fixed launcher image +0.01: Initial release +0.02: Fixed launcher image diff --git a/apps/showimg/metadata.json b/apps/showimg/metadata.json new file mode 100644 index 000000000..d5e44c0ee --- /dev/null +++ b/apps/showimg/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "showimg", + "name": "simple image viewer", + "shortName":"showImage", + "version":"0.02", + "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} + ] +} diff --git a/apps/simple_clock/metadata.json b/apps/simple_clock/metadata.json new file mode 100644 index 000000000..ccec0bfbc --- /dev/null +++ b/apps/simple_clock/metadata.json @@ -0,0 +1,17 @@ +{ "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} + ] +} diff --git a/apps/simplest/ChangeLog b/apps/simplest/ChangeLog index e7ab5f2c3..a09bc47b9 100644 --- a/apps/simplest/ChangeLog +++ b/apps/simplest/ChangeLog @@ -4,3 +4,4 @@ 0.04: Use queueDraw(), update every minute, respect theme, use Lato font 0.05: Decided against custom font as it inceases the code size minimalism is useful when narrowing down issues +0.06: renamed some files diff --git a/apps/simplest/metadata.json b/apps/simplest/metadata.json new file mode 100644 index 000000000..d938aab9f --- /dev/null +++ b/apps/simplest/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "simplest", + "name": "Simplest Clock", + "version": "0.06", + "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":"simplest.app.js"}, + {"name":"simplest.img","url":"simplest.icon.js","evaluate":true} + ] +} diff --git a/apps/simplest/app.js b/apps/simplest/simplest.app.js similarity index 97% rename from apps/simplest/app.js rename to apps/simplest/simplest.app.js index 582c4c2d5..4038212d0 100644 --- a/apps/simplest/app.js +++ b/apps/simplest/simplest.app.js @@ -13,7 +13,6 @@ function draw() { g.setFontAlign(0, 0); g.setColor(g.theme.fg); g.drawString(timeStr, w/2, h/2); - queueDraw(); } @@ -42,13 +41,12 @@ Bangle.on('lcdPower',on=>{ g.clear(); // Show launcher when middle button pressed -//Bangle.setUI("clock"); +// Bangle.setUI("clock"); // use clockupdown as it tests for issue #1249 Bangle.setUI("clockupdown", btn=> { draw(); }); - // Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); diff --git a/apps/simplest/icon.js b/apps/simplest/simplest.icon.js similarity index 100% rename from apps/simplest/icon.js rename to apps/simplest/simplest.icon.js diff --git a/apps/simpletimer/metadata.json b/apps/simpletimer/metadata.json new file mode 100644 index 000000000..d319da39e --- /dev/null +++ b/apps/simpletimer/metadata.json @@ -0,0 +1,19 @@ +{ + "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"}] +} diff --git a/apps/slash/metadata.json b/apps/slash/metadata.json new file mode 100644 index 000000000..6bdb4cd53 --- /dev/null +++ b/apps/slash/metadata.json @@ -0,0 +1,18 @@ +{ + "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} + ] +} diff --git a/apps/sleepphasealarm/ChangeLog b/apps/sleepphasealarm/ChangeLog index 0f0e4a20b..b78459a11 100644 --- a/apps/sleepphasealarm/ChangeLog +++ b/apps/sleepphasealarm/ChangeLog @@ -1,4 +1,4 @@ 0.01: New App! 0.02: Respect Quiet Mode -0.03: available for BangleJS2 - added support for qalarms \ No newline at end of file +0.03: Add compatibility for Bangle.js 2 and new firmware, added "Alarm at " for the alarm time +0.04: added support for qalarms diff --git a/apps/sleepphasealarm/app.js b/apps/sleepphasealarm/app.js index ed5077b02..c584d453e 100644 --- a/apps/sleepphasealarm/app.js +++ b/apps/sleepphasealarm/app.js @@ -1,4 +1,5 @@ const alarms = require("Storage").readJSON("alarm.json",1)||require("Storage").readJSON("qalarm.json",1)||[]; +const BANGLEJS2 = process.env.HWVERSION == 2; //# check for bangle 2 const active = alarms.filter(a=>a.on); // Sleep/Wake detection with Estimation of Stationary Sleep-segments (ESS): @@ -52,21 +53,21 @@ active.forEach(alarm => { } }); -function drawString(s, x, y) { - g.clearRect(0,y-15,239,y+15); - g.reset(); - g.setFont("Vector",20); - g.setFontAlign(0,0); // align right bottom - g.drawString(s, x, y); +function drawString(s, y) { //# replaced x: always centered + g.reset(); //# moved up to prevent blue background + g.clearRect(0, y - 12, 239, y + 8); //# minimized upper+lower clearing + g.setFont("Vector", 20); + g.setFontAlign(0, 0); // align centered + g.drawString(s, g.getWidth() / 2, y); //# set x to center } function drawApp() { - g.clearRect(0,24,239,215); + g.clearRect(0,24,239,215); //# no problem var alarmHour = nextAlarm.getHours(); var alarmMinute = nextAlarm.getMinutes(); if (alarmHour < 10) alarmHour = "0" + alarmHour; if (alarmMinute < 10) alarmMinute = "0" + alarmMinute; - const s = alarmHour + ":" + alarmMinute + "\n\n"; + const s = "Alarm at " + alarmHour + ":" + alarmMinute + "\n\n"; //# make distinct to time E.showMessage(s, "Sleep Phase Alarm"); function drawTime() { @@ -78,12 +79,20 @@ function drawApp() { if (nowHour < 10) nowHour = "0" + nowHour; if (nowMinute < 10) nowMinute = "0" + nowMinute; if (nowSecond < 10) nowSecond = "0" + nowSecond; - const time = nowHour + ":" + nowMinute + ":" + nowSecond; - drawString(time, 120, 140); + const time = nowHour + ":" + nowMinute + (BANGLEJS2 ? "" : ":" + nowSecond); //# hide seconds on bangle 2 + drawString(time, BANGLEJS2 ? 85 : 105); //# remove x, adjust height for bangle 2 an newer firmware } } - setInterval(drawTime, 500); // 2Hz + if (BANGLEJS2) { + drawTime(); + setTimeout(_ => { + drawTime(); + setInterval(drawTime, 60000); + }, 60000 - Date.now() % 60000); //# every new minute on bangle 2 + } else { + setInterval(drawTime, 500); // 2Hz + } } var buzzCount = 19; @@ -104,8 +113,8 @@ function buzz() { var minAlarm = new Date(); var measure = true; if (nextAlarm !== undefined) { + Bangle.loadWidgets(); //# correct widget load draw order Bangle.drawWidgets(); - Bangle.loadWidgets(); // minimum alert 30 minutes early minAlarm.setTime(nextAlarm.getTime() - (30*60*1000)); @@ -116,7 +125,7 @@ if (nextAlarm !== undefined) { if (swest !== undefined) { if (Bangle.isLCDOn()) { - drawString(swest ? "Sleep" : "Awake", 120, 180); + drawString(swest ? "Sleep" : "Awake", BANGLEJS2 ? 150 : 180); //# remove x, adjust height } } @@ -133,6 +142,6 @@ if (nextAlarm !== undefined) { E.showMessage('No Alarm'); setTimeout(load, 1000); } -// BTN2 to menu, BTN3 to main -setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); -setWatch(() => load(), BTN3, { repeat: false, edge: "falling" }); +// BTN2 to menu, BTN3 to main # on bangle 2 only BTN to main +if (!BANGLEJS2) setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); +setWatch(() => load(), BANGLEJS2 ? BTN : BTN3, { repeat: false, edge: "falling" }); diff --git a/apps/sleepphasealarm/metadata.json b/apps/sleepphasealarm/metadata.json new file mode 100644 index 000000000..ed0f21028 --- /dev/null +++ b/apps/sleepphasealarm/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "sleepphasealarm", + "name": "SleepPhaseAlarm", + "shortName": "SleepPhaseAlarm", + "version": "0.03", + "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","BANGLEJS2"], + "storage": [ + {"name":"sleepphasealarm.app.js","url":"app.js"}, + {"name":"sleepphasealarm.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/slevel/ChangeLog b/apps/slevel/ChangeLog index 3a6431e50..e77ca4e8b 100644 --- a/apps/slevel/ChangeLog +++ b/apps/slevel/ChangeLog @@ -1,2 +1,4 @@ 0.01: New App! 0.02: Updated to work with both Bangle.js 1 and 2. +0.03: Now also visible on Bangle.js 2 +0.04: Now work with different themes diff --git a/apps/slevel/metadata.json b/apps/slevel/metadata.json new file mode 100644 index 000000000..2a8223498 --- /dev/null +++ b/apps/slevel/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "slevel", + "name": "Spirit Level", + "version": "0.04", + "description": "Show the current angle of the watch, so you can use it to make sure something is absolutely flat", + "icon": "spiritlevel.png", + "screenshots": [{"url":"screenshot.png"}], + "tags": "tool", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"slevel.app.js","url":"spiritlevel.js"}, + {"name":"slevel.img","url":"spiritlevel-icon.js","evaluate":true} + ] +} diff --git a/apps/slevel/screenshot.png b/apps/slevel/screenshot.png new file mode 100644 index 000000000..e945414ac Binary files /dev/null and b/apps/slevel/screenshot.png differ diff --git a/apps/slevel/spiritlevel.js b/apps/slevel/spiritlevel.js index 9db54b825..dd6d56bdc 100644 --- a/apps/slevel/spiritlevel.js +++ b/apps/slevel/spiritlevel.js @@ -2,6 +2,7 @@ g.clear(); var old = {x:0,y:0}; var W = g.getWidth(); var H = g.getHeight(); + Bangle.on('accel',function(v) { var max = Math.max(Math.abs(v.x),Math.abs(v.y),Math.abs(v.z)); if (Math.abs(v.y)==max) { @@ -12,21 +13,26 @@ Bangle.on('accel',function(v) { var d = Math.sqrt(v.x*v.x+v.y*v.y); var ang = Math.atan2(d,Math.abs(v.z))*180/Math.PI; - - g.setColor(1,1,1); - g.setFont("6x8",2); - g.setFontAlign(0,-1); - g.clearRect(W*(1/4),0,W*(3/4),H*(1/16)); - g.drawString(ang.toFixed(1),W/2,0); + + g.reset(); + g.clearRect(W*(1/4),0,W*(3/4),16);// clear behind text + g.setFont("6x8",2).setFontAlign(0,-1).drawString(ang.toFixed(1),W/2,0); var n = { x:E.clip(W/2+v.x*256,4,W-4), y:E.clip(H/2+v.y*256,4,H-4)}; - g.clearRect(old.x-3,old.y-3,old.x+6,old.y+6); - g.setColor(1,1,1); - g.fillRect(n.x-3,n.y-3,n.x+6,n.y+6); - g.setColor(1,0,0); + g.clearRect(old.x-3,old.y-3,old.x+6,old.y+6); // clear old marker + g.setColor("#0f0"); + g.fillRect(n.x-3,n.y-3,n.x+6,n.y+6); // draw new marker + // draw rings + g.setColor("#f00"); g.drawCircle(W/2,H/2,W*(1/12)); g.drawCircle(W/2,H/2,W*(1/4)); g.drawCircle(W/2,H/2,W*(5/12)); old = n; }); + +setWatch(_=>load(), BTN1); +if (global.BTN2) { + setWatch(_=>load(), BTN2); + setWatch(_=>load(), BTN3); +} diff --git a/apps/slidingtext/metadata.json b/apps/slidingtext/metadata.json new file mode 100644 index 000000000..0f380ec8f --- /dev/null +++ b/apps/slidingtext/metadata.json @@ -0,0 +1,25 @@ +{ + "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"} + ] +} diff --git a/apps/slimehunt/metadata.json b/apps/slimehunt/metadata.json new file mode 100644 index 000000000..2a3509a01 --- /dev/null +++ b/apps/slimehunt/metadata.json @@ -0,0 +1,14 @@ +{ "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} +] +} diff --git a/apps/slomoclock/metadata.json b/apps/slomoclock/metadata.json new file mode 100644 index 000000000..5afcbf87c --- /dev/null +++ b/apps/slomoclock/metadata.json @@ -0,0 +1,20 @@ +{ + "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"}] +} diff --git a/apps/smartibot/metadata.json b/apps/smartibot/metadata.json new file mode 100644 index 000000000..612a6f904 --- /dev/null +++ b/apps/smartibot/metadata.json @@ -0,0 +1,14 @@ +{ + "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} + ] +} diff --git a/apps/smtswch/metadata.json b/apps/smtswch/metadata.json new file mode 100644 index 000000000..14c768a5d --- /dev/null +++ b/apps/smtswch/metadata.json @@ -0,0 +1,20 @@ +{ + "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} + ] +} diff --git a/apps/snake/metadata.json b/apps/snake/metadata.json new file mode 100644 index 000000000..c8a92c39b --- /dev/null +++ b/apps/snake/metadata.json @@ -0,0 +1,15 @@ +{ + "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} + ] +} diff --git a/apps/snaky/metadata.json b/apps/snaky/metadata.json new file mode 100644 index 000000000..62baa8608 --- /dev/null +++ b/apps/snaky/metadata.json @@ -0,0 +1,14 @@ +{ "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} + ] +} diff --git a/apps/snek/metadata.json b/apps/snek/metadata.json new file mode 100644 index 000000000..d8d3568f2 --- /dev/null +++ b/apps/snek/metadata.json @@ -0,0 +1,14 @@ +{ "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} + ] +} diff --git a/apps/solarclock/metadata.json b/apps/solarclock/metadata.json new file mode 100644 index 000000000..428b4c486 --- /dev/null +++ b/apps/solarclock/metadata.json @@ -0,0 +1,29 @@ +{ + "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"} + ] +} diff --git a/apps/sonicclk/Changelog b/apps/sonicclk/Changelog index 7c83f6988..d78fe291f 100644 --- a/apps/sonicclk/Changelog +++ b/apps/sonicclk/Changelog @@ -1,2 +1,5 @@ -1.00 Added sonic clock app -1.01 Fixed text alignment issue; Increased acceleration required to activate twist; \ No newline at end of file +0.01 [MAJOR] Added sonic clock app +0.02 [PATCH] Fixed text alignment issue; Increased acceleration required to activate twist; +0.03 [MINOR] Added settings menu to control twist threshold and LCD Activity +0.04 [PATCH] Call `Bangle.setUI` when exiting settings menu, settings tap moved to top +0.05 [PATCH] Firmware 2v11 - use `wakeOnTwist` rather than manual `setLCDPower`; Reset sonic on `fullReset` diff --git a/apps/sonicclk/README.md b/apps/sonicclk/README.md index a381e0a07..54de16115 100644 --- a/apps/sonicclk/README.md +++ b/apps/sonicclk/README.md @@ -8,6 +8,13 @@ A classic sonic clock featuring run, stop and wait animations. - Sonic will run when the screen is unlocked - Sonic will stop when the screen is locked -- Sonic will wait when looking at your watch face (when `Bangle.on("twist", fn)` is fired). +- Sonic will wait when looking at your watch face (when `Bangle.on("twist", fn)` is fired). This option is configurable (see below). + +## Configuration + +To access the settings menu, you can **double tap** on the **top** side of the watch. The following options are configurable: + +- `Active Mode` - catering for 'active' behaviour where the `twist` method can be fired undesirably. When `on` this will prevent the LCD from turning on when a `twist` event is fired. +- `Twist Thresh` - customise the acceleration needed to activate the twist method (see the [`Bangle.setOptions`](https://www.espruino.com/Reference#:~:text=twisted%3F%20default%20%3D%20true-,twistThreshold,-How%20much%20acceleration) method for more info). ### Made with love by [Joseph](https://github.com/Johoseph) 🤗 diff --git a/apps/sonicclk/app.js b/apps/sonicclk/app.js index 296677281..eddb971f8 100644 --- a/apps/sonicclk/app.js +++ b/apps/sonicclk/app.js @@ -109,10 +109,16 @@ let currentSonic = -1; let drawTimeout, drawInterval, waitTimeout; let bgScroll = [0, null]; -const start = () => { +const fullReset = () => { if (drawTimeout) clearTimeout(drawTimeout); if (waitTimeout) clearTimeout(waitTimeout); if (drawInterval) clearInterval(drawInterval); + currentSonic = -1; + currentSpeed = 0; +}; + +const start = () => { + fullReset(); drawInterval = setInterval(() => { draw("start"); @@ -144,7 +150,6 @@ const wait = () => { currentSpeed = 0; if (drawTimeout) clearTimeout(drawTimeout); if (drawInterval) clearInterval(drawInterval); - Bangle.setLCDPower(1); drawInterval = setInterval(() => draw("wait"), timeout); @@ -252,22 +257,80 @@ const draw = (action) => { if (action === "reset") queueDraw(); }; +// Settings +const settings = require("Storage").readJSON("sonicclk-settings") || { + activeMode: false, + twistThreshold: 1600, +}; +let isSettings = false; + +const settingsMenu = { + "": { title: "Settings" }, + "Active Mode": { + value: settings.activeMode, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => (settings.activeMode = v), + }, + "Twist Thresh": { + value: settings.twistThreshold, + min: 800, + max: 4000, + step: 200, + onchange: (v) => (settings.twistThreshold = v), + }, + Exit: () => { + isSettings = false; + + require("Storage").writeJSON("sonicclk-settings", settings); + Bangle.setOptions({ + lockTimeout: 10000, + backlightTimeout: 12000, + twistThreshold: settings.twistThreshold, + wakeOnTwist: !settings.activeMode, + }); + + E.showMenu(); + Bangle.setUI("clock"); + draw("reset"); + start(); + }, +}; + g.setTheme({ bg: "#0099ff", fg: "#fff", dark: true }).clear(); Bangle.on("lock", (locked) => { - if (locked) { - stop(); - } else { - start(); + if (!isSettings) { + if (locked) { + stop(); + } else { + start(); + } } }); -Bangle.on("twist", () => wait()); +Bangle.on("twist", () => { + if (settings.activeMode) { + fullReset(); + draw("reset"); + } else { + wait(); + } +}); + +Bangle.on("tap", (d) => { + if (d.double && d.dir === "top") { + fullReset(); + isSettings = true; + Bangle.setLocked(false); + E.showMenu(settingsMenu); + } +}); Bangle.setOptions({ lockTimeout: 10000, backlightTimeout: 12000, - twistThreshold: 1600, + twistThreshold: settings.twistThreshold, + wakeOnTwist: !settings.activeMode, }); Bangle.setUI("clock"); diff --git a/apps/sonicclk/metadata.json b/apps/sonicclk/metadata.json new file mode 100644 index 000000000..5a2d64db1 --- /dev/null +++ b/apps/sonicclk/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "sonicclk", + "name": "Sonic Clock", + "version": "0.05", + "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} + ] +} diff --git a/apps/speedalt/ChangeLog b/apps/speedalt/ChangeLog index de5c9c221..09d33f615 100644 --- a/apps/speedalt/ChangeLog +++ b/apps/speedalt/ChangeLog @@ -5,6 +5,6 @@ 0.05: Add setting to turn vibrate on/off. 0.06: Tweaks to vibration settings. 0.07: Switch to BTN1 for Max toggle and reset function. -1.00: New features. Added waypoints file and distance to selected waypoint display. Added integration with GPS Setup module to switch GPS to low power mode when screen off. Save display settings and restore when app restarted. -1.01: Add third screen mode with large clock and waypoint selection display to ease visibility in bright daylight. -1.02: Add Kalman filter to smooth the speed and altitude values. Can be disabled in settings. +0.08: New features. Added waypoints file and distance to selected waypoint display. Added integration with GPS Setup module to switch GPS to low power mode when screen off. Save display settings and restore when app restarted. +0.09: Add third screen mode with large clock and waypoint selection display to ease visibility in bright daylight. +0.10: Add Kalman filter to smooth the speed and altitude values. Can be disabled in settings. diff --git a/apps/speedalt/metadata.json b/apps/speedalt/metadata.json new file mode 100644 index 000000000..458023278 --- /dev/null +++ b/apps/speedalt/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "speedalt", + "name": "GPS Adventure Sports", + "shortName": "GPS Adv Sport", + "version": "0.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":"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"}] +} diff --git a/apps/speedalt2/ChangeLog b/apps/speedalt2/ChangeLog index fa2e32f5b..c58795e3b 100644 --- a/apps/speedalt2/ChangeLog +++ b/apps/speedalt2/ChangeLog @@ -1,4 +1,12 @@ 0.01: Initial import. -0.07: Add swipe to change screens. +0.02: Misc development. +0.03: Enable screen off. +0.04: Vibrate once on no fix, twice on fix. +0.05: Add setting to turn vibrate on/off. +0.06: Tweaks to vibration settings. +0.07: Switch to BTN1 for Max toggle and reset function. +0.08: New features. Added waypoints file and distance to selected waypoint display. Added integration with GPS Setup module to switch GPS to low power mode when screen off. Save display settings and restore when app restarted. +0.09: Add third screen mode with large clock and waypoint selection display to ease visibility in bright daylight. +0.10: Add Kalman filter to smooth the speed and altitude values. Can be disabled in settings. 1.06: Misc memory and screen optimisations. -1.10: ... +1.10: Adds Kalman filter. diff --git a/apps/speedalt2/metadata.json b/apps/speedalt2/metadata.json new file mode 100644 index 000000000..f05611338 --- /dev/null +++ b/apps/speedalt2/metadata.json @@ -0,0 +1,19 @@ +{ + "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"}] +} diff --git a/apps/speedo/metadata.json b/apps/speedo/metadata.json new file mode 100644 index 000000000..f081e73da --- /dev/null +++ b/apps/speedo/metadata.json @@ -0,0 +1,13 @@ +{ + "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} + ] +} diff --git a/apps/stepo/metadata.json b/apps/stepo/metadata.json new file mode 100644 index 000000000..6706b7eff --- /dev/null +++ b/apps/stepo/metadata.json @@ -0,0 +1,15 @@ +{ + "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} + ] +} diff --git a/apps/stetho/metadata.json b/apps/stetho/metadata.json new file mode 100644 index 000000000..6328f49de --- /dev/null +++ b/apps/stetho/metadata.json @@ -0,0 +1,13 @@ +{ + "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} + ] +} diff --git a/apps/stopwatch/metadata.json b/apps/stopwatch/metadata.json new file mode 100644 index 000000000..e72d85af1 --- /dev/null +++ b/apps/stopwatch/metadata.json @@ -0,0 +1,15 @@ +{ + "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} + ] +} diff --git a/apps/supf/metadata.json b/apps/supf/metadata.json new file mode 100644 index 000000000..324e37b84 --- /dev/null +++ b/apps/supf/metadata.json @@ -0,0 +1,18 @@ +{ + "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} + ] +} diff --git a/apps/supmariodark/metadata.json b/apps/supmariodark/metadata.json new file mode 100644 index 000000000..b56b19735 --- /dev/null +++ b/apps/supmariodark/metadata.json @@ -0,0 +1,25 @@ +{ + "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} + ] +} diff --git a/apps/svclock/metadata.json b/apps/svclock/metadata.json new file mode 100644 index 000000000..aa213f168 --- /dev/null +++ b/apps/svclock/metadata.json @@ -0,0 +1,16 @@ +{ + "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} + ] +} diff --git a/apps/swatch/metadata.json b/apps/swatch/metadata.json new file mode 100644 index 000000000..6c76d3fa2 --- /dev/null +++ b/apps/swatch/metadata.json @@ -0,0 +1,17 @@ +{ + "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} + ] +} diff --git a/apps/sweepclock/metadata.json b/apps/sweepclock/metadata.json new file mode 100644 index 000000000..179d5d95e --- /dev/null +++ b/apps/sweepclock/metadata.json @@ -0,0 +1,17 @@ +{ + "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} + ] +} diff --git a/apps/swiperclocklaunch/metadata.json b/apps/swiperclocklaunch/metadata.json new file mode 100644 index 000000000..733aaa032 --- /dev/null +++ b/apps/swiperclocklaunch/metadata.json @@ -0,0 +1,14 @@ +{ + "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} + ] +} diff --git a/apps/swlclk/metadata.json b/apps/swlclk/metadata.json new file mode 100644 index 000000000..2c0d39c5a --- /dev/null +++ b/apps/swlclk/metadata.json @@ -0,0 +1,18 @@ +{ + "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} + ] +} diff --git a/apps/swp2clk/metadata.json b/apps/swp2clk/metadata.json new file mode 100644 index 000000000..aa95a6473 --- /dev/null +++ b/apps/swp2clk/metadata.json @@ -0,0 +1,17 @@ +{ + "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"}] +} diff --git a/apps/tabata/metadata.json b/apps/tabata/metadata.json new file mode 100644 index 000000000..14429090f --- /dev/null +++ b/apps/tabata/metadata.json @@ -0,0 +1,14 @@ +{ + "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} + ] +} diff --git a/apps/tapelauncher/metadata.json b/apps/tapelauncher/metadata.json new file mode 100644 index 000000000..fd5051749 --- /dev/null +++ b/apps/tapelauncher/metadata.json @@ -0,0 +1,15 @@ +{ + "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} + ] +} diff --git a/apps/teatimer/metadata.json b/apps/teatimer/metadata.json new file mode 100644 index 000000000..acace0402 --- /dev/null +++ b/apps/teatimer/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "teatimer", + "name": "Tea Timer", + "version": "0.01", + "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"} + ] +} diff --git a/apps/testuserinput/metadata.json b/apps/testuserinput/metadata.json new file mode 100644 index 000000000..e279ae509 --- /dev/null +++ b/apps/testuserinput/metadata.json @@ -0,0 +1,15 @@ +{ + "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} + ] +} diff --git a/apps/themesetter/metadata.json b/apps/themesetter/metadata.json new file mode 100644 index 000000000..f6a319ec0 --- /dev/null +++ b/apps/themesetter/metadata.json @@ -0,0 +1,17 @@ +{ "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} + ] +} diff --git a/apps/thermom/metadata.json b/apps/thermom/metadata.json new file mode 100644 index 000000000..381f85e17 --- /dev/null +++ b/apps/thermom/metadata.json @@ -0,0 +1,15 @@ +{ + "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} + ] +} diff --git a/apps/thermomF/metadata.json b/apps/thermomF/metadata.json new file mode 100644 index 000000000..ff821d780 --- /dev/null +++ b/apps/thermomF/metadata.json @@ -0,0 +1,13 @@ +{ + "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} + ] +} diff --git a/apps/tilthydro/metadata.json b/apps/tilthydro/metadata.json new file mode 100644 index 000000000..38fe599e7 --- /dev/null +++ b/apps/tilthydro/metadata.json @@ -0,0 +1,14 @@ +{ + "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} + ] +} diff --git a/apps/timeandlife/ChangeLog b/apps/timeandlife/ChangeLog index c7b309a74..115067b80 100644 --- a/apps/timeandlife/ChangeLog +++ b/apps/timeandlife/ChangeLog @@ -1 +1 @@ -0.1: New app +0.01: New app diff --git a/apps/timeandlife/metadata.json b/apps/timeandlife/metadata.json new file mode 100644 index 000000000..86800f16f --- /dev/null +++ b/apps/timeandlife/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "timeandlife", + "name": "Time and Life", + "shortName":"Time and Lfie", + "icon": "app.png", + "version":"0.01", + "description": "A simple watchface which displays the time when the screen is tapped and decay according to the rules of Conway's game of life.", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator":true, + "readme": "README.md", + "storage": [ + {"name":"timeandlife.app.js","url":"app.js"}, + {"name":"timeandlife.img","url":"app-icon.js","evaluate":true} + ] +} + diff --git a/apps/timecal/metadata.json b/apps/timecal/metadata.json new file mode 100644 index 000000000..3237dd08a --- /dev/null +++ b/apps/timecal/metadata.json @@ -0,0 +1,13 @@ +{ "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"} + ] +} diff --git a/apps/timerclk/ChangeLog b/apps/timerclk/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/timerclk/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/timerclk/README.md b/apps/timerclk/README.md new file mode 100644 index 000000000..fd6d2b16b --- /dev/null +++ b/apps/timerclk/README.md @@ -0,0 +1,72 @@ +# Timer Clock + +A clock based on the Anton Clock with stopwatches, timers and alarms based on the Stopwatch Touch style and an alarm widget based on the one from Default alarm & timer. + +## Features + +* two slots for stopwatches / timers on the clock screen +* configurable font and size (Anton font has fixed size) +* stopwatch with modifiable start value +* timer that can be paused +* alarms +* multiple stopwatches, timers and alarms +* stopwatches and timers keep running in the background + +## Images + +![](screenshot.png) + +### Stopwatch + +![](screenshot_stopwatch1.png) + +![](screenshot_stopwatch2.png) + +### Settings + +![](screenshot_settings1.png) + +![](screenshot_settings2.png) + +![](screenshot_settings3.png) + +## Controls + +### Bangle.js 1 + +#### Clock + +* Left: Stopwatch +* Right: Timer +* Button 1 / 2: Alarm + +#### Stopwatch / Timer / Alarm + +* Button 1: + * edit mode: increase + * control mode: play / pause +* Button 2: switch between edit / control mode +* Button 3: + * edit mode: decrease + * control mode: reset / remove +* Left: + * edit mode: previous index + * control mode: previous stopwatch / timer / alarm +* Right: + * edit mode: next index + * control mode: next stopwatch / timer / alarm + +### Bangle.js 2 + +#### Clock + +* Swipe left: Stopwatch +* Swipe right: Timer +* Swipe over date: Alarm + +#### Stopwatch / Timer / Alarm + +* Swipe left: previous stopwatch / timer / alarm +* Swipe right: next stopwatch / timer / alarm +* Swipe up: increase index swiped over +* Swipe down: decrease index swiped over \ No newline at end of file diff --git a/apps/timerclk/alarm.alert.js b/apps/timerclk/alarm.alert.js new file mode 100644 index 000000000..f4b61822a --- /dev/null +++ b/apps/timerclk/alarm.alert.js @@ -0,0 +1,57 @@ +if (timerclkAlarmTimeout) clearInterval(timerclkAlarmTimeout); +var timerclk = require("timerclk.lib.js"); +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "vibrate":10 +}, settings.alarm||{}); + +function showAlarm(alarm) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); + Bangle.setLocked(false); + E.showPrompt("Alarm!",{ + title:"ALARM!", + buttons : {/*LANG*/"Ok":true} + }).then(function(ok) { + buzzCount = 0; + if (ok) { + alarm.last = new Date().getDate(); + } + require("Storage").write("timerclk.alarm.json",JSON.stringify(alarms)); + load(); + }); + function vibrate(counter) { + VIBRATE.write(1); + setTimeout(() => VIBRATE.write(0), 100); + if (--counter) setTimeout(() => vibrate(counter), 250); + } + function buzz() { + if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence + vibrate(4); + if (buzzCount--) + setTimeout(buzz, 3000); + else { // auto-snooze + buzzCount = settings.vibrate; + setTimeout(buzz, 600000); + } + } + var buzzCount = settings.vibrate; + buzz(); +} + +// Check for alarms +console.log("checking for alarms..."); +var alarms = require("Storage").readJSON("timerclk.alarm.json",1)||[]; +var active = alarms.filter(e=>e.on); +if (active.length) { + // if there's an alarm, show it + active = active.sort((a,b)=>(a.time-b.time)+(a.last-b.last)*86400000); + if (active[0].last != new Date().getDate()) { + showAlarm(active[0]); + } else { + setTimeout(load, 100); + } +} else { + // otherwise just go back to default app + setTimeout(load, 100); +} diff --git a/apps/timerclk/alarm.info b/apps/timerclk/alarm.info new file mode 100644 index 000000000..1289f8cef --- /dev/null +++ b/apps/timerclk/alarm.info @@ -0,0 +1 @@ +{"id":"timerclk","name":"tclk Alarm","src":"timerclk.alarm.js","icon":"timerclk.img","version":"0.01","tags":"","files":"","sortorder":10} diff --git a/apps/timerclk/alarm.js b/apps/timerclk/alarm.js new file mode 100644 index 000000000..4acaa6cf0 --- /dev/null +++ b/apps/timerclk/alarm.js @@ -0,0 +1,116 @@ +var timerclk = require("timerclk.lib.js"); +const height = g.getHeight(), width = g.getWidth(); + +var all = require("Storage").readJSON("timerclk.alarm.json") || []; +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, + "vibrate":4, +}, settings = settings.alarm||{}); +var defaultElement = {time:43200000, on:true, last:null}; + +var current = 0; +var editIndex = 0; +var drawInterval; +var drawIntervalTimeout; +var buttons; +var dragBorderHrsMins=0, dragBorderMinsSecs=0; + +function update() { + if (drawInterval) clearInterval(drawInterval); + if (drawIntervalTimeout) clearTimeout(drawIntervalTimeout); + if (all[current].start) { + drawIntervalTimeout = setTimeout(() => {drawInterval = setInterval(draw, 1000); draw();}, 1000 - (timerclk.getTime(all[current]) % 1000)); + } else { + drawInterval = null; + drawIntervalTimeout = null; + } + draw(); + drawButtons(); +} +function activate() { + all[current].on = !all[current].on; + all[current].last = null; + update(); + require("Storage").write("timerclk.alarm.json",JSON.stringify(all)); + timerclkCheckAlarms(); +} +function remove() { + all.splice(current, 1); + if (current == all.length) current--; + if (all.length == 0) { + all.push(defaultElement.clone()); + current++; + } + update(); + require("Storage").write("timerclk.alarm.json",JSON.stringify(all)); + timerclkCheckAlarms(); +} + +function edit(position, change) { + if (position == 1) all[current].time += change*1000; + else if (position == 2) all[current].time += change*60000; + else if (position == 3) all[current].time += change*3600000; + require("Storage").write("timerclk.alarm.json",JSON.stringify(all)); + timerclkCheckAlarms(); +} + +var buttons = { + reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: remove, img: timerclk.remove_img, col:"#f50"}, // remove + play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: activate, img: timerclk.play_img, col:"#0ff"}, // active +}; + + +function drawButtons() { + if (all[current].on) { + buttons.play.img = timerclk.pause_img; + } else { + buttons.play.img = timerclk.play_img; + } + for (var button of buttons) { + g.setColor(button.col); + g.fillRect(button.pos[0], button.pos[1], button.pos[2], button.pos[3]); + g.setColor("#000"); + // scale 24px images + let iw = settings.buttonHeight-10; + var scale = iw/24; + let ix = button.pos[0] + ((button.pos[2]-button.pos[0] - iw) /2); + let iy = button.pos[1] + ((button.pos[3]-button.pos[1] - iw) /2); + g.drawImage(button.img, ix, iy, {scale: scale}); + } +} + +function draw() { + var x = g.getWidth()/2; + var y = g.getHeight()/2; + g.reset(); + + g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2-settings.buttonHeight); + g.setFontAlign(0,0).setFont(settings.indexFont, settings.indexFontSize); + g.drawString(current+1, x, Bangle.appRect.y + (g.stringMetrics("0").height/2)); + g.setFontAlign(0,0).setFont(settings.font, settings.fontSize); + var timeStr = timerclk.formatTime(all[current].time, false, false, true); + g.drawString(timeStr,x,y); + var start = (width-g.stringMetrics(timeStr).width)/2; + timeStr = timeStr.split(":"); + var markerPosChange = g.stringMetrics("__").width/2; + if (editIndex == 3) x = start + g.stringMetrics(timeStr[0]).width - markerPosChange; + else if (editIndex == 2) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]).width - markerPosChange; + else if (editIndex == 1) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]+":"+timeStr[2]).width - markerPosChange; + else x = 0; + if (x) g.drawString("__", x, y); + dragBorderHrsMins = start+g.stringMetrics(timeStr[0]).width+g.stringMetrics(":").width/2; + dragBorderMinsSecs = start+g.stringMetrics(timeStr[0]+":"+timeStr[1]).width+g.stringMetrics(":").width/2; +} + +if (all.length == 0) { + all.push(defaultElement.clone()); +} +timerclk.registerControls(this); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +update(); diff --git a/apps/timerclk/app-icon.js b/apps/timerclk/app-icon.js new file mode 100644 index 000000000..278cf4bb6 --- /dev/null +++ b/apps/timerclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgP/AFHzvmf+f8z/8tnv/vs9/1t/v+kv94jR/H4n/wn4CBAYPwnEP8AFDg/AAoUwAoPgmABBwfQAonwAo0/4gFC4AFE4gFLmGEAoQDBxgFCwEQAIIFIj4FD/k//hNBAoZZBAoc8j6oS8/P+1NAoP63+7+wMCz/u/YEB/v/v4dI1+pAQIFBx/J/2/AoP5tFJr71eA==")) diff --git a/apps/timerclk/app-icon.png b/apps/timerclk/app-icon.png new file mode 100644 index 000000000..074db4ed7 Binary files /dev/null and b/apps/timerclk/app-icon.png differ diff --git a/apps/timerclk/app.js b/apps/timerclk/app.js new file mode 100644 index 000000000..eeb3ac4cd --- /dev/null +++ b/apps/timerclk/app.js @@ -0,0 +1,151 @@ +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)); +}; + +var timerclk = require("timerclk.lib.js"); +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "timeFont":"Anton", + "timeFontSize":0, + "dateFont":"6x8", + "dateFontSize":2, + "dowFont":"6x8", + "dowFontSize":2, + "specialFont":"6x8", + "specialFontSize":2, + "shortDate":true, + "showStopwatches":true, + "showTimers":true, +}, settings.clock||{}); + +var stopwatches = [], timers = []; +if (settings.showStopwatches) { + stopwatches = require("Storage").readJSON("timerclk.stopwatch.json") || []; + stopwatches = stopwatches.filter(e=>e.start||e.time); +} +if (settings.showTimers) { + timers = require("Storage").readJSON("timerclk.timer.json") || []; + timers = timers.filter(e=>e.start||e.timeAdd); +} + +// timeout used to update every minute +var drawTimeout; +var drawSpecialTimeout; +// border between time and date/dow +var dragBorder = g.getHeight()/2; + +// schedule a draw for the next minute +function queueDraw(timeout, interval, func) { + if (timeout) clearTimeout(timeout); + timeout = setTimeout(function() { + timeout = undefined; + func(); + }, interval - (Date.now() % interval)); +} + +function drawSpecial() { + var interval = 60000; + var stopwatch = 0, timer = 0, time; + var x = g.getWidth()/4; + g.setColor(g.theme.fg); + g.setFontAlign(0,0).setFont(settings.specialFont, settings.specialFontSize); + var y = Bangle.appRect.y + g.stringMetrics("00:00").height/2; + g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y+g.stringMetrics("00:00").height); + + if (stopwatches.length) { + time = timerclk.getTime(stopwatches[stopwatch]); + g.drawString(timerclk.formatTime(time, true), x, y); + if (Math.floor(time/3600000) === 0) interval = 1000; + stopwatch++; + } else if (timers.length > 1) { + time = timers[timer].time - timerclk.getTime(timers[timer]); + g.drawString(timerclk.formatTime(time, true), x, y); + if (Math.floor(time/3600000) === 0) interval = 1000; + timer++; + } + x += g.getWidth()/2; + if (timers.length) { + time = timers[timer].time - timerclk.getTime(timers[timer]); + g.drawString(timerclk.formatTime(time, true), x, y); + if (Math.floor(time/3600000) === 0) interval = 1000; + } else if (stopwatches.length > 1) { + time = timerclk.getTime(stopwatches[stopwatch]); + g.drawString(timerclk.formatTime(time, true), x, y); + if (Math.floor(time/3600000) === 0) interval = 1000; + } + queueDraw(drawSpecialTimeout, interval, drawSpecial); +} + +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,settings.shortDate).toUpperCase(); + var dowStr = require("locale").dow(date).toUpperCase(); + + // draw time + if (settings.timeFont == "Anton") { + g.setFontAlign(0,0).setFont("Anton"); + } else { + g.setFontAlign(0,0).setFont(settings.timeFont, settings.timeFontSize); + } + g.clearRect(Bangle.appRect.x, x-g.stringMetrics(timeStr).height/2, Bangle.appRect.x2, Bangle.appRect.y2); // clear the background + g.drawString(timeStr,x,y); + // draw date + y += g.stringMetrics(timeStr).height/2; + g.setFontAlign(0,0).setFont(settings.dateFont, settings.dateFontSize); + dragBorder = y; + y += g.stringMetrics(dateStr).height/2; + g.drawString(dateStr,x,y); + //draw day of week + y += g.stringMetrics(dateStr).height/2; + g.setFontAlign(0,0).setFont(settings.dowFont, settings.dowFontSize); + y += g.stringMetrics(dowStr).height/2; + g.drawString(dowStr,x,y); + // queue draw in one minute + queueDraw(drawTimeout, 60000, draw); +} + +if (process.env.HWVERSION==1) { + setWatch(()=>load("timerclk.stopwatch.js"), BTN4); + setWatch(()=>load("timerclk.timer.js"), BTN5); + setWatch(()=>load("timerclk.alarm.js"), BTN3); + setWatch(()=>load("timerclk.alarm.js"), BTN1); +} else { + var absY, lastX, lastY; + Bangle.on('drag', e=>{ + if (!e.b) { + if (lastX > 50) { // right + if (absY < dragBorder) { // drag over time + load("timerclk.timer.js"); + }else { // drag over date/dow + load("timerclk.alarm.js"); + } + } else if (lastX < -50) { // left + if (absY < dragBorder) { // drag over time + load("timerclk.stopwatch.js"); + }else { // drag over date/dow + load("timerclk.alarm.js"); + } + } else if (lastY > 50) { // down + } else if (lastY < -50) { // up + } + lastX = 0; + lastY = 0; + } else { + lastX = lastX + e.dx; + lastY = lastY + e.dy; + absY = e.y; + } + }); +} + +Bangle.setUI("clock"); // Show launcher when middle button pressed +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw(); +if (stopwatches || timers) drawSpecial(); diff --git a/apps/timerclk/boot.js b/apps/timerclk/boot.js new file mode 100644 index 000000000..9a09f68f3 --- /dev/null +++ b/apps/timerclk/boot.js @@ -0,0 +1,48 @@ +var timerclkTimerTimeout; +var timerclkAlarmTimeout; +function timerclkCheckTimers() { + if (timerclkTimerTimeout) clearTimeout(timerclkTimerTimeout); + var timers = require('Storage').readJSON('timerclk.timer.json',1)||[]; + timers = timers.filter(e=>e.start); + if (timers.length) { + timers = timers.sort((a,b)=>{ + var at = a.timeAdd; + if (a.start) at += Date.now()-a.start; + at = a.period-at; + var bt = b.timeAdd; + if (b.start) bt += Date.now()-b.start; + bt = b.period-bt; + return at-bt; + }); + if (!require('Storage').read("timerclk.timer.alert.js")) { + console.log("No timer app!"); + } else { + var time = timers[0].timeAdd; + if (timers[0].start) time += Date.now()-timers[0].start; + time = timers[0].time - time; + if (time<1000) t=1000; + if (timerclkTimerTimeout) clearTimeout(timerclkTimerTimeout); + timerclkTimerTimeout = setTimeout(() => load("timerclk.timer.alert.js"),time); + } + } +} +function timerclkCheckAlarms() { + if (timerclkAlarmTimeout) clearTimeout(timerclkAlarmTimeout); + var alarms = require('Storage').readJSON('timerclk.alarm.json',1)||[]; + var currentTime = require("timerclk.lib.js").getCurrentTime(); + alarms = alarms.filter(e=>e.on); + if (alarms.length) { + alarms = alarms.sort((a,b)=>(a.time-b.time)+(a.last-b.last)*86400000); + if (!require('Storage').read("timerclk.alarm.alert.js")) { + console.log("No alarm app!"); + } else { + var time = alarms[0].time-currentTime; + if (alarms[0].last == new Date().getDate() || time < 0) time += 86400000; + if (time<1000) t=1000; + if (timerclkAlarmTimeout) clearTimeout(timerclkAlarmTimeout); + timerclkAlarmTimeout = setTimeout(() => load("timerclk.alarm.alert.js"),time); + } + } +} +timerclkCheckTimers(); +timerclkCheckAlarms(); diff --git a/apps/timerclk/lib.js b/apps/timerclk/lib.js new file mode 100644 index 000000000..718962fe0 --- /dev/null +++ b/apps/timerclk/lib.js @@ -0,0 +1,127 @@ +exports.pause_img = atob("GBiBAf///////////+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B////////////w=="); +exports.play_img = atob("GBiBAf////////////P///D///A///Af//AH//AB//AAf/AAH/AAB/AAB/AAH/AAf/AB//AH//Af//A///D///P//////////////w=="); +exports.reset_img = atob("GBiBAf////////////AAD+AAB+f/5+f/5+f/5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+f/5+f/5+f/5+AAB/AAD////////////w=="); +exports.remove_img = atob("GBiBAf///////////+P/x+H/h+D/B/B+D/g8H/wYP/4Af/8A//+B//+B//8A//4Af/wYP/g8H/B+D+D/B+H/h+P/x////////////w=="); + +exports.formatTime = function(t, short, tnthEnable, fullTime) { + var negative = ""; + if (t < 0) { + t = t*(-1); + negative = "-"; + } + let hrs = Math.floor(t/3600000); + let mins = Math.floor(t/60000)%60; + let secs = Math.floor(t/1000)%60; + var tnth = ""; + if (tnthEnable) { + tnth = Math.floor(t/100)%10; + tnth = "."+tnth; + } + var hrsStr = hrs; + if (hrs < 10 && !negative) hrsStr = "0"+hrs; + var text; + if (short) { + if (hrs === 0) text = negative + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2); + else text = negative + hrsStr + "/" + ("0"+mins).substr(-2); + } else { + if (hrs === 0 && !fullTime) text = negative + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + tnth; + else text = negative + hrsStr + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2); + } + return text; +}; + +exports.getTime = function(e) { + var time = e.timeAdd; + if (e.start) { + time += Date.now() - e.start; + } + return time; +}; + +exports.getCurrentTime = function() { + var date = new Date(); + return date.getHours()*3600000+date.getMinutes()*60000+date.getSeconds()*1000+date.getMilliseconds(); +}; + +exports.registerControls = function(o) { + if (process.env.HWVERSION==1) { + setWatch(()=>{ + if (o.editIndex == 0) o.buttons.play.callback(); + else o.edit(o.editIndex, 1); + o.draw(); + }, BTN1, {repeat:true}); + setWatch(()=>{ + o.editIndex = !o.editIndex; + o.draw(); + }, BTN2, {repeat:true}); + setWatch(()=>{ + if (o.editIndex == 0) o.buttons.reset.callback(); + else o.edit(o.editIndex, -1); + o.draw(); + }, BTN3, {repeat:true}); + setWatch(()=>{ + if (o.editIndex) { + o.editIndex++; + if (o.editIndex > 3) o.editIndex = 1; + } else if (o.current > 0) o.current--; + o.update(); + }, BTN4, {repeat:true}); + setWatch(()=>{ + if (o.editIndex) { + o.editIndex--; + if (o.editIndex < 1) o.editIndex = 3; + } else { + o.current++; + if (o.current == o.all.length) o.all.push(o.defaultElement.clone()); + } + o.update(); + }, BTN5, {repeat:true}); + } else { + setWatch(()=>load(), BTN1); + Bangle.on('touch',(n,e)=>{ + for (var button of o.buttons) { + if (e.x>=button.pos[0] && e.y>=button.pos[1] && + e.x{ + if (!e.b) { + if (lastX > 40) { // right + o.current++; + if (o.current == o.all.length) o.all.push(o.defaultElement.clone()); + } else if (lastX < -40) { // left + if (o.current > 0) { + o.current--; + } + } else if (lastY > 30) { // down + if (absX < o.dragBorderHrsMins) { + o.edit(3, -1); + } else if (absX > o.dragBorderHrsMins && absX < o.dragBorderMinsSecs) { + o.edit(2, -1); + } else { + o.edit(1, -1); + } + } else if (lastY < -30) { // up + if (absX < o.dragBorderHrsMins) { + o.edit(3, 1); + } else if (absX > o.dragBorderHrsMins && absX < o.dragBorderMinsSecs) { + o.edit(2, 1); + } else { + o.edit(1, 1); + } + } + lastX = 0; + lastY = 0; + o.update(); + } else { + absX = e.x; + lastX = lastX + e.dx; + lastY = lastY + e.dy; + } + }); + } +}; diff --git a/apps/timerclk/metadata.json b/apps/timerclk/metadata.json new file mode 100644 index 000000000..6b415c0fc --- /dev/null +++ b/apps/timerclk/metadata.json @@ -0,0 +1,38 @@ +{ + "id": "timerclk", + "name": "Timer Clock", + "shortName":"Timer Clock", + "version":"0.01", + "description": "A clock with stopwatches, timers and alarms build in.", + "icon": "app-icon.png", + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS","BANGLEJS2"], + "screenshots": [ + {"url":"screenshot.png"}, + {"url":"screenshot_stopwatch1.png"}, + {"url":"screenshot_stopwatch2.png"}, + {"url":"screenshot_settings1.png"}, + {"url":"screenshot_settings2.png"}, + {"url":"screenshot_settings3.png"} + ], + "readme": "README.md", + "storage": [ + {"name":"timerclk.app.js","url":"app.js"}, + {"name":"timerclk.img","url":"app-icon.js","evaluate":true}, + {"name":"timerclk.boot.js","url":"boot.js"}, + {"name":"timerclk.lib.js","url":"lib.js"}, + {"name":"timerclk.wid.js","url":"wid.js"}, + {"name":"timerclk.settings.js","url":"settings.js"}, + {"name":"timerclk.stopwatch.js","url":"stopwatch.js"}, + {"name":"timerclk.timer.js","url":"timer.js"}, + {"name":"timerclk.timer.alert.js","url":"timer.alert.js"}, + {"name":"timerclk.alarm.js","url":"alarm.js"}, + {"name":"timerclk.alarm.alert.js","url":"alarm.alert.js"}, + {"name":"timerclk.stopwatch.info","url":"stopwatch.info"}, + {"name":"timerclk.timer.info","url":"timer.info"}, + {"name":"timerclk.alarm.info","url":"alarm.info"} + ], + "data": [{"name":"timerclk.json"},{"name":"timerclk.stopwatch.json"},{"name":"timerclk.timer.json"},{"name":"timerclk.alarm.json"}], + "sortorder": 0 +} diff --git a/apps/timerclk/pause-24.png b/apps/timerclk/pause-24.png new file mode 100644 index 000000000..7ff72e906 Binary files /dev/null and b/apps/timerclk/pause-24.png differ diff --git a/apps/timerclk/play-24.png b/apps/timerclk/play-24.png new file mode 100644 index 000000000..26fa8d99c Binary files /dev/null and b/apps/timerclk/play-24.png differ diff --git a/apps/timerclk/remove-24.png b/apps/timerclk/remove-24.png new file mode 100644 index 000000000..b59505bcb Binary files /dev/null and b/apps/timerclk/remove-24.png differ diff --git a/apps/timerclk/reset-24.png b/apps/timerclk/reset-24.png new file mode 100644 index 000000000..73fb28dec Binary files /dev/null and b/apps/timerclk/reset-24.png differ diff --git a/apps/timerclk/screenshot.png b/apps/timerclk/screenshot.png new file mode 100644 index 000000000..1bccf6807 Binary files /dev/null and b/apps/timerclk/screenshot.png differ diff --git a/apps/timerclk/screenshot_settings1.png b/apps/timerclk/screenshot_settings1.png new file mode 100644 index 000000000..da187e496 Binary files /dev/null and b/apps/timerclk/screenshot_settings1.png differ diff --git a/apps/timerclk/screenshot_settings2.png b/apps/timerclk/screenshot_settings2.png new file mode 100644 index 000000000..4b12848d0 Binary files /dev/null and b/apps/timerclk/screenshot_settings2.png differ diff --git a/apps/timerclk/screenshot_settings3.png b/apps/timerclk/screenshot_settings3.png new file mode 100644 index 000000000..b1ef2381f Binary files /dev/null and b/apps/timerclk/screenshot_settings3.png differ diff --git a/apps/timerclk/screenshot_stopwatch1.png b/apps/timerclk/screenshot_stopwatch1.png new file mode 100644 index 000000000..f50d7a1d1 Binary files /dev/null and b/apps/timerclk/screenshot_stopwatch1.png differ diff --git a/apps/timerclk/screenshot_stopwatch2.png b/apps/timerclk/screenshot_stopwatch2.png new file mode 100644 index 000000000..89f91ae1b Binary files /dev/null and b/apps/timerclk/screenshot_stopwatch2.png differ diff --git a/apps/timerclk/settings.js b/apps/timerclk/settings.js new file mode 100644 index 000000000..556dded98 --- /dev/null +++ b/apps/timerclk/settings.js @@ -0,0 +1,292 @@ +(function(back) { + const FILE = "timerclk.json"; + const BOOL_FORMAT = v=>v?/*LANG*/"On":/*LANG*/"Off"; + // Load settings + var settings = require('Storage').readJSON(FILE, true) || {} + settings.clock = Object.assign({ + "timeFont":"Anton", + "timeFontSize":0, + "dateFont":"6x8", + "dateFontSize":2, + "dowFont":"6x8", + "dowFontSize":2, + "specialFont":"6x8", + "specialFontSize":2, + "shortDate":true, + "showStopwatches":true, + "showTimers":true, + }, settings.clock||{}); + settings.stopwatch = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, + }, settings.stopwatch||{}); + settings.timer = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, + "vibrate":10, + }, settings.timer||{}); + settings.alarm = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, + "vibrate":10, + }, settings.alarm||{}); + var timeFonts = ["Anton"].concat(g.getFonts()); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Show the menu + var mainMenu = { + "" : { "title" : "Timer Clock" }, + "< Back" : () => back(), + "Clock": ()=>{E.showMenu(clockMenu);}, + "Stopwatch": ()=>{E.showMenu(stopwatchMenu);}, + "Timer": ()=>{E.showMenu(timerMenu);}, + "Alarm": ()=>{E.showMenu(alarmMenu);}, + }; + var clockMenu = { + "" : { "title" : "Clock" }, + "< Back" : () => E.showMenu(mainMenu), + "time font":{ + value: 0|timeFonts.indexOf(settings.clock.timeFont), + format: v => timeFonts[v], + min: 0, max: timeFonts.length-1, + onchange: v => { + settings.clock.timeFont = timeFonts[v]; + writeSettings(); + } + }, + "time size":{ + value: 0|settings.clock.timeFontSize, + min: 0, + onchange: v => { + settings.clock.timeFontSize = v; + writeSettings(); + } + }, + "date font":{ + value: 0|g.getFonts().indexOf(settings.clock.dateFont), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.clock.dateFont = g.getFonts()[v]; + writeSettings(); + } + }, + "date size":{ + value: 0|settings.clock.dateFontSize, + min: 0, + onchange: v => { + settings.clock.dateFontSize = v; + writeSettings(); + } + }, + "dow font":{ + value: 0|g.getFonts().indexOf(settings.clock.dowFont), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.clock.dowFont = g.getFonts()[v]; + writeSettings(); + } + }, + "dow size":{ + value: 0|settings.clock.dowFontSize, + min: 0, + onchange: v => { + settings.clock.dowFontSize = v; + writeSettings(); + } + }, + "short date": { + value: !!settings.clock.shortDate, + format: BOOL_FORMAT, + onchange: v => { + settings.clock.shortDate = v; + writeSettings(); + } + }, + "stopwatches": { + value: !!settings.clock.showStopwatches, + format: v=>v?/*LANG*/"Show":/*LANG*/"Hide", + onchange: v => { + settings.clock.showStopwatches = v; + writeSettings(); + } + }, + "timers": { + value: !!settings.clock.showTimers, + format: v=>v?/*LANG*/"Show":/*LANG*/"Hide", + onchange: v => { + settings.clock.showTimers = v; + writeSettings(); + } + }, + }; + + var stopwatchMenu = { + "" : { "title" : "Stopwatch" }, + "< Back" : () => E.showMenu(mainMenu), + "font":{ + value: 0|g.getFonts().indexOf(settings.stopwatch.font), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.stopwatch.font = g.getFonts()[v]; + writeSettings(); + } + }, + "fontsize":{ + value: 0|settings.stopwatch.fontSize, + min: 0, + onchange: v => { + settings.stopwatch.fontSize = v; + writeSettings(); + } + }, + "index font":{ + value: 0|g.getFonts().indexOf(settings.stopwatch.indexFont), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.stopwatch.indexFont = g.getFonts()[v]; + writeSettings(); + } + }, + "index size":{ + value: 0|settings.stopwatch.indexFontSize, + min: 0, + onchange: v => { + settings.stopwatch.indexFontSize = v; + writeSettings(); + } + }, + "button height":{ + value: 0|settings.stopwatch.buttonHeight, + min: 0, + onchange: v => { + settings.stopwatch.buttonHeight = v; + writeSettings(); + } + }, + }; + var timerMenu = { + "" : { "title" : "Timer" }, + "< Back" : () => E.showMenu(mainMenu), + "font":{ + value: 0|g.getFonts().indexOf(settings.timer.font), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.timer.font = g.getFonts()[v]; + writeSettings(); + } + }, + "fontsize":{ + value: 0|settings.timer.fontSize, + min: 0, + onchange: v => { + settings.timer.fontSize = v; + writeSettings(); + } + }, + "index font":{ + value: 0|g.getFonts().indexOf(settings.timer.indexFont), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.timer.indexFont = g.getFonts()[v]; + writeSettings(); + } + }, + "index size":{ + value: 0|settings.timer.indexFontSize, + min: 0, + onchange: v => { + settings.timer.indexFontSize = v; + writeSettings(); + } + }, + "button height":{ + value: 0|settings.timer.buttonHeight, + min: 0, + onchange: v => { + settings.timer.buttonHeight = v; + writeSettings(); + } + }, + "vibrate":{ + value: 0|settings.timer.vibrate, + min: 0, + onchange: v=>{ + settings.timer.vibrate = v; + writeSettings(); + } + } + }; + var alarmMenu = { + "" : { "title" : "Alarm" }, + "< Back" : () => E.showMenu(mainMenu), + "font":{ + value: 0|g.getFonts().indexOf(settings.alarm.font), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.alarm.font = g.getFonts()[v]; + writeSettings(); + } + }, + "fontsize":{ + value: 0|settings.alarm.fontSize, + min: 0, + onchange: v => { + settings.alarm.fontSize = v; + writeSettings(); + } + }, + "index font":{ + value: 0|g.getFonts().indexOf(settings.alarm.indexFont), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.alarm.indexFont = g.getFonts()[v]; + writeSettings(); + } + }, + "index size":{ + value: 0|settings.alarm.indexFontSize, + min: 0, + onchange: v => { + settings.alarm.indexFontSize = v; + writeSettings(); + } + }, + "button height":{ + value: 0|settings.alarm.buttonHeight, + min: 0, + onchange: v => { + settings.alarm.buttonHeight = v; + writeSettings(); + } + }, + "vibrate":{ + value: 0|settings.alarm.vibrate, + min: 0, + onchange: v=>{ + settings.alarm.vibrate = v; + writeSettings(); + } + } + }; + E.showMenu(mainMenu); +}); diff --git a/apps/timerclk/stopwatch.info b/apps/timerclk/stopwatch.info new file mode 100644 index 000000000..72ad418b1 --- /dev/null +++ b/apps/timerclk/stopwatch.info @@ -0,0 +1 @@ +{"id":"timerclk","name":"tclk Stopwatch","src":"timerclk.stopwatch.js","icon":"timerclk.img","version":"0.01","tags":"","files":"","sortorder":10} diff --git a/apps/timerclk/stopwatch.js b/apps/timerclk/stopwatch.js new file mode 100644 index 000000000..8ac6d30a7 --- /dev/null +++ b/apps/timerclk/stopwatch.js @@ -0,0 +1,135 @@ +var timerclk = require("timerclk.lib.js"); +const height = g.getHeight(), width = g.getWidth(); + +var all = require("Storage").readJSON("timerclk.stopwatch.json") || []; + +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, +}, settings.stopwatch||{}); +var defaultElement = {start:null, timeAdd:0}; +var current = 0; +var editIndex = 0; +var drawInterval; +var drawIntervalTimeout; +var buttons; + +function update() { + if (drawInterval) clearInterval(drawInterval); + if (drawIntervalTimeout) clearTimeout(drawIntervalTimeout); + var interval = Math.floor(timerclk.getTime(all[current])/3600000)?1000:100; + if (all[current].start) { + drawIntervalTimeout = setTimeout(() => {drawInterval = setInterval(draw, interval); draw();}, interval - (timerclk.getTime(all[current]) % interval)); + } else { + drawInterval = null; + drawIntervalTimeout = null; + } + draw(); + drawButtons(); +} +function play() { + if (all[current].start) { // running + all[current].timeAdd += Date.now() - all[current].start; + all[current].start = null; + update(); + } else { // paused + all[current].start = Date.now(); + update(); + } + require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all)); +} +function reset() { + all[current] = defaultElement.clone(); + update(); + require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all)); +} +function remove() { + all.splice(current, 1); + if (current == all.length) current--; + if (all.length == 0) { + all.push(defaultElement.clone()); + current++; + } + update(); + require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all)); +} + +function edit(position, change) { + if (position == 1) all[current].timeAdd += change*1000; + else if (position == 2) all[current].timeAdd += change*60000; + else if (position == 3) all[current].timeAdd += change*3600000; + require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all)); +} + + +var buttonsRunning = { + reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: reset, img: timerclk.reset_img, col:"#f50"}, + play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:"#0ff"}, +}; +var buttonsNormal = { + reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: remove, img: timerclk.remove_img, col:buttonsRunning.reset.col}, + play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:buttonsRunning.play.col}, +}; +buttons = buttonsNormal; + +function drawButtons() { + if (all[current].start || all[current].time) { + buttons = buttonsRunning; + if (all[current].start) { + buttons.play.img = timerclk.pause_img; + } else { + buttons.play.img = timerclk.play_img; + } + } else { + buttons = buttonsNormal; + } + for (var button of buttons) { + g.setColor(button.col); + g.fillRect(button.pos[0], button.pos[1], button.pos[2], button.pos[3]); + g.setColor("#000"); + // scale 24px images + let iw = settings.buttonHeight-10; + var scale = iw/24; + let ix = button.pos[0] + ((button.pos[2]-button.pos[0] - iw) /2); + let iy = button.pos[1] + ((button.pos[3]-button.pos[1] - iw) /2); + g.drawImage(button.img, ix, iy, {scale: scale}); + } +} + +function draw() { + var x = g.getWidth()/2; + var y = g.getHeight()/2; + g.reset(); + + var timeStr = timerclk.formatTime(timerclk.getTime(all[current]), false, true); + g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2-settings.buttonHeight); + g.setFontAlign(0,0).setFont(settings.indexFont, settings.indexFontSize); + g.drawString(current+1, x, Bangle.appRect.y + (g.stringMetrics("0").height/2)); + g.setFontAlign(0,0).setFont(settings.font, settings.fontSize); + g.drawString(timeStr,x,y); + + var start = (width-g.stringMetrics(timeStr).width)/2; + timeStr = timeStr.split(".")[0].split(":"); + if (timeStr.length < 3) timeStr = [""].concat(timeStr); + var markerPosChange = g.stringMetrics("__").width/2; + if (editIndex == 3) x = start + g.stringMetrics(timeStr[0]).width - markerPosChange; + else if (editIndex == 2) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]).width - markerPosChange; + else if (editIndex == 1) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]+":"+timeStr[2]).width - markerPosChange; + else x = 0; + if (x) g.drawString("__", x, y); + dragBorderHrsMins = start+g.stringMetrics(timeStr[0]).width+g.stringMetrics(":").width/2; + dragBorderMinsSecs = start+g.stringMetrics(timeStr[0]+":"+timeStr[1]).width+g.stringMetrics(":").width/2; +} + +if (all.length == 0) { + all.push(defaultElement.clone()); +} +timerclk.registerControls(this); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +update(); diff --git a/apps/timerclk/timer.alert.js b/apps/timerclk/timer.alert.js new file mode 100644 index 000000000..f51ea6767 --- /dev/null +++ b/apps/timerclk/timer.alert.js @@ -0,0 +1,62 @@ +if (timerclkTimerTimeout) clearInterval(timerclkTimerTimeout); +var timerclk = require("timerclk.lib.js"); +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "vibrate":10 +}, settings.timer||{}); + +function showTimer(timer) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); + Bangle.setLocked(false); + E.showPrompt("Timer finished!",{ + title:"TIMER!", + buttons : {/*LANG*/"Ok":true} + }).then(function(ok) { + buzzCount = 0; + if (ok) { + timer.time += Date.now() - timer.start; + timer.start = null; + } + require("Storage").write("timerclk.timer.json",JSON.stringify(timers)); + load(); + }); + function vibrate(counter) { + VIBRATE.write(1); + setTimeout(() => VIBRATE.write(0), 100); + if (--counter) setTimeout(() => vibrate(counter), 250); + } + function buzz() { + if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence + vibrate(4); + if (buzzCount--) + setTimeout(buzz, 3000); + else { // auto-snooze + buzzCount = settings.vibrate; + setTimeout(buzz, 600000); + } + } + var buzzCount = settings.vibrate; + buzz(); +} + +// Check for timers +console.log("checking for timers..."); +var timers = require("Storage").readJSON("timerclk.timer.json",1)||[]; +var active = timers.filter(e=>e.start); +if (active.length) { + // if there's an timer, show it + active = active.sort((a,b)=>{ + var at = a.time; + if (a.start) at += Date.now()-a.start; + at = a.period-at; + var bt = b.time; + if (b.start) bt += Date.now()-b.start; + bt = b.period-bt; + return at-bt; + }); + showTimer(active[0]); +} else { + // otherwise just go back to default app + setTimeout(load, 100); +} diff --git a/apps/timerclk/timer.info b/apps/timerclk/timer.info new file mode 100644 index 000000000..39a338693 --- /dev/null +++ b/apps/timerclk/timer.info @@ -0,0 +1 @@ +{"id":"timerclk","name":"tclk Timer","src":"timerclk.timer.js","icon":"timerclk.img","version":"0.01","tags":"","files":"","sortorder":10} diff --git a/apps/timerclk/timer.js b/apps/timerclk/timer.js new file mode 100644 index 000000000..060c07813 --- /dev/null +++ b/apps/timerclk/timer.js @@ -0,0 +1,139 @@ +var timerclk = require("timerclk.lib.js"); +const height = g.getHeight(), width = g.getWidth(); + +var all = require("Storage").readJSON("timerclk.timer.json") || []; +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, + "vibrate":4, +}, settings = settings.timer||{}); +var defaultElement = {time:300000, start:null, timeAdd:0}; + +var current = 0; +var editIndex = 0; +var drawInterval; +var drawIntervalTimeout; +var buttons; +var dragBorderHrsMins=0, dragBorderMinsSecs=0; + +function update() { + if (drawInterval) clearInterval(drawInterval); + if (drawIntervalTimeout) clearTimeout(drawIntervalTimeout); + if (all[current].start) { + drawIntervalTimeout = setTimeout(() => {drawInterval = setInterval(draw, 1000); draw();}, 1000 - (timerclk.getTime(all[current]) % 1000)); + } else { + drawInterval = null; + drawIntervalTimeout = null; + } + draw(); + drawButtons(); +} +function play() { + if (all[current].start) { // running + all[current].timeAdd += Date.now() - all[current].start; + all[current].start = null; + update(); + } else { // paused + all[current].start = Date.now(); + update(); + } + require("Storage").write("timerclk.timer.json",JSON.stringify(all)); + timerclkCheckTimers(); +} +function reset() { + all[current] = defaultElement.clone(); + update(); + require("Storage").write("timerclk.timer.json",JSON.stringify(all)); + timerclkCheckTimers(); +} +function remove() { + all.splice(current, 1); + if (current == all.length) current--; + if (all.length == 0) { + all.push(defaultElement.clone()); + current++; + } + update(); + require("Storage").write("timerclk.timer.json",JSON.stringify(all)); + timerclkCheckTimers(); +} + +function edit(position, change) { + if (position == 1) all[current].time += change*1000; + else if (position == 2) all[current].time += change*60000; + else if (position == 3) all[current].time += change*3600000; + require("Storage").write("timerclk.timer.json",JSON.stringify(all)); + timerclkCheckTimers(); +} + +var buttonsRunning = { + reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: reset, img: timerclk.reset_img, col:"#f50"}, + play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:"#0ff"}, +}; +var buttonsNormal = { + reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: remove, img: timerclk.remove_img, col:buttonsRunning.reset.col}, + play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:buttonsRunning.play.col}, +}; +buttons = buttonsNormal; + + +function drawButtons() { + if (all[current].start || all[current].timeAdd) { + buttons = buttonsRunning; + if (all[current].start) { + buttons.play.img = timerclk.pause_img; + } else { + buttons.play.img = timerclk.play_img; + } + } else { + buttons = buttonsNormal; + } + for (var button of buttons) { + g.setColor(button.col); + g.fillRect(button.pos[0], button.pos[1], button.pos[2], button.pos[3]); + g.setColor("#000"); + // scale 24px images + let iw = settings.buttonHeight-10; + var scale = iw/24; + let ix = button.pos[0] + ((button.pos[2]-button.pos[0] - iw) /2); + let iy = button.pos[1] + ((button.pos[3]-button.pos[1] - iw) /2); + g.drawImage(button.img, ix, iy, {scale: scale}); + } +} + +function draw() { + var x = g.getWidth()/2; + var y = g.getHeight()/2; + g.reset(); + + var time = all[current].time - timerclk.getTime(all[current]); + g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2-settings.buttonHeight); + g.setFontAlign(0,0).setFont(settings.indexFont, settings.indexFontSize); + g.drawString(current+1, x, Bangle.appRect.y + (g.stringMetrics("0").height/2)); + g.setFontAlign(0,0).setFont(settings.font, settings.fontSize); + var timeStr = timerclk.formatTime(time, false, false, true); + g.drawString(timeStr,x,y); + + var start = (width-g.stringMetrics(timeStr).width)/2; + timeStr = timeStr.split(":"); + var markerPosChange = g.stringMetrics("__").width/2; + if (editIndex == 3) x = start + g.stringMetrics(timeStr[0]).width - markerPosChange; + else if (editIndex == 2) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]).width - markerPosChange; + else if (editIndex == 1) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]+":"+timeStr[2]).width - markerPosChange; + else x = 0; + if (x) g.drawString("__", x, y); + dragBorderHrsMins = start+g.stringMetrics(timeStr[0]).width+g.stringMetrics(":").width/2; + dragBorderMinsSecs = start+g.stringMetrics(timeStr[0]+":"+timeStr[1]).width+g.stringMetrics(":").width/2; +} + +if (all.length == 0) { + all.push(defaultElement.clone()); +} +timerclk.registerControls(this); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +update(); diff --git a/apps/timerclk/wid.js b/apps/timerclk/wid.js new file mode 100644 index 000000000..e3ddeb791 --- /dev/null +++ b/apps/timerclk/wid.js @@ -0,0 +1,7 @@ +WIDGETS["timerclk.alarm"]={area:"tl",width:0,draw:function() { + if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y); + },reload:function() { + WIDGETS["timerclk.alarm"].width = (require('Storage').readJSON('timerclk.alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0; + } +}; +WIDGETS["timerclk.alarm"].reload(); diff --git a/apps/tinydraw/ChangeLog b/apps/tinydraw/ChangeLog index af7f83942..2ee16e6b5 100644 --- a/apps/tinydraw/ChangeLog +++ b/apps/tinydraw/ChangeLog @@ -1 +1,2 @@ 0.01: Initial release +0.02: Don't start drawing with white colour on white canvas diff --git a/apps/tinydraw/app.js b/apps/tinydraw/app.js index e4c612219..b0b3ef15b 100644 --- a/apps/tinydraw/app.js +++ b/apps/tinydraw/app.js @@ -1,7 +1,7 @@ (function () { var pen = 'circle'; var discard = null; - var kule = [255, 255, 255]; + var kule = [0, 255, 255]; // R, G, B var oldLock = false; setInterval(() => { diff --git a/apps/tinydraw/metadata.json b/apps/tinydraw/metadata.json new file mode 100644 index 000000000..357fcc1d0 --- /dev/null +++ b/apps/tinydraw/metadata.json @@ -0,0 +1,19 @@ +{ "id": "tinydraw", + "name": "TinyDraw", + "shortName":"TinyDraw", + "version":"0.02", + "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" } + ] +} diff --git a/apps/torch/metadata.json b/apps/torch/metadata.json new file mode 100644 index 000000000..39655dbba --- /dev/null +++ b/apps/torch/metadata.json @@ -0,0 +1,15 @@ +{ + "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} + ] +} diff --git a/apps/toucher/metadata.json b/apps/toucher/metadata.json new file mode 100644 index 000000000..8b2715f0c --- /dev/null +++ b/apps/toucher/metadata.json @@ -0,0 +1,18 @@ +{ + "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", + "screenshots": [{"url":"screenshot1.jpg"}], + "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"}] +} diff --git a/apps/touchmenu/ChangeLog b/apps/touchmenu/ChangeLog index c5277e465..c5d90cb57 100644 --- a/apps/touchmenu/ChangeLog +++ b/apps/touchmenu/ChangeLog @@ -1 +1,2 @@ 0.01: App launched +0.02: Menu uses the correct screen area and properly resets when closed diff --git a/apps/touchmenu/metadata.json b/apps/touchmenu/metadata.json new file mode 100644 index 000000000..825989d99 --- /dev/null +++ b/apps/touchmenu/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "touchmenu", + "name": "TouchMenu", + "version": "0.02", + "description": "Redesigned menu that uses the full touchscreen on the Bangle.js 2", + "screenshots": [{"url":"touchmenu.gif"}], + "icon": "touchmenu.png", + "type": "bootloader", + "tags": "tool", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"touchmenu.boot.js","url":"touchmenu.boot.js"} + ] +} diff --git a/apps/touchmenu/touchmenu.boot.js b/apps/touchmenu/touchmenu.boot.js index 93a0ba1c8..6141c6a00 100644 --- a/apps/touchmenu/touchmenu.boot.js +++ b/apps/touchmenu/touchmenu.boot.js @@ -1,10 +1,8 @@ E.showMenu = function(items) { - const gw = g.getWidth(); - const gh = g.getHeight(); + var ar = Bangle.appRect; Bangle.removeAllListeners("drag"); if(!items){ - delete m; - g.clearRect(0, 30, gw, gh - 30); + g.clearRect(ar.x, ar.y, ar.x2, ar.y2); return false; } var loc = require("locale"); @@ -26,45 +24,45 @@ E.showMenu = function(items) { draw: () => { g.reset().setFont('12x20'); m.info.predraw(g); - g.setColor(m.info.cB).fillRect(0, 50, gw, gh - 30).setColor(m.info.cF); + g.setColor(m.info.cB).fillRect(ar.x, ar.y + 20, ar.x2, ar.y2).setColor(m.info.cF); m.items.forEach((e, i) => { - const s = (i * 48) - m.scroll + 50; - if(s < 30 || s > gh - 74){ + const s = (i * 48) - m.scroll + ar.y + 20; + if(s < ar.y || s > ar.y2 - 44){ return false; } if(i == m.selected){ - g.setColor(m.info.cHB).fillRect(0, s, gw, Math.min(s + 48, gh - 30)).setColor(m.info.cHF); + g.setColor(m.info.cHB).fillRect(ar.x, s, ar.x2, Math.min(s + 48, ar.y2)).setColor(m.info.cHF); }else{ g.setColor(m.info.cF); } - g.drawString(e.title, (e.icon ? 30 : 10), s + 5); + g.drawString(e.title, ar.x + (e.icon ? 30 : 10), s + 5); if(e.icon){ - g.drawImage(e.icon, 5, s + 5); + g.drawImage(e.icon, ar.x + 5, s + 5); } - if(e.type && s < gh - 72){ + if(e.type && s < ar.y2 - 42){ if(e.format){ - g.setFontAlign(1, -1, 0).drawString(e.format(e.value), gw - 10, s + 25).setFontAlign(-1, -1, 0); + g.setFontAlign(1, -1, 0).drawString(e.format(e.value), ar.x2 - 10, s + 25).setFontAlign(-1, -1, 0); }else{ - g.setFontAlign(1, -1, 0).drawString(e.value, gw - 10, s + 25).setFontAlign(-1, -1, 0); + g.setFontAlign(1, -1, 0).drawString(e.value, ar.x2 - 10, s + 25).setFontAlign(-1, -1, 0); } } }); - g.setColor(m.info.cAB).fillRect(0, 30, gw, 50); - g.setColor(m.info.cAF).drawString(m.info.title, (m.back ? 30 : 10), 32); + g.setColor(m.info.cAB).fillRect(ar.x, ar.y, ar.x2, ar.y + 20); + g.setColor(m.info.cAF).drawString(m.info.title, ar.x + (m.back ? 30 : 10), ar.y + 2); if(m.back){ - g.drawLine(5, 40, 20, 40); - g.drawLine(5, 40, 15, 33); - g.drawLine(5, 40, 15, 47); + g.drawLine(ar.x + 5, ar.y + 10, ar.x + 20, ar.y + 10); + g.drawLine(ar.x + 5, ar.y + 10, ar.x + 15, ar.y + 17); + g.drawLine(ar.x + 5, ar.y + 10, ar.x + 15, ar.y + 3); } m.info.preflip(g, m.scroll > 0, m.scroll < (m.items.length - 1) * 48); }, select: (x, y) => { - if(m.selected == -1 || m.selected !== Math.max(Math.min(Math.floor((y + m.scroll - 50) / 48), m.items.length - 1), 0)){ + if(m.selected == -1 || m.selected !== Math.max(Math.min(Math.floor((y + m.scroll - ar.y - 20) / 48), m.items.length - 1), 0)){ if(y){ - if(y < 50 || y > gh - 30){ + if(y < ar.y + 20 || y > ar.y2){ return false; }else{ - m.selected = Math.max(Math.min(Math.floor((y + m.scroll - 50) / 48), m.items.length - 1), 0); + m.selected = Math.max(Math.min(Math.floor((y + m.scroll - ar.y - 20) / 48), m.items.length - 1), 0); } }else{ m.selected = Math.floor(m.scroll / 48); @@ -76,7 +74,7 @@ E.showMenu = function(items) { m.items[m.selected].onchange(m.items[m.selected].value); m.draw(); }else if(m.items[m.selected].type && m.items[m.selected].type === "number"){ - if(x && x < (gw / 2)){ + if(x && x < ((ar.x + ar.x2) / 2)){ m.items[m.selected].value = m.items[m.selected].value - (m.items[m.selected].step ? m.items[m.selected].step : 1); }else{ m.items[m.selected].value = m.items[m.selected].value + (m.items[m.selected].step ? m.items[m.selected].step : 1); @@ -99,7 +97,7 @@ E.showMenu = function(items) { move: d => { m.scroll += (d * 48); m.scroll = Math.min(Math.max(m.scroll, 0), (m.items.length - 1) * 48); - m.selected = Math.max(Math.min(Math.floor((m.scroll - 50) / 48), m.items.length - 1), 0); + m.selected = Math.max(Math.min(Math.floor((m.scroll - ar.y - 20) / 48), m.items.length - 1), 0); m.draw(); }, }; @@ -127,7 +125,7 @@ E.showMenu = function(items) { return false; } if(d.dx == 0 && d.dy == 0){ - if(d.x < 30 && d.y < 50){ + if(d.x < ar.x + 30 && d.y < ar.y + 20){ m.back(); return false; } @@ -195,3 +193,9 @@ E.showPrompt = function (e, t){ E.showMenu(menu); }); }; + +const bsl = Bangle.showLauncher; +Bangle.showLauncher = function (){ + Bangle.removeAllListeners("drag"); + bsl(); +}; diff --git a/apps/touchtimer/metadata.json b/apps/touchtimer/metadata.json new file mode 100644 index 000000000..645a0ce18 --- /dev/null +++ b/apps/touchtimer/metadata.json @@ -0,0 +1,18 @@ +{ + "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"}] +} diff --git a/apps/trex/metadata.json b/apps/trex/metadata.json new file mode 100644 index 000000000..8344ba161 --- /dev/null +++ b/apps/trex/metadata.json @@ -0,0 +1,18 @@ +{ + "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}] +} diff --git a/apps/vectorclock/ChangeLog b/apps/vectorclock/ChangeLog index 8addc7170..f1fd5b134 100644 --- a/apps/vectorclock/ChangeLog +++ b/apps/vectorclock/ChangeLog @@ -1,3 +1,5 @@ 0.01: New watch face 0.02: Use Bangle.setUI for button/launcher handling 0.03: Bangle.js 2 support +0.04: Adds costumizable colours and the respective settings menu +0.05: "Chime the time" (buzz or beep) with up/down swipe added diff --git a/apps/vectorclock/app.js b/apps/vectorclock/app.js index 78c17b3d4..e99a13ed1 100644 --- a/apps/vectorclock/app.js +++ b/apps/vectorclock/app.js @@ -1,5 +1,10 @@ const is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; const locale = require("locale"); +var settings = require('Storage').readJSON("vectorclock.json", true) || {}; +var dowcol = settings.dowcol || g.theme.fg; +var timecol = settings.timecol || g.theme.fg; +var datecol = settings.datecol || g.theme.fg; +var chimetype = settings.chimetype; function padNum(n, l) { return ("0".repeat(l)+n).substr(-l); @@ -26,8 +31,8 @@ function executeCommands() { commands = []; } -function drawVectorText(text, size, x, y, alignX, alignY) { - g.setFont("Vector", size).setFontAlign(alignX, alignY).drawString(text, x, y); +function drawVectorText(text, size, x, y, alignX, alignY, color) { + g.setFont("Vector", size).setColor(color).setFontAlign(alignX, alignY).drawString(text, x, y); var m = g.stringMetrics(text); return { x1: x - m.width * (alignX / 2 + 0.5), @@ -39,7 +44,7 @@ function drawVectorText(text, size, x, y, alignX, alignY) { function draw() { g.reset(); - + let d = new Date(); let hours = is12Hour ? ((d.getHours() + 11) % 12) + 1 : d.getHours(); let timeText = `${hours}:${padNum(d.getMinutes(), 2)}`; @@ -53,27 +58,27 @@ function draw() { let timeFontSize = width / ((g.stringWidth(timeText) / 256) + (Math.max(g.stringWidth(meridian), g.stringWidth(secondsText)) / 512 * 9 / 10)); let dowFontSize = width / (g.stringWidth(dowText) / 256); let dateFontSize = width / (g.stringWidth(dateText) / 256); - + let timeHeight = g.setFont("Vector", timeFontSize).getFontHeight() * 9 / 10; let dowHeight = g.setFont("Vector", dowFontSize).getFontHeight(); let dateHeight = g.setFont("Vector", dateFontSize).getFontHeight(); - + let remainingHeight = g.getHeight() - 24 - timeHeight - dowHeight - dateHeight; let spacer = remainingHeight / 4; - + let x = 2; let y = 24 + spacer; - - pushCommand(drawVectorText, timeText, timeFontSize, x, y, -1, -1); - pushCommand(drawVectorText, meridian, timeFontSize*9/20, x + width, y, 1, -1); - if (showSeconds) pushCommand(drawVectorText, secondsText, timeFontSize*9/20, x + width, y + timeHeight, 1, 1); + + pushCommand(drawVectorText, timeText, timeFontSize, x, y, -1, -1, timecol); + pushCommand(drawVectorText, meridian, timeFontSize*9/20, x + width, y, 1, -1, timecol); + if (showSeconds) pushCommand(drawVectorText, secondsText, timeFontSize*9/20, x + width, y + timeHeight, 1, 1, timecol); y += timeHeight + spacer; - - pushCommand(drawVectorText, dowText, dowFontSize, x + width/2, y, 0, -1); + + pushCommand(drawVectorText, dowText, dowFontSize, x + width/2, y, 0, -1, dowcol); y += dowHeight + spacer; - - pushCommand(drawVectorText, dateText, dateFontSize, x + width/2, y, 0, -1); - + + pushCommand(drawVectorText, dateText, dateFontSize, x + width/2, y, 0, -1, datecol); + executeCommands(); } @@ -85,6 +90,57 @@ function tick() { timeout = setTimeout(tick, period - getTime() * 1000 % period); } +// ====================================== Vibration (taken from "Vibrate Clock") +// vibrate 0..9 +function vibrateDigitBuzz(num) { + if (num==0) return Bangle.buzz(500); + return new Promise(function f(resolve){ + if (num--<=0) return resolve(); + Bangle.buzz(100).then(()=>{ + setTimeout(()=>f(resolve), 200); + }); + }); +} +function vibrateDigitBeep(num) { + if (num==0) return Bangle.beep(500); + return new Promise(function f(resolve){ + if (num--<=0) return resolve(); + Bangle.beep(100).then(()=>{ + setTimeout(()=>f(resolve), 200); + }); + }); +} +// vibrate multiple digits (num must be a string) +function vibrateNumber(num) { + return new Promise(function f(resolve){ + if (!num.length) return resolve(); + var digit = num[0]; + num = num.substr(1); + if ("buzz"==chimetype) + vibrateDigitBuzz(digit).then(()=>{ + setTimeout(()=>f(resolve),500);}); + if ("beep"==chimetype) + vibrateDigitBeep(digit).then(()=>{ + setTimeout(()=>f(resolve),500);}); + }); +} +var vibrateBusy; +function vibrateTime() { + if (vibrateBusy) return; + vibrateBusy = true; + var d = new Date(); + var hours = d.getHours(), minutes = d.getMinutes(); + if (is12Hour) { + if (hours == 0) hours = 12; + else if (hours>12) hours -= 12; + } + vibrateNumber(hours.toString()). + then(() => new Promise(resolve=>setTimeout(resolve,500))). + then(() => vibrateNumber(minutes.toString())). + then(() => vibrateBusy=false); +} + + Bangle.on('lcdPower', function(on) { if (timeout) clearTimeout(timeout); timeout = null; @@ -98,6 +154,10 @@ Bangle.on('lock', function(locked) { tick(); }); +if ("buzz"==chimetype || "beep"==chimetype) + Bangle.on("swipe",function(direction){ + if (0==direction) vibrateTime();}); // 0 = swipe up/down + g.clear(); tick(); Bangle.loadWidgets(); diff --git a/apps/vectorclock/metadata.json b/apps/vectorclock/metadata.json new file mode 100644 index 000000000..63d2ca678 --- /dev/null +++ b/apps/vectorclock/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "vectorclock", + "name": "Vector Clock", + "version": "0.05", + "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}, + {"name":"vectorclock.settings.js","url":"settings.js"} + ], + "data": [{"name":"vectorclock.json"}] +} diff --git a/apps/vectorclock/settings.js b/apps/vectorclock/settings.js new file mode 100644 index 000000000..86cbad1e5 --- /dev/null +++ b/apps/vectorclock/settings.js @@ -0,0 +1,55 @@ +(function(back) { + var FILE = "vectorclock.json"; + // Load settings + var settings = Object.assign({ + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + var colnames = ["white", "yellow", "green", "cyan", "red", "orange", "magenta", "black"]; + var colvalues = [0xFFFF, 0xFFE0, 0x07E0, 0x07FF, 0xF800, 0xFD20, 0xF81F, 0x0000]; + var chimenames = ["off", "buzz", "beep"]; + // Show the menu + E.showMenu({ + "" : { "title" : "VectorClock settings" }, + 'Time': { + value: Math.max(0 | colvalues.indexOf(settings.timecol),0), + min: 0, max: colvalues.length-1, + format: v => colnames[v], + onchange: v => { + settings.timecol = colvalues[v]; + writeSettings(); + } + }, + 'Weekday': { + value: Math.max(0 | colvalues.indexOf(settings.dowcol),0), + min: 0, max: colvalues.length-1, + format: v => colnames[v], + onchange: v => { + settings.dowcol = colvalues[v]; + writeSettings(); + } + }, + 'Date': { + value: Math.max(0 | colvalues.indexOf(settings.datecol),0), + min: 0, max: colvalues.length-1, + format: v => colnames[v], + onchange: v => { + settings.datecol = colvalues[v]; + writeSettings(); + } + }, + 'Chimetype': { + value: Math.max(0 | chimenames.indexOf(settings.chimetype),0), + min: 0, max: chimenames.length-1, + format: v => chimenames[v], + onchange: v => { + settings.chimetype = chimenames[v]; + writeSettings(); + } + }, + "< Back" : () => back(), + }); +}) diff --git a/apps/vernierrespirate/Vernier-go-direct-respiration-belt-screenshot.png b/apps/vernierrespirate/Vernier-go-direct-respiration-belt-screenshot.png new file mode 100644 index 000000000..f5d095582 Binary files /dev/null and b/apps/vernierrespirate/Vernier-go-direct-respiration-belt-screenshot.png differ diff --git a/apps/vernierrespirate/metadata.json b/apps/vernierrespirate/metadata.json new file mode 100644 index 000000000..5e2baf2bb --- /dev/null +++ b/apps/vernierrespirate/metadata.json @@ -0,0 +1,17 @@ +{ + "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", + "screenshots": [{"url":"Vernier-go-direct-respiration-belt-screenshot.png"}], + "storage": [ + {"name":"vernierrespirate.app.js","url":"app.js"}, + {"name":"vernierrespirate.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"vernierrespirate.json"}] +} diff --git a/apps/verticalface/metadata.json b/apps/verticalface/metadata.json new file mode 100644 index 000000000..da41b3f0d --- /dev/null +++ b/apps/verticalface/metadata.json @@ -0,0 +1,17 @@ +{ + "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} + ] +} diff --git a/apps/vibrclock/metadata.json b/apps/vibrclock/metadata.json new file mode 100644 index 000000000..d05553960 --- /dev/null +++ b/apps/vibrclock/metadata.json @@ -0,0 +1,16 @@ +{ + "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} + ] +} diff --git a/apps/viewstl/metadata.json b/apps/viewstl/metadata.json new file mode 100644 index 000000000..8d1cc3f98 --- /dev/null +++ b/apps/viewstl/metadata.json @@ -0,0 +1,18 @@ +{ + "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"} + ] +} diff --git a/apps/walkersclock/metadata.json b/apps/walkersclock/metadata.json new file mode 100644 index 000000000..3d93cffdf --- /dev/null +++ b/apps/walkersclock/metadata.json @@ -0,0 +1,16 @@ +{ + "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} + ] +} diff --git a/apps/waveclk/metadata.json b/apps/waveclk/metadata.json new file mode 100644 index 000000000..503c9c11c --- /dev/null +++ b/apps/waveclk/metadata.json @@ -0,0 +1,16 @@ +{ + "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} + ] +} diff --git a/apps/waypointer/metadata.json b/apps/waypointer/metadata.json new file mode 100644 index 000000000..cb477107b --- /dev/null +++ b/apps/waypointer/metadata.json @@ -0,0 +1,16 @@ +{ + "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"}] +} diff --git a/apps/wclock/metadata.json b/apps/wclock/metadata.json new file mode 100644 index 000000000..f22b53dc1 --- /dev/null +++ b/apps/wclock/metadata.json @@ -0,0 +1,16 @@ +{ + "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} + ] +} diff --git a/apps/weather/metadata.json b/apps/weather/metadata.json new file mode 100644 index 000000000..1d0b6b469 --- /dev/null +++ b/apps/weather/metadata.json @@ -0,0 +1,19 @@ +{ + "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"}] +} diff --git a/apps/weatherClock/metadata.json b/apps/weatherClock/metadata.json new file mode 100644 index 000000000..cf8bd899e --- /dev/null +++ b/apps/weatherClock/metadata.json @@ -0,0 +1,17 @@ +{ + "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} + ] +} diff --git a/apps/welcome/metadata.json b/apps/welcome/metadata.json new file mode 100644 index 000000000..b132c2d51 --- /dev/null +++ b/apps/welcome/metadata.json @@ -0,0 +1,20 @@ +{ + "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"}] +} diff --git a/apps/whereworld/metadata.json b/apps/whereworld/metadata.json new file mode 100644 index 000000000..1106a3b39 --- /dev/null +++ b/apps/whereworld/metadata.json @@ -0,0 +1,15 @@ +{ + "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"} + ] +} diff --git a/apps/widChargingStatus/ChangeLog b/apps/widChargingStatus/ChangeLog index d3175e1ab..1033c0cd3 100644 --- a/apps/widChargingStatus/ChangeLog +++ b/apps/widChargingStatus/ChangeLog @@ -1 +1 @@ -0.1: First release. \ No newline at end of file +0.01: First release. diff --git a/apps/widChargingStatus/metadata.json b/apps/widChargingStatus/metadata.json new file mode 100644 index 000000000..f68ccf5b4 --- /dev/null +++ b/apps/widChargingStatus/metadata.json @@ -0,0 +1,13 @@ +{ "id": "widChargingStatus", + "name": "Charging Status", + "shortName":"ChargingStatus", + "icon": "widget.png", + "version":"0.01", + "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"} + ] +} diff --git a/apps/wid_a_battery_widget/ChangeLog b/apps/wid_a_battery_widget/ChangeLog index 8a1538479..a111d259a 100644 --- a/apps/wid_a_battery_widget/ChangeLog +++ b/apps/wid_a_battery_widget/ChangeLog @@ -1,4 +1,4 @@ -1.00: Release for Bangle 2 (2021/11/18) -1.01: Internal id update to wid_* as per Gordon's request (2021/11/21) -1.02: Support dark themes -1.03: Increase screen update rate when charging +0.01: Release for Bangle 2 (2021/11/18) +0.02: Internal id update to wid_* as per Gordon's request (2021/11/21) +0.03: Support dark themes +0.04: Increase screen update rate when charging diff --git a/apps/wid_a_battery_widget/metadata.json b/apps/wid_a_battery_widget/metadata.json new file mode 100644 index 000000000..6c507b7b3 --- /dev/null +++ b/apps/wid_a_battery_widget/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "wid_a_battery_widget", + "name": "A Battery Widget (with percentage)", + "shortName":"A Battery Widget", + "icon": "widget.png", + "version":"0.04", + "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"} + ] +} diff --git a/apps/wid_edit/ChangeLog b/apps/wid_edit/ChangeLog new file mode 100644 index 000000000..2fa857bd8 --- /dev/null +++ b/apps/wid_edit/ChangeLog @@ -0,0 +1 @@ +0.01: new Widget Editor! \ No newline at end of file diff --git a/apps/wid_edit/README.md b/apps/wid_edit/README.md new file mode 100644 index 000000000..e5003280c --- /dev/null +++ b/apps/wid_edit/README.md @@ -0,0 +1,16 @@ +# Widget Editor + +This adds a setting menu which allows you to change the location of widgets. + +## Settings + +There is no app icon in the launcher; you can find the settings under +`Apps`->`Widget Editor`. + +For every widget, you have these options: +* **Side**: On which side to draw the widget. +* **Sort Order**: Changes the order if several widgets use the same side. + +## Creator + +Richard de Boer diff --git a/apps/wid_edit/boot.js b/apps/wid_edit/boot.js new file mode 100644 index 000000000..872965c97 --- /dev/null +++ b/apps/wid_edit/boot.js @@ -0,0 +1,24 @@ +Bangle.loadWidgets = function() { + global.WIDGETS={}; + require("Storage").list(/\.wid\.js$/) + .forEach(w=>{ + try { eval(require("Storage").read(w)); } + catch (e) { print(w, e); } + }); + const s = require("Storage").readJSON("wid_edit.json", 1) || {}, + c = s.custom || {}; + for (const w in c){ + if (!(w in WIDGETS)) continue; // widget no longer exists + // store defaults of customized values in _WIDGETS + global._WIDGETS=global._WIDGETS||{}; + _WIDGETS[w] = {}; + Object.keys(c[w]).forEach(k => _WIDGETS[w][k] = WIDGETS[w][k]); + Object.assign(WIDGETS[w], c[w]); + } + const W = WIDGETS; + WIDGETS = {}; + Object.keys(W) + .sort() + .sort((a, b) => (0|W[b].sortorder)-(0|W[a].sortorder)) + .forEach(k => WIDGETS[k] = W[k]); +} \ No newline at end of file diff --git a/apps/wid_edit/icon.png b/apps/wid_edit/icon.png new file mode 100644 index 000000000..1d072c381 Binary files /dev/null and b/apps/wid_edit/icon.png differ diff --git a/apps/wid_edit/metadata.json b/apps/wid_edit/metadata.json new file mode 100644 index 000000000..66d0192f6 --- /dev/null +++ b/apps/wid_edit/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "wid_edit", + "version": "0.01", + "name": "Widget Editor", + "icon": "icon.png", + "description": "Customize widget locations", + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "type": "bootloader", + "tags": "widget,tool", + "storage": [ + {"name":"wid_edit.boot.js","url":"boot.js"}, + {"name":"wid_edit.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"wid_edit.json"} + ] +} diff --git a/apps/wid_edit/settings.js b/apps/wid_edit/settings.js new file mode 100644 index 000000000..0969ed533 --- /dev/null +++ b/apps/wid_edit/settings.js @@ -0,0 +1,190 @@ +/** + * @param {function} back Use back() to return to settings menu + */ +(function(back) { + const names = {}; + const settings = require("Storage").readJSON("wid_edit.json", 1) || {}; + if (!('custom' in settings)) settings.custom = {}; + global._WIDGETS = global._WIDGETS || {}; + + let cleanup = false; + for (const id in settings.custom) { + if (!(id in WIDGETS)) { + // widget which no longer exists + cleanup = true; + delete settings.custom[id]; + } + } + if (cleanup) { + if (!Object.keys(settings.custom).length) delete settings.custom; + require("Storage").writeJSON("wid_edit.json", settings); + } + + /** + * Sort & redraw all widgets + */ + function redrawWidgets() { + let W = WIDGETS; + global.WIDGETS = {}; + Object.keys(W) + .sort() + .sort((a, b) => (0|W[b].sortorder)-(0|W[a].sortorder)) + .forEach(k => {WIDGETS[k] = W[k]}); + Bangle.drawWidgets(); + } + + /** + * Try to find app name for widget + * @param {string} widget WIDGETS key + * @return {string} widget name + */ + function name(widget) { + if (!(widget in names)) { + let infoFile = widget+".info"; + // widget names don't always correspond to appid :-( + // so we try both with and without 'wid'-prefix + if (!require("Storage").list(new RegExp(`^${infoFile}$`)).length) { + infoFile = (widget.substr(0, 3)==="wid") ? infoFile.substr(3) : ("wid"+infoFile); + } + names[widget] = (require("Storage").readJSON(infoFile, 1) || {}).name || widget; + } + return names[widget]; + } + + function edit(id) { + let WIDGET = WIDGETS[id], + def = {area: WIDGET.area, sortorder: WIDGET.sortorder|0}; // default values + Object.assign(def, _WIDGETS[id]||{}); // defaults were saved in _WIDGETS + + settings.custom = settings.custom||{}; + let saved = settings.custom[id] || {}, + area = saved.area || def.area, + sortorder = ("sortorder" in saved) ? saved.sortorder : def.sortorder; + + /** + * Draw highlighted widget + */ + function highlight() { + if (WIDGET.width > 0) { + // draw widget, then draw a highlighted border on top + WIDGET.draw(); + g.setColor(g.theme.fgH) + .drawRect(WIDGET.x, WIDGET.y, WIDGET.x+WIDGET.width-1, WIDGET.y+23); + } else { + // hidden widget: fake a width and provide our own draw() + const draw = WIDGET.draw, width = WIDGET.width; + WIDGET.width = 24; + WIDGET.draw = function() { + g.setColor(g.theme.bgH).setColor(g.theme.fgH) + .clearRect(this.x, this.y, this.x+23, this.y+23) + .drawRect(this.x, this.y, this.x+23, this.y+23) + .drawLine(this.x, this.y, this.x+23, this.y+23) + .drawLine(this.x, this.y+23, this.x+23, this.y); + }; + // re-layout+draw all widgets with our placeholder in between + redrawWidgets(); + // and restore original values + WIDGET.draw = draw; + WIDGET.width = width; + } + } + highlight(); + + /** + * Save widget and redraw with new settings + */ + function save() { + // we only save non-default values + saved = {}; + if ((area!==def.area) || (sortorder!==def.sortorder)) { + if (area!==def.area) saved.area = area; + if (sortorder!==def.sortorder) saved.sortorder = sortorder; + settings.custom = settings.custom || {}; + settings.custom[id] = saved; + } else if (settings.custom) { + delete settings.custom[id] + } + if (!Object.keys(settings.custom).length) delete settings.custom; + require("Storage").writeJSON("wid_edit.json", settings); + Object.assign(WIDGET, def, saved); + if (WIDGET.sortorder === undefined) delete WIDGET.sortorder; // default can be undefined, but don't put that in the widget + // if we assigned custom values, store defaults in _WIDGETS + let _W = {}; + if (saved.area) _W.area = def.area; + if ('sortorder' in saved) _W.sortorder = def.sortorder; + if (Object.keys(_W).length) _WIDGETS[id] = _W; + else delete _WIDGETS[id]; + + // drawWidgets won't clear e.g. bottom bar if we just disabled the last bottom widget + redrawWidgets(); + + highlight(); + m.draw(); + } + + const menu = { + "": {"title": name(id)}, + /*LANG*/"< Back": () => { + redrawWidgets(); + mainMenu(); + }, + /*LANG*/"Side": { + value: (area === 'tl'), + format: tl => tl ? /*LANG*/"Left" : /*LANG*/"Right", + onchange: tl => { + area = tl ? "tl" : "tr"; + save(); + } + }, + /*LANG*/"Sort Order": { + value: sortorder, + onchange: o => { + sortorder = o; + save(); + } + }, + /*LANG*/"Reset": () => { + area = def.area; + sortorder = def.sortorder; + save(); + mainMenu(); // changing multiple values made the rest of the menu wrong, so take the easy out + } + } + + let m = E.showMenu(menu); + } + + + function mainMenu() { + let menu = { + "": {"title": /*LANG*/"Widgets"}, + }; + menu[/*LANG*/"< Back"] = ()=>{ + if (!Object.keys(_WIDGETS).length) delete _WIDGETS; // no defaults to remember + back(); + }; + Object.keys(WIDGETS).forEach(id=>{ + // mark customized widgets with asterisk + menu[name(id)+((id in _WIDGETS) ? " *" : "")] = () => edit(id); + }); + if (Object.keys(_WIDGETS).length) { // only show reset if there is anything to reset + menu[/*LANG*/"Reset All"] = () => { + E.showPrompt(/*LANG*/"Reset all widgets?").then(confirm => { + if (confirm) { + delete settings.custom; + require("Storage").writeJSON("wid_edit.json", settings); + for(let id in _WIDGETS) { + Object.assign(WIDGETS[id], _WIDGETS[id]) // restore defaults + } + global._WIDGETS = {}; + redrawWidgets(); + } + mainMenu(); // reload with reset widgets + }) + } + } + + E.showMenu(menu); + } + mainMenu(); +}); diff --git a/apps/widancs/ChangeLog b/apps/widancs/ChangeLog deleted file mode 100644 index 471507736..000000000 --- a/apps/widancs/ChangeLog +++ /dev/null @@ -1,7 +0,0 @@ -0.01: New Widget! -0.02: Version using connect back -0.03: Version using modified firmware -0.04: Works on both standard and modified firmware -0.05: Bug fixes w.r.t. reconnection -0.06: Update README - Release version -0.07: Respect Quiet Mode diff --git a/apps/widancs/README.md b/apps/widancs/README.md deleted file mode 100644 index d3ee0bdc4..000000000 --- a/apps/widancs/README.md +++ /dev/null @@ -1,70 +0,0 @@ -## ANCS - iPhone notifications for Bangle.js - -The ANCS widget allows you to answer or cancel iPhone incoming calls and also displays messages and notifications. It connects to the Apple Notification Center Service which is already on all iPhones, so you do not need to install any additional iPhone apps to use this widget. - -## Firmware -The widget will run on the standard firmware, however, installation of a slightly modified version - the zip file is available from [this directory](https://github.com/jeffmer/JeffsBangleAppsDev/tree/master/apps/widancs) - will increase the performance of the app by an order of magnitude in terms of the time to connect or reconnect to the iPhone. In addition, the Bangle will stay connected to the iPhone over a greater separation distance than with the standard firmware. - - -![](widget_pic.jpg) - -## Installation - -After the widget is uploaded to the Bangle, it needs to be enabled in the Bangle Settings app:- `ANCS Widget` will appear in `APP/Widget settings`. There is also a menu in these settings to let you configure the categories of notifications that you want to be displayed. You must disconnect from the App Loader before enabling the widget. - -## Compatible Apps - -The widget will only run with a compatible app - for the reason for this see Issue 1 below. The apps that are compatible with the ANCS widget are:- **Multi Clock**, **Navigation Compass** and **GPS Navigation**. When you switch to an app that is not compatible, the ANCS phone icon will not appear. - -## iPhone Pairing -Once enabled, the widget icon should be displayed coloured grey (its green in the photo). Go to the phone's Bluetooth settings menu and your Bangle should appear under Other devices. If this is the first time you have connected with the Bangle from your iPhone, it may be named Accessory. Click on the name and the iPhone should connect and start pairing. The widget icon will turn red and the iPhone will ask you to enter a pairing code - the traditional 123456. You have 10 seconds to enter this after which you will need to start pairing again. After that, the iPhone may also ask to allow the device access to ANCS. Once pairing is complete, the widget icon should go blue and eventually green. The range of colours is: - -* **Grey** - not connected - advertising -* **Red** - connected - not paired. -* **Blue** - paired and connected - getting services -* **Yellow** - got Services. -* **Green** - waiting for new notifications. - -After pairing the first time, the Bangle should connect automatically when the widget is running. Sometimes you may need you to click on the Bangle name in `Settings:Bluetooth:My devices` on the iPhone or disable and then enable Bluetooth to start connection. If you need to load other apps from the iPhone, it will be necessary to ask the iPhone to forget the pairing and you will also need to disable the widget in Settings and restart the Bangle by turning it off in Settings and then pressing BTN1 to restart. If you are loading apps from a different device, you simply need to turn off the iPhone bluetooth which will retain the pairing. You still need to disable the widget and restart the Bangle. - -![](message_pic.jpg) - -## Messages & Calls -Messages are displayed as shown above until BTN2 is pressed to dismiss it. I strongly advise disabling the BTN2 LCD wake function in the Settings App as otherwise when the screen times out and you press BTN2 to wake the LCD, the screen will turn on and the Message Alert will be dismissed!. Calls can be answered or dropped. - -![](call_pic.jpg) ![](missed_pic.jpg) - - -## Issues -1. With GadgetBridge, the Android phone has a Central-Client role with the Bangle as Peripheral-Server. With the ANCS widget there is the fairly unusual situation in which the Bangle is Peripheral-Client to the iPhone's Central-Server role. Since Espruino does not deal explicitly with Bangle as Peripheral-Client an additional function has been added in the modified firmware: `var gatt = NRF.getGattforCentralServer(addr);`. This returns a bluetooth remote GATT server given the address of the iPhone which has just connected to the Bangle. With the standard firmware, the widget reconnects to the iPhone as a Client - however this has greatly degraded performance. See [Issue 1800.](https://github.com/espruino/Espruino/issues/1800) for more details. - -2. When the Bangle switches apps, all state - including widget state - is lost unless explicitly stored. The consequence of this is that when the Bangle switches apps, the connection to iPhone has to be re-established to restore the remote GATT server and characteristics state. This is quite slow. To minimise reconnection, the widget needs to grab the screen from the running app to signal messages and calls. To allow this to work, the app needs to implement the `SCREENACCESS` interface. In essence, the widget only connects when running with compatible apps that implement this interface. An example implementation is: - -``` -var SCREENACCESS = { - withApp:true, - request:function(){ - this.withApp=false; - stopdraw(); //clears redraw timers etc - clearWatch(); //clears button handlers - }, - release:function(){ - this.withApp=true; - startdraw(); //redraw app screen, restart timers etc - setButtons(); //install button event handlers - } -} - -Bangle.on('lcdPower',function(on) { - if (!SCREENACCESS.withApp) return; - if (on) { - startdraw(); - } else { - stopdraw(); - } -}); -``` - -## Support - -Please report bugs etc. by raising an issue [here](https://github.com/jeffmer/JeffsBangleAppsDev). \ No newline at end of file diff --git a/apps/widancs/ancs.js b/apps/widancs/ancs.js deleted file mode 100644 index 50720cd23..000000000 --- a/apps/widancs/ancs.js +++ /dev/null @@ -1,266 +0,0 @@ -(() => { - - var s = require("Storage").readJSON("widancs.json",1)||{settings:{enabled:false, category:[1,2,4]}}; - var ENABLED = s.settings.enabled; - var CATEGORY = s.settings.category; - - function advert(){ - NRF.setAdvertising([ - 0x02, //length - 0x01, //flags - 0x06, // - 0x11, //length - 0x15, //solicited Service UUID - 0xD0,0x00,0x2D,0x12,0x1E,0x4B, - 0x0F,0xA4, - 0x99,0x4E, - 0xCE,0xB5, - 0x31,0xF4,0x05,0x79],{connectable:true,discoverable:true,interval:375}); - } - - var state = { - gatt:null, - ancs:null, - current:{cat:0,uid:0}, - notqueue:[], - msgTO:undefined, - com:new Uint8Array([0,0,0,0,0,1,20,0,3,100,0]), - buf:new Uint8Array(132), - inp:0, - store:function(b){ - var i = this.inp; - if (i+b.length<=132){ - this.buf.set(b,i); - this.inp+=b.length; - } - }, - gotmsg:function(){ - var n = this.inp; - var vw = DataView(this.buf.buffer); - if (n<8) return null; - var tn=vw.getUint16(6,true); - if (n<(tn+8)) return null; - var mn=vw.getUint16(9+tn,true); - if (n<(mn+tn+11)) return null; - return {tlen:tn, mlen:mn}; - } - }; - - //stop advertising when peripheral link disconnected - if (!NRF.getGattforCentralServer && ENABLED && typeof SCREENACCESS!='undefined') - NRF.on('disconnect',function(reason){ - NRF.sleep(); - }); - - if (ENABLED && typeof SCREENACCESS!='undefined') - NRF.on('connect',function(addr){ - if(NRF.getGattforCentralServer) - do_bond(NRF.getGattforCentralServer(addr)); - else - NRF.connect(addr).then(do_bond); - }); - - function do_bond(g) { - var tval, ival; - state.gatt = g; - function cleanup(){ - drawIcon(0); //disconnect from iPhone - delete state.gatt; - delete state.ancs; - if(!NRF.getGattforCentralServer) NRF.disconnect(); - setTimeout(()=>{NRF.wake();},500); - } - drawIcon(1); //connect from iPhone - state.gatt.device.on('gattserverdisconnected', function(reason) { - if (ival) clearInterval(ival); - if (tval) clearInterval(tval); - cleanup(); - }); - E.on("kill",function(){ - state.gatt.disconnect().then(function(){NRF.sleep();}); - }); - NRF.setSecurity({passkey:"123456",mitm:1,display:1}); - tval = setTimeout(function(){ - if (ival) clearInterval(ival); - state.gatt.disconnect().then(cleanup); - },10000); - state.gatt.startBonding().then(function(){ - ival = setInterval(function(){ - var sec = state.gatt.getSecurityStatus(); - if (!sec.connected) {clearInterval(ival); clearTimeout(tval); return;} - if (sec.connected && sec.encrypted){ - clearInterval(ival); - clearTimeout(tval); - drawIcon(2); //bonded to iPhone - do_ancs(); - return; - } - },1000); - }).catch(function(e){ - Terminal.println("ERROR "+e); - }); - } - - function do_ancs() { - state.ancs = {primary:null, notify:null, control:null, data:null}; - state.gatt.getPrimaryService("7905F431-B5CE-4E99-A40F-4B1E122D00D0").then(function(s) { - state.ancs.primary=s; - return s.getCharacteristic("9FBF120D-6301-42D9-8C58-25E699A21DBD"); - }).then(function(c) { - state.ancs.notify=c; - return state.ancs.primary.getCharacteristic("69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9"); - }).then(function(c) { - state.ancs.control=c; - return state.ancs.primary.getCharacteristic("22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB"); - }).then(function(c) { - state.ancs.data =c; - drawIcon(3);//got remote services - state.ancs.notify.on('characteristicvaluechanged', function(ev) { - getnotify(ev.target.value); - }); - state.ancs.data.on('characteristicvaluechanged', function(e) { - state.store(e.target.value.buffer); - var inds = state.gotmsg(); - if (inds) printmsg(state.buf,inds); - }); - state.ancs.notify.startNotifications().then(function(){ - state.ancs.data.startNotifications().then(function(){ - drawIcon(4); //ready for messages - }); - }); - }).catch(function(e){ - Terminal.println("ERROR "+e); - }); - } - - function wordwrap(s){ - var txt = s.split("\n"); - var MAXCHARS = 18; - for (var i = 0; i < txt.length; i++) { - txt[i] = txt[i].trim(); - var l = txt[i]; - if (l.length > MAXCHARS) { - var p = MAXCHARS; - while (p > MAXCHARS - 8 && !" \t-_".includes(l[p])) - p--; - if (p == MAXCHARS - 8) p = MAXCHARS; - txt[i] = l.substr(0, p); - txt.splice(i + 1, 0, l.substr(p)); - } - } - return txt.join("\n"); - } - - - var buzzing =false; - var screentimeout = undefined; - var inalert = false; - - function release_screen(){ - screentimeout= setTimeout(() => { - SCREENACCESS.release(); - screentimeout = undefined; - inalert=false; - next_notify(); - }, 500); - } - - function printmsg(buf,inds){ - - function send_action(tf){ - var bb = new Uint8Array(6); - var v = DataView(bb.buffer); - v.setUint8(0,2); - v.setUint32(1,state.current.uid,true); - v.setUint8(5,tf?0:1 ); - state.ancs.control.writeValue(bb).then(release_screen); - } - - if (state.msgTO) clearTimeout(state.msgTO); - var title=""; - for (var i=8;i<8+inds.tlen; ++i) title+=String.fromCharCode(buf[i]); - var message = ""; - for (var j=11+inds.tlen;j<11+inds.tlen+inds.mlen;++j) { - message+=String.fromCharCode(buf[j]); - } - message = wordwrap(message); - //we may already be displaying a prompt, so clear it - E.showPrompt(); - if (screentimeout) clearTimeout(screentimeout); - if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) { - Bangle.setLCDPower(true); - } - SCREENACCESS.request(); - if (!buzzing && !(require('Storage').readJSON('setting.json',1)||{}).quiet){ - buzzing=true; - Bangle.buzz(500).then(()=>{buzzing=false;}); - } - if (state.current.cat!=1){ - E.showAlert(message,title).then(send_action.bind(null,false)); - } else { - E.showPrompt(message,{title:title,buttons:{"Accept":true,"Cancel":false}}).then(send_action); - } - } - - var notifyTO; - function getnotify(d){ - var eid = d.getUint8(0); - var ct = d.getUint8(2); - var id = d.getUint32(4,true); - if (eid>1) return; - if (notifyTO) clearTimeout(notifyTO); - if(!CATEGORY.includes(ct)) return; - var len = state.notqueue.length; - if (ct == 1) { // it's a call so pre-empt - if (inalert) {state.notqueue.push(state.current); inalert=false;} - state.notqueue.push({cat:ct, uid:id}); - } else if (len<16) - state.notqueue[len] = {cat:ct, uid:id}; - notifyTO = setTimeout(next_notify,1000); - } - - function next_notify(){ - if(state.notqueue.length==0 || inalert) return; - inalert=true; - state.current = state.notqueue.pop(); - var v = DataView(state.com.buffer); - if (state.current.cat==6) v.setUint8(8,2); else v.setUint8(8,3);//get email title - v.setUint32(1,state.current.uid,true); - state.inp=0; - state.ancs.control.writeValue(state.com).then(function(){ - state.msgTO=setTimeout(()=>{ - inalert=false; - state.msgTO=undefined; - next_notify(); - },1000); - }); - } - - var stage = 5; - //grey, pink, lightblue, yellow, green - function draw(){ - var colors = new Uint16Array([0xc618,0xf818,0x3ff,0xffe0,0x07e0,0x0000]); - var img = E.toArrayBuffer(atob("GBgBAAAABAAADgAAHwAAPwAAf4AAP4AAP4AAP4AAHwAAH4AAD8AAB+AAA/AAAfgAAf3gAH/4AD/8AB/+AA/8AAf4AAHwAAAgAAAA")); - g.setColor(colors[stage]); - g.drawImage(img,this.x,this.y); - } - - WIDGETS["ancs"] ={area:"tl", width:24,draw:draw}; - - function drawIcon(id){ - stage = id; - WIDGETS["ancs"].draw(); - } - - if (ENABLED && typeof SCREENACCESS!='undefined') { - stage = 0; - NRF.setServices(undefined,{uart:false}); - NRF.sleep(); - NRF.wake(); - advert(); - } - - })(); - - - \ No newline at end of file diff --git a/apps/widancs/ancs.min.js b/apps/widancs/ancs.min.js deleted file mode 100644 index 8ccf58e61..000000000 --- a/apps/widancs/ancs.min.js +++ /dev/null @@ -1,10 +0,0 @@ -(function(){function t(a){function e(){k(0);delete b.gatt;delete b.ancs;NRF.getGattforCentralServer||NRF.disconnect();setTimeout(function(){NRF.wake()},500)}var d;b.gatt=a;k(1);b.gatt.device.on("gattserverdisconnected",function(a){d&&clearInterval(d);c&&clearInterval(c);e()});E.on("kill",function(){b.gatt.disconnect().then(function(){NRF.sleep()})});NRF.setSecurity({passkey:"123456",mitm:1,display:1});var c=setTimeout(function(){d&&clearInterval(d);b.gatt.disconnect().then(e)},1E4);b.gatt.startBonding().then(function(){d= - setInterval(function(){var a=b.gatt.getSecurityStatus();a.connected?a.connected&&a.encrypted&&(clearInterval(d),clearTimeout(c),k(2),v()):(clearInterval(d),clearTimeout(c))},1E3)})["catch"](function(a){Terminal.println("ERROR "+a)})}function v(){b.ancs={primary:null,notify:null,control:null,data:null};b.gatt.getPrimaryService("7905F431-B5CE-4E99-A40F-4B1E122D00D0").then(function(a){b.ancs.primary=a;return a.getCharacteristic("9FBF120D-6301-42D9-8C58-25E699A21DBD")}).then(function(a){b.ancs.notify= - a;return b.ancs.primary.getCharacteristic("69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9")}).then(function(a){b.ancs.control=a;return b.ancs.primary.getCharacteristic("22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB")}).then(function(a){b.ancs.data=a;k(3);b.ancs.notify.on("characteristicvaluechanged",function(a){var e=a.target.value,c=e.getUint8(0);a=e.getUint8(2);e=e.getUint32(4,!0);1c&&(b.notqueue[c]={cat:a,uid:e}),m=setTimeout(n,1E3)))});b.ancs.data.on("characteristicvaluechanged",function(a){b.store(a.target.value.buffer);(a=b.gotmsg())&&x(b.buf,a)});b.ancs.notify.startNotifications().then(function(){b.ancs.data.startNotifications().then(function(){k(4)})})})["catch"](function(a){Terminal.println("ERROR "+a)})}function y(a){a=a.split("\n");for(var b=0;b=b+a.length&&(this.buf.set(a,b),this.inp+=a.length)},gotmsg:function(){var a=this.inp,b= - DataView(this.buf.buffer);if(8>a)return null;var d=b.getUint16(6,!0);if(a{return v!=n;}); - } - const menu = { - '': { 'title': 'Set Categories' } - }; - for (var i=0; iv?'Yes':'No', - onchange:setcat.bind(null,i) - }; - menu['< Back'] = ()=>{save(); showMain();}; - return E.showMenu(menu); - } - - function showMain(){ - return E.showMenu({ - 'Enable ANCS': { - value: s.enabled, - format: () => (s.enabled ? 'Yes' : 'No'), - onchange: () => { - s.enabled = !s.enabled; - save(); - }, - }, - 'Set Category':setcategory, - '< Back': back, - }); - } - - showMain(); -}); \ No newline at end of file diff --git a/apps/widancs/widget.png b/apps/widancs/widget.png deleted file mode 100644 index c6f57cc1e..000000000 Binary files a/apps/widancs/widget.png and /dev/null differ diff --git a/apps/widancs/widget_pic.jpg b/apps/widancs/widget_pic.jpg deleted file mode 100644 index 68b04f2e9..000000000 Binary files a/apps/widancs/widget_pic.jpg and /dev/null differ diff --git a/apps/widbars/metadata.json b/apps/widbars/metadata.json new file mode 100644 index 000000000..e8d52c90a --- /dev/null +++ b/apps/widbars/metadata.json @@ -0,0 +1,15 @@ +{ + "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"} +] +} diff --git a/apps/widbat/metadata.json b/apps/widbat/metadata.json new file mode 100644 index 000000000..0f040396f --- /dev/null +++ b/apps/widbat/metadata.json @@ -0,0 +1,13 @@ +{ + "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"} + ] +} diff --git a/apps/widbata/ChangeLog b/apps/widbata/ChangeLog index 51575d3b4..f23705341 100644 --- a/apps/widbata/ChangeLog +++ b/apps/widbata/ChangeLog @@ -1 +1,3 @@ 0.01: Created +0.02: Set sort order to -10 so always display in right hand corner +0.03: Set sort order from the code diff --git a/apps/widbata/metadata.json b/apps/widbata/metadata.json new file mode 100644 index 000000000..d8149d13f --- /dev/null +++ b/apps/widbata/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "widbata", + "name": "Battery Level Widget (Themed)", + "shortName":"Battery Theme", + "icon": "widbata.png", + "screenshots": [{"url":"screenshot_widbata_1.png"}], + "version":"0.03", + "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"} + ] +} diff --git a/apps/widbata/widbata.wid.js b/apps/widbata/widbata.wid.js index 1c04bf8ae..1de4c2fe9 100644 --- a/apps/widbata/widbata.wid.js +++ b/apps/widbata/widbata.wid.js @@ -2,7 +2,7 @@ setInterval(()=>WIDGETS["bata"].draw(), 60000); Bangle.on('lcdPower', function(on) { if (on) WIDGETS["bata"].draw(); }); -WIDGETS["bata"]={area:"tr",width:27,draw:function() { +WIDGETS["bata"]={area:"tr",sortorder:-10,width:27,draw:function() { var s = 26; var t = 13; // thickness var x = this.x, y = this.y; diff --git a/apps/widbatpc/metadata.json b/apps/widbatpc/metadata.json new file mode 100644 index 000000000..7da4e3e0c --- /dev/null +++ b/apps/widbatpc/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "widbatpc", + "name": "Battery Level Widget (with percentage)", + "shortName": "Battery Widget", + "version": "0.16", + "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", + "screenshots": [{"url":"widbatpc.full.jpg"},{"url":"widbatpc.part.jpg"}], + "storage": [ + {"name":"widbatpc.wid.js","url":"widget.js"}, + {"name":"widbatpc.settings.js","url":"settings.js"} + ], + "data": [{"name":"widbatpc.json"}] +} diff --git a/apps/widbatv/metadata.json b/apps/widbatv/metadata.json new file mode 100644 index 000000000..37cf6197b --- /dev/null +++ b/apps/widbatv/metadata.json @@ -0,0 +1,13 @@ +{ + "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"} + ] +} diff --git a/apps/widbatwarn/metadata.json b/apps/widbatwarn/metadata.json new file mode 100644 index 000000000..959eeca08 --- /dev/null +++ b/apps/widbatwarn/metadata.json @@ -0,0 +1,19 @@ +{ + "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"}] +} diff --git a/apps/widbt/metadata.json b/apps/widbt/metadata.json new file mode 100644 index 000000000..e2d5082a5 --- /dev/null +++ b/apps/widbt/metadata.json @@ -0,0 +1,13 @@ +{ + "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"} + ] +} diff --git a/apps/widchime/metadata.json b/apps/widchime/metadata.json new file mode 100644 index 000000000..db75e76ac --- /dev/null +++ b/apps/widchime/metadata.json @@ -0,0 +1,15 @@ +{ + "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"}] +} diff --git a/apps/widclk/metadata.json b/apps/widclk/metadata.json new file mode 100644 index 000000000..6996f4080 --- /dev/null +++ b/apps/widclk/metadata.json @@ -0,0 +1,13 @@ +{ + "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"} + ] +} diff --git a/apps/widclkbttm/metadata.json b/apps/widclkbttm/metadata.json new file mode 100644 index 000000000..9e92f7c46 --- /dev/null +++ b/apps/widclkbttm/metadata.json @@ -0,0 +1,15 @@ +{ + "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"} + ] +} diff --git a/apps/widcom/metadata.json b/apps/widcom/metadata.json new file mode 100644 index 000000000..4f7473a70 --- /dev/null +++ b/apps/widcom/metadata.json @@ -0,0 +1,14 @@ +{ + "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"} + ] +} diff --git a/apps/widgps/metadata.json b/apps/widgps/metadata.json new file mode 100644 index 000000000..87790a895 --- /dev/null +++ b/apps/widgps/metadata.json @@ -0,0 +1,14 @@ +{ + "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"} + ] +} diff --git a/apps/widhrm/metadata.json b/apps/widhrm/metadata.json new file mode 100644 index 000000000..e566142d2 --- /dev/null +++ b/apps/widhrm/metadata.json @@ -0,0 +1,13 @@ +{ + "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"} + ] +} diff --git a/apps/widhrt/metadata.json b/apps/widhrt/metadata.json new file mode 100644 index 000000000..a8f030157 --- /dev/null +++ b/apps/widhrt/metadata.json @@ -0,0 +1,14 @@ +{ + "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"} + ] +} diff --git a/apps/widhwt/metadata.json b/apps/widhwt/metadata.json new file mode 100644 index 000000000..375ddf498 --- /dev/null +++ b/apps/widhwt/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "widhwt", + "name": "Hand Wash Timer", + "version": "0.02", + "description": "On Bangle.js 1 swipe your wrist over the watch face to start your personal Bangle.js 1 hand wash timer. On Bangle.js2 the Pattern Launcher is recommended to start the timer. Start washing after the short buzz and stop after the long buzz 35sec. later.", + "icon": "widget.png", + "type": "widget", + "tags": "widget,tool", + "allow_emulator": true, + "screenshots": [{"url":"wash-hand-timer-screenshot.png"}], + "supports": ["BANGLEJS", "BANGLEJS2"], + "storage": [ + {"name":"widhwt.app.js","url":"app.js"}, + {"name":"widhwt.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widhwt/wash-hand-timer-screenshot.png b/apps/widhwt/wash-hand-timer-screenshot.png new file mode 100644 index 000000000..05859cd7a Binary files /dev/null and b/apps/widhwt/wash-hand-timer-screenshot.png differ diff --git a/apps/widid/metadata.json b/apps/widid/metadata.json new file mode 100644 index 000000000..e8831604c --- /dev/null +++ b/apps/widid/metadata.json @@ -0,0 +1,13 @@ +{ + "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"} + ] +} diff --git a/apps/widlock/ChangeLog b/apps/widlock/ChangeLog index 8aeb75429..bb84c2d44 100644 --- a/apps/widlock/ChangeLog +++ b/apps/widlock/ChangeLog @@ -2,3 +2,5 @@ 0.02: Handle new firmwares with 'lock' event 0.03: Don't try to be fancy - just bail out on firmwares without a lock event 0.04: Set sortorder to -1 so that widget always takes up the furthest left position +0.05: Set sortorder to -10 so that others can take -1 etc +0.06: Set sortorder to -10 in widget code diff --git a/apps/widlock/metadata.json b/apps/widlock/metadata.json new file mode 100644 index 000000000..8635a5434 --- /dev/null +++ b/apps/widlock/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "widlock", + "name": "Lock Widget", + "version": "0.06", + "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"} + ] +} diff --git a/apps/widlock/widget.js b/apps/widlock/widget.js index 4c59e6575..592361cd9 100644 --- a/apps/widlock/widget.js +++ b/apps/widlock/widget.js @@ -4,7 +4,7 @@ WIDGETS["lock"].width = Bangle.isLocked()?16:0; Bangle.drawWidgets(); }); - WIDGETS["lock"]={area:"tl",width:Bangle.isLocked()?16:0,draw:function(w) { + WIDGETS["lock"]={area:"tl",sortorder:10,width:Bangle.isLocked()?16:0,draw:function(w) { if (Bangle.isLocked()) g.reset().drawImage(atob("DhABH+D/wwMMDDAwwMf/v//4f+H/h/8//P/z///f/g=="), w.x+1, w.y+4); }}; diff --git a/apps/widmp/metadata.json b/apps/widmp/metadata.json new file mode 100644 index 000000000..9c7506a09 --- /dev/null +++ b/apps/widmp/metadata.json @@ -0,0 +1,13 @@ +{ + "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"} + ] +} diff --git a/apps/widmpsh/metadata.json b/apps/widmpsh/metadata.json new file mode 100644 index 000000000..e6ecf9e73 --- /dev/null +++ b/apps/widmpsh/metadata.json @@ -0,0 +1,13 @@ +{ + "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"} + ] +} diff --git a/apps/widnceu/metadata.json b/apps/widnceu/metadata.json new file mode 100644 index 000000000..c8e41f4c8 --- /dev/null +++ b/apps/widnceu/metadata.json @@ -0,0 +1,13 @@ +{ + "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"} + ] +} diff --git a/apps/widncr/metadata.json b/apps/widncr/metadata.json new file mode 100644 index 000000000..d710fb853 --- /dev/null +++ b/apps/widncr/metadata.json @@ -0,0 +1,13 @@ +{ + "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"} + ] +} diff --git a/apps/widpa/ChangeLog b/apps/widpa/ChangeLog index 5197bb4bd..4a8809923 100644 --- a/apps/widpa/ChangeLog +++ b/apps/widpa/ChangeLog @@ -1,2 +1,4 @@ 0.01: First release 0.02: Size widget after step count is reset +0.03: set sortorder to -1 +0.04: set sortorder through code diff --git a/apps/widpa/metadata.json b/apps/widpa/metadata.json new file mode 100644 index 000000000..95a8a8a9c --- /dev/null +++ b/apps/widpa/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "widpa", + "name": "Simple Pedometer", + "shortName":"Simple Pedometer", + "icon": "screenshot_widpa.png", + "screenshots": [{"url":"screenshot_widpa.png"}], + "version":"0.04", + "type": "widget", + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "description": "Displays the current step count from `Bangle.getHealthStatus(\"day\").steps` in 12x16 font, requires firmware v2.11.21 or later", + "tags": "widget,battery", + "storage": [ + {"name":"widpa.wid.js","url":"widpa.wid.js"} + ] +} diff --git a/apps/widpa/widpa.wid.js b/apps/widpa/widpa.wid.js index 1c0f27394..8cde29bc1 100644 --- a/apps/widpa/widpa.wid.js +++ b/apps/widpa/widpa.wid.js @@ -2,7 +2,7 @@ Bangle.on('step', function(s) { WIDGETS["widpa"].draw(); }); Bangle.on('lcdPower', function(on) { if (on) WIDGETS["widpa"].draw(); }); -WIDGETS["widpa"]={area:"tl",width:13,draw:function() { +WIDGETS["widpa"]={area:"tl",sortorder:-1,width:13,draw:function() { if (!Bangle.isLCDOn()) return; // dont redraw if LCD is off var steps = Bangle.getHealthStatus("day").steps; var w = 1 + (steps.toString().length)*12; diff --git a/apps/widpb/ChangeLog b/apps/widpb/ChangeLog index 1409a81ff..8a4a48b56 100644 --- a/apps/widpb/ChangeLog +++ b/apps/widpb/ChangeLog @@ -1,2 +1,5 @@ 0.01: First release 0.02: Fixed widget id to wibpb, Size widget after step count is reset +0.03: Fixed widget id in onStep() come on get it right! +0.04: set sortorder to -1 +0.05: set sortorder through code diff --git a/apps/widpb/metadata.json b/apps/widpb/metadata.json new file mode 100644 index 000000000..0c2ed747a --- /dev/null +++ b/apps/widpb/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "widpb", + "name": "Lato Pedometer", + "shortName":"Lato Pedometer", + "icon": "screenshot_widpb.png", + "screenshots": [{"url":"screenshot_widpb.png"}], + "version":"0.05", + "type": "widget", + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "description": "Displays the current step count from `Bangle.getHealthStatus(\"day\").steps` in the Lato font, requires firmware v2.11.21 or later", + "tags": "widget,battery", + "storage": [ + {"name":"widpb.wid.js","url":"widpb.wid.js"} + ] + } diff --git a/apps/widpb/widpb.wid.js b/apps/widpb/widpb.wid.js index 6129fac51..ceebb9937 100644 --- a/apps/widpb/widpb.wid.js +++ b/apps/widpb/widpb.wid.js @@ -1,9 +1,9 @@ // on.step version -Bangle.on('step', function(s) { WIDGETS["bata"].draw(); }); +Bangle.on('step', function(s) { WIDGETS["widpb"].draw(); }); Bangle.on('lcdPower', function(on) { if (on) WIDGETS["widpb"].draw(); }); -WIDGETS["widpb"]={area:"tl",width:13,draw:function() { +WIDGETS["widpb"]={area:"tl",sortorder:-1,width:13,draw:function() { if (!Bangle.isLCDOn()) return; // dont redraw if LCD is off var steps = Bangle.getHealthStatus("day").steps; var w = 1 + (steps.toString().length)*12; diff --git a/apps/widpedom/metadata.json b/apps/widpedom/metadata.json new file mode 100644 index 000000000..f49d3ba5b --- /dev/null +++ b/apps/widpedom/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "widpedom", + "name": "Pedometer widget", + "version": "0.22", + "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"} + ] +} diff --git a/apps/widram/ChangeLog b/apps/widram/ChangeLog index 4c21f3ace..e7b406081 100644 --- a/apps/widram/ChangeLog +++ b/apps/widram/ChangeLog @@ -1 +1,2 @@ 0.01: New Widget! +0.02: Now also visible on Bangle.js 2 diff --git a/apps/widram/metadata.json b/apps/widram/metadata.json new file mode 100644 index 000000000..19ae6d311 --- /dev/null +++ b/apps/widram/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "widram", + "name": "RAM Widget", + "shortName": "RAM Widget", + "version": "0.02", + "description": "Display your Bangle's RAM usage percentage in a widget", + "icon": "widget.png", + "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"widram.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widram/widget.js b/apps/widram/widget.js index dc7fed6c3..210c85357 100644 --- a/apps/widram/widget.js +++ b/apps/widram/widget.js @@ -1,11 +1,15 @@ (() => { function draw() { + BANGLEJS2 = process.env.HWVERSION==2; g.reset(); var m = process.memory(); - var pc = Math.round(m.usage*100/m.total); + var percent = Math.round(m.usage*100/m.total); g.drawImage(atob("BwgBqgP////AVQ=="), this.x+(24-7)/2, this.y+4); - g.setColor(pc>70 ? "#ff0000" : (pc>50 ? "#ffff00" : "#ffffff")); - g.setFont("6x8").setFontAlign(0,0).drawString(pc+"%", this.x+12, this.y+20, true/*solid*/); + if (!BANGLEJS2) + g.setColor(percent>70 ? "#ff0000" : (percent>50 ? "#ffff00" : "#ffffff")); + else + g.setColor(percent>70 ? "#f00" : (percent>50 ? "#00f" : "#0f0")); + g.setFont("6x8").setFontAlign(0,0).drawString(percent+"%", this.x+12, this.y+20, true/*solid*/); } var ramInterval; Bangle.on('lcdPower', function(on) { @@ -20,4 +24,4 @@ } }); WIDGETS["ram"]={area:"tl",width: 24,draw:draw}; -})() +})(); diff --git a/apps/widtbat/metadata.json b/apps/widtbat/metadata.json new file mode 100644 index 000000000..7e882d247 --- /dev/null +++ b/apps/widtbat/metadata.json @@ -0,0 +1,13 @@ +{ + "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"} + ] +} diff --git a/apps/widver/metadata.json b/apps/widver/metadata.json new file mode 100644 index 000000000..061844a36 --- /dev/null +++ b/apps/widver/metadata.json @@ -0,0 +1,13 @@ +{ + "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"} + ] +} diff --git a/apps/widviz/metadata.json b/apps/widviz/metadata.json new file mode 100644 index 000000000..ba9cf793b --- /dev/null +++ b/apps/widviz/metadata.json @@ -0,0 +1,14 @@ +{ + "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"} + ] +} diff --git a/apps/widviztime/metadata.json b/apps/widviztime/metadata.json new file mode 100644 index 000000000..b364bbd74 --- /dev/null +++ b/apps/widviztime/metadata.json @@ -0,0 +1,15 @@ +{ + "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"} + ] +} diff --git a/apps/wohrm/metadata.json b/apps/wohrm/metadata.json new file mode 100644 index 000000000..b2b89336c --- /dev/null +++ b/apps/wohrm/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "wohrm", + "name": "Workout HRM", + "version": "0.09", + "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", "BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "screenshots": [{"url":"bangle1-workout-HRM-screenshot.png"}], + "storage": [ + {"name":"wohrm.app.js","url":"app.js"}, + {"name":"wohrm.settings.js","url":"settings.js"}, + {"name":"wohrm.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/worldclock/metadata.json b/apps/worldclock/metadata.json new file mode 100644 index 000000000..706831a09 --- /dev/null +++ b/apps/worldclock/metadata.json @@ -0,0 +1,19 @@ +{ + "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"}] +} diff --git a/apps/wpmoto/README.md b/apps/wpmoto/README.md new file mode 100644 index 000000000..96c72ff55 --- /dev/null +++ b/apps/wpmoto/README.md @@ -0,0 +1,158 @@ +# Waypointer Moto + +Waypointer Moto is a GPS navigation aid intended to be attached to +the handlebars of a motorcycle. +It uses the GPS to find out which direction it's +travelling and shows the direction and distance to the destination +"as the crow flies". It gives you an indication of where to go, +but exploring and navigating the environment is left up to the user. + +![](watch-on-bike.jpeg) + +(Please note that it would be foolish in the extreme to rely on this +as your only navigation aid! Make sure you read this entire document +before using the app for navigation so that you know the drawbacks +and shortcomings.) + +## App usage + +### Main screen + +![](screenshot.png) + +The main screen shows the direction arrow, the distance to the waypoint, +and the name of the selected waypoint. + +It also shows the status of the GPS fix in the colour of the arrow: + + * Red: no GPS fix at all + * Yellow: GPS location, but no GPS course (probably you're moving too slowly); + in this case the direction of travel comes from the compass bearing instead + of the GPS course, but note that the compass is unreliable + * White: GPS fix includes both location and course, and the GPS course is used + to determine the direction of travel + +### Select a waypoint + +![](screenshot-menu.png) + +Press the middle button (`BTN2`) to enter the menu, choose a waypoint using +the up/down arrows, and use the middle button again to select a waypoint and +return to the main screen. + +### Add a waypoint + +Press the middle button (`BTN2`) to enter the menu, and select the "+ Here" +option. This will add a waypoint named "WP*n*" marking your current location, +where "*n*" is the next unused number. + +### Delete a waypoint + +![](screenshot-delete.png) + +Select a waypoint using the menu. Once the waypoint is selected and you're +back on the main screen, press either the top or bottom button (`BTN1` or +`BTN3`). Confirm that you want to delete the waypoint with the middle +button (`BTN2`). + +## Waypoint editor + +With the Bangle.js app loader connected to the watch, find the +Waypointer Moto app and click on the floppy disk icon: + +![](floppy-disk.png) + +This will load up the waypoint editor: + +![](editor.png) + +### Add a waypoint + +Use the map to find your destination. Clicking on the map will +populate the latitude/longitude input boxes with the coordinates +of the point you clicked on. Type in a name for the waypoint and +click "Add Waypoint". Click "Upload" to send the updated list of +waypoints to the watch. + +### Edit a waypoint + +Click on the pencil icon next to the waypoint you wish to edit. +This will remove the waypoint from the list and populate the +input boxes. +Edit the coordinates by hand, or by clicking on the map. Edit +the name if you want. Click "Add Waypoint" to save the waypoint +back to the list. Click "Upload" to send the updated list of +waypoints to the watch. + +### Delete a waypoint + +Click on the pencil icon next to the waypoint you wish to edit. +This will remove the waypoint from the list. +Click "Upload" to send the updated list of waypoints to the watch. + +## Mounting the watch on the bike + +There is a 3d-printable "artificial wrist" which will fit over a 7/8" +handlebar and allow the watch strap to tighten up. +Alternatively, in a pinch you can strap the watch around a glove or a sponge +or anything else that will pad out the space so that the watch is a tight +fit. + +The 3d-printed part should be a snug fit on the handlebar so that it does +not flop around. If it is too loose, line it with a layer or 2 of tape. + +[Download the handlebar mount STL »](handlebar-mount.stl) + +[Download the handlebar mount FreeCAD source »](handlebar-mount.FCStd) + +![](handlebar-mount.png) + +![](handlebar-mount.jpeg) + +## Comparison to Way Pointer + +Compared to the original Way Pointer app, Waypointer Moto: + + * removes the numerical display of compass bearing + * makes the distance text bigger + * uses a higher-resolution arrow icon + * has a visual indication of the GPS status (the arrow colour) + * uses GPS course instead of compass bearing + * has OpenStreetMap integration in the waypoint editor + * uses Bangle.js menus to select waypoints instead of custom UI + * can add new waypoints from inside the app without requiring a blank slot + * can delete waypoints from inside the app without needing the PC + * still uses the same `waypoints.json` file + +## Gotchas + +Waypointer Moto derives your current heading from the GPS course +rather than the compass, whenever GPS course is available. +The compass bearing is based on the angle the watch is held, but +the GPS course is based on the direction it's *travelling*. If the +watch is not aligned with the direction of travel of the vehicle +then the arrow will not point in the correct direction. + +When travelling too slowly, there is no GPS course information, so the +app reverts to using the compass (and draws it in yellow), but +the compass is not very reliable, and I +have especially found it not to be reliable when placed on a motorcyle, +maybe because of all the metal in the immediate vicinity. So if +the arrow is not drawn in white, then you should probably not trust +it. If you're not sure, just ride in a straight line until the arrow +turns white again. + +## Possible Future Enhancements + + - "routes" with multiple waypoints; automatically step from one + waypoint to the next when you get near to it + - some way to manually input coordinates directly on the watch + - make the text & arrow more legible in direct sunlight + - integrate a charging connector into the handlebar mount + - upstream the map integration to the other waypoint apps + +## Acknowledgements + +Waypointer Moto is a project by [James Stanley](https://incoherency.co.uk/). It is a derivative of [Adam Schmalhofer's](https://github.com/adamschmalhofer) Way Pointer app, which is in turn a derivative of +[jeffmer's](https://github.com/jeffmer/JeffsBangleAppsDev) GPS +Navigation and Compass Navigation apps. diff --git a/apps/wpmoto/app.js b/apps/wpmoto/app.js new file mode 100644 index 000000000..8a0e86ef6 --- /dev/null +++ b/apps/wpmoto/app.js @@ -0,0 +1,288 @@ +var loc = require("locale"); + +var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"NONE"}]; +var wp = waypoints[0]; +var wp_bearing = 0; +var candraw = true; + +var direction = 0; +var dist = 0; + +var savedfix; + +var previous = { + dst: '', + wp_name: '', + course: 180, + selected: false, +}; + +/*** Drawing ***/ + +var pal_by = new Uint16Array([0x0000,0xFFC0],0,1); // black, yellow +var pal_bw = new Uint16Array([0x0000,0xffff],0,1); // black, white +var pal_bb = new Uint16Array([0x0000,0x07ff],0,1); // black, blue +var pal_br = new Uint16Array([0x0000,0xf800],0,1); // black, red +var pal_compass = pal_by; + +var buf = Graphics.createArrayBuffer(160,160,1, {msb:true}); +var arrow_img = require("heatshrink").decompress(atob("vF4wJC/AEMYBxs8Bxt+Bxv/BpkB/+ABxcD//ABxcH//gBxcP//wBxcf//4Bxc///8Bxd///+OxgABOxgABPBR2BAAJ4KOwIABPBR2BAAJ4KOwIABPBR2BAAJ4KOwIABPBQNCPBR2DPBR2DPBR2DPBR2DPBR2DPBR2DPBR2DPBQNEPBB2FPBB2FPBB2FPBB2FPBB2FPBB2FPBB2FPBANGPAx2HPAx2HPAx2HPAx2HPAx2HPAx2HeJTeJB34O/B34O/B34O/B34O/B34O/B34O/B34O/B34OTAH4AT")); + +function flip1(x,y,palette) { + g.drawImage({width:160,height:160,bpp:1,buffer:buf.buffer, palette:palette},x,y); + buf.clear(); +} + +function flip2_bw(x,y) { + g.drawImage({width:160,height:40,bpp:1,buffer:buf.buffer, palette:pal_bw},x,y); + buf.clear(); +} + +function flip2_bb(x,y) { + g.drawImage({width:160,height:40,bpp:1,buffer:buf.buffer, palette:pal_bb},x,y); + buf.clear(); +} + +function drawCompass(course) { + if (!candraw) return; + + previous.course = course; + + buf.setColor(1); + buf.fillCircle(80,80, 79); + buf.setColor(0); + buf.fillCircle(80,80, 69); + buf.setColor(1); + buf.drawImage(arrow_img, 80, 80, {rotate:radians(course)} ); + var palette = pal_br; + if (savedfix !== undefined && savedfix.fix !== 0) palette = pal_compass; + flip1(40, 30, palette); +} + +function drawN(force){ + if (!candraw) return; + + buf.setFont("Vector",24); + var dst = loc.distance(dist); + + // distance on left + if (force || previous.dst !== dst) { + previous.dst = dst; + buf.setColor(1); + buf.setFontAlign(-1, -1); + buf.setFont("Vector",40); + buf.drawString(dst,0,0); + flip2_bw(8, 200); + } + + // waypoint name on right + if (force || previous.wp_name !== wp.name) { + previous.wp_name = wp.name; + buf.setColor(1); + buf.setFontAlign(1, -1); + buf.setFont("Vector", 15); + buf.drawString(wp.name, 80, 0); + flip2_bw(160, 220); + } +} + +function drawAll(force) { + if (!candraw) return; + + g.setColor(1,1,1); + drawN(force); + drawCompass(direction); +} + +/*** Heading ***/ + +var heading = 0; +function newHeading(m,h){ + var s = Math.abs(m - h); + var delta = (m>h)?1:-1; + if (s>=180){s=360-s; delta = -delta;} + if (s<2) return h; + var hd = h + delta*(1 + Math.round(s/5)); + if (hd<0) hd+=360; + if (hd>360)hd-= 360; + return hd; +} + +var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null; + +function tiltfixread(O,S){ + var start = Date.now(); + var m = Bangle.getCompass(); + var g = Bangle.getAccel(); + m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z; + var d = Math.atan2(-m.dx,m.dy)*180/Math.PI; + if (d<0) d+=360; + var phi = Math.atan(-g.x/-g.z); + var cosphi = Math.cos(phi), sinphi = Math.sin(phi); + var theta = Math.atan(-g.y/(-g.x*sinphi-g.z*cosphi)); + var costheta = Math.cos(theta), sintheta = Math.sin(theta); + var xh = m.dy*costheta + m.dx*sinphi*sintheta + m.dz*cosphi*sintheta; + var yh = m.dz*sinphi - m.dx*cosphi; + var psi = Math.atan2(yh,xh)*180/Math.PI; + if (psi<0) psi+=360; + return psi; +} + +function read_heading() { + if (savedfix !== undefined && !isNaN(savedfix.course)) { + Bangle.setCompassPower(0); + heading = savedfix.course; + pal_compass = pal_bw; + } else { + var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale); + Bangle.setCompassPower(1); + heading = newHeading(d,heading); + pal_compass = pal_by; + } + + direction = wp_bearing - heading; + if (direction < 0) direction += 360; + if (direction > 360) direction -= 360; + drawCompass(direction); +} + + +/*** Maths ***/ + +function radians(a) { + return a*Math.PI/180; +} + +function degrees(a) { + var d = a*180/Math.PI; + return (d+360)%360; +} + +function bearing(a,b){ + var delta = radians(b.lon-a.lon); + var alat = radians(a.lat); + var blat = radians(b.lat); + var y = Math.sin(delta) * Math.cos(blat); + var x = Math.cos(alat)*Math.sin(blat) - Math.sin(alat)*Math.cos(blat)*Math.cos(delta); + return Math.round(degrees(Math.atan2(y, x))); +} + +function distance(a,b){ + var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2)); + var y = radians(b.lat-a.lat); + return Math.round(Math.sqrt(x*x + y*y) * 6371000); +} + +/*** Waypoints ***/ + +function addCurrentWaypoint() { + var wpnum = 0; + var ok = false; + // XXX: O(n^2) search for lowest unused WP number + while (!ok) { + ok = true; + for (var i = 0; i < waypoints.length && ok; i++) { + if (waypoints[i].name == ("WP"+wpnum)) { + wpnum++; + ok = false; + } + } + } + + waypoints.push({ + name: "WP" + wpnum, + lat: savedfix.lat, + lon: savedfix.lon, + }); + wp = waypoints[waypoints.length-1]; + saveWaypoints(); +} + +function saveWaypoints() { + require("Storage").writeJSON("waypoints.json", waypoints); +} + +function deleteWaypoint(w) { + for (var i = 0; i < waypoints.length; i++) { + if (waypoints[i] == w) { + waypoints.splice(i, 1); + saveWaypoints(); + wp = {name:"NONE"}; + } + } +} + +/*** Setup ***/ + +function onGPS(fix) { + savedfix = fix; + + if (fix !== undefined && fix.fix == 1){ + dist = distance(fix, wp); + if (isNaN(dist)) dist = 0; + wp_bearing = bearing(fix, wp); + if (isNaN(wp_bearing)) wp_bearing = 0; + drawN(); + } +} + +function startTimers() { + setInterval(function() { + Bangle.setLCDPower(1); + read_heading(); + }, 500); +} + +function addWaypointToMenu(menu, i) { + menu[waypoints[i].name] = function() { + wp = waypoints[i]; + mainScreen(); + }; +} + +function mainScreen() { + E.showMenu(); + candraw = true; + drawAll(true); + + Bangle.setUI("updown", function(v) { + if (v === undefined) { + candraw = false; + var menu = { + "": { "title": "-- Waypoints --" }, + }; + for (let i = 0; i < waypoints.length; i++) { + addWaypointToMenu(menu, i); + } + menu["+ Here"] = function() { + addCurrentWaypoint(); + mainScreen(); + }; + menu["< Back"] = mainScreen; + E.showMenu(menu); + } else { + candraw = false; + E.showPrompt("Delete waypoint: " + wp.name + "?").then(function(confirmed) { + var name = wp.name; + if (confirmed) { + deleteWaypoint(wp); + E.showAlert("Waypoint deleted: " + name).then(mainScreen); + } else { + mainScreen(); + } + }); + } + }); +} + +Bangle.on('kill',()=>{ + Bangle.setCompassPower(0); + Bangle.setGPSPower(0); +}); + +g.clear(); +Bangle.setLCDBrightness(1); +Bangle.setGPSPower(1); +startTimers(); +Bangle.on('GPS', onGPS); +mainScreen(); diff --git a/apps/wpmoto/arrow.png b/apps/wpmoto/arrow.png new file mode 100644 index 000000000..870c7b6b2 Binary files /dev/null and b/apps/wpmoto/arrow.png differ diff --git a/apps/wpmoto/editor.png b/apps/wpmoto/editor.png new file mode 100644 index 000000000..0d2f9841b Binary files /dev/null and b/apps/wpmoto/editor.png differ diff --git a/apps/wpmoto/floppy-disk.png b/apps/wpmoto/floppy-disk.png new file mode 100644 index 000000000..dde8500c9 Binary files /dev/null and b/apps/wpmoto/floppy-disk.png differ diff --git a/apps/wpmoto/handlebar-mount.FCStd b/apps/wpmoto/handlebar-mount.FCStd new file mode 100644 index 000000000..a370c1090 Binary files /dev/null and b/apps/wpmoto/handlebar-mount.FCStd differ diff --git a/apps/wpmoto/handlebar-mount.jpeg b/apps/wpmoto/handlebar-mount.jpeg new file mode 100644 index 000000000..c4556a0d0 Binary files /dev/null and b/apps/wpmoto/handlebar-mount.jpeg differ diff --git a/apps/wpmoto/handlebar-mount.png b/apps/wpmoto/handlebar-mount.png new file mode 100644 index 000000000..da5adf7a7 Binary files /dev/null and b/apps/wpmoto/handlebar-mount.png differ diff --git a/apps/wpmoto/handlebar-mount.stl b/apps/wpmoto/handlebar-mount.stl new file mode 100644 index 000000000..966d0c327 Binary files /dev/null and b/apps/wpmoto/handlebar-mount.stl differ diff --git a/apps/wpmoto/icon.js b/apps/wpmoto/icon.js new file mode 100644 index 000000000..fc8eee898 --- /dev/null +++ b/apps/wpmoto/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4kA///tVK/feuekkEh1dSnnn5P2imlgdr221vvv0E5x9z8dqoEpMf4AqgMQCysRiIYUgMd6IXVqc1iIXXAAo2NiIXBivdAAfVGwxwCAAgXBicmswACsVRGAsRqoAEqIXC0QXDs0lGAkBisrBomBC4WqGAloMIkRqW72wNDlvTC4QwEwIvDLoOr3e7KwkzmsdmczxAABwaQFF4QwEC4SAGR4pfBGAO2kQABlQXIX4wlHL4LQNX5IXNDw4XygdZ96MFF58NvP8C6Mzu93uF9+oXQrQWBAAPU/5kPgoWDu+UoIQJ7vR6IGCm4XEz9MC5HMAAvHC4mc4gWHk93zPsC5F58oWHgQOE//8//M//+5mf9vAFxFwOQef5nu9wbB5nOiNQBoInCCYMCk8FjnM7qkB5lEp3//udRoKlBuueDwMyC4MojnJz2dmEA/1EovDn3d6nEiEHv3pzOc4oXB1nJ0Wo7JVBjnE/td6IxBgtQu/u4ci1vs6EAj+S3eynvBgEP92Z/+XzIXBiF59GOte27goB5OrlGDlWcMAP3SgJvBusBgF/61sycrlPFgvJsWZzMrziGB84XDvlBg+excp9N73P1gbQB5//+lEiMAi93vwaBz4XC1ep5OS2f9gPJlWZ93d+lBqNfzn//N5+lAg+cF4OZF4UA52LlWi3RfBSANEAAVBgEHz+W3VzlepVANczW73ep+L4CgNTnrfBAAPc8drUAOcR4MB/PYwefAwIXCjvRdgIABgvO7Ui1PMX4MA5n+96+BC4cRiIXDKAPOzPvIwIABhsc4oPEDAIGFhqhB4K2BACY+EAH4AG")) diff --git a/apps/wpmoto/metadata.json b/apps/wpmoto/metadata.json new file mode 100644 index 000000000..4e6c3497a --- /dev/null +++ b/apps/wpmoto/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "wpmoto", + "name": "Waypointer Moto", + "shortName": "Waypointer Moto", + "version": "0.01", + "description": "Waypoint-based motorcycle navigation aid", + "icon": "wpmoto.png", + "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS"], + "screenshots": [{"url":"screenshot.png"},{"url":"screenshot-menu.png"},{"url":"screenshot-delete.png"}], + "readme": "README.md", + "interface": "wpmoto.html", + "storage": [ + {"name":"wpmoto.app.js","url":"app.js"}, + {"name":"wpmoto.img","url":"icon.js","evaluate":true} + ], + "data": [{"name":"waypoints.json","url":"waypoints.json"}] +} diff --git a/apps/wpmoto/screenshot-delete.png b/apps/wpmoto/screenshot-delete.png new file mode 100644 index 000000000..4669a6cd0 Binary files /dev/null and b/apps/wpmoto/screenshot-delete.png differ diff --git a/apps/wpmoto/screenshot-menu.png b/apps/wpmoto/screenshot-menu.png new file mode 100644 index 000000000..cd4cedf2f Binary files /dev/null and b/apps/wpmoto/screenshot-menu.png differ diff --git a/apps/wpmoto/screenshot.png b/apps/wpmoto/screenshot.png new file mode 100644 index 000000000..756e54118 Binary files /dev/null and b/apps/wpmoto/screenshot.png differ diff --git a/apps/wpmoto/watch-on-bike.jpeg b/apps/wpmoto/watch-on-bike.jpeg new file mode 100644 index 000000000..c97fe9412 Binary files /dev/null and b/apps/wpmoto/watch-on-bike.jpeg differ diff --git a/apps/wpmoto/waypoints.json b/apps/wpmoto/waypoints.json new file mode 100644 index 000000000..8a4ab83b8 --- /dev/null +++ b/apps/wpmoto/waypoints.json @@ -0,0 +1,5 @@ +[ + { + "name":"NONE" + }, +] diff --git a/apps/wpmoto/wpmoto.html b/apps/wpmoto/wpmoto.html new file mode 100644 index 000000000..2fb7c9455 --- /dev/null +++ b/apps/wpmoto/wpmoto.html @@ -0,0 +1,198 @@ + + + + + + + + + +

List of waypoints

+ + + + + + + + + + + + +
NameLat.Long.Actions
+
+

Add a new waypoint

+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+ + + + + + + + diff --git a/apps/wpmoto/wpmoto.png b/apps/wpmoto/wpmoto.png new file mode 100644 index 000000000..fea1d128a Binary files /dev/null and b/apps/wpmoto/wpmoto.png differ diff --git a/bin/apploader.js b/bin/apploader.js index 6b4c2202d..427a0ef99 100755 --- a/bin/apploader.js +++ b/bin/apploader.js @@ -13,6 +13,7 @@ for Noble. var SETTINGS = { pretokenise : true }; +var APPSDIR = __dirname+"/../apps/"; var Utils = require("../core/js/utils.js"); var AppInfo = require("../core/js/appinfo.js"); var noble; @@ -29,18 +30,27 @@ if (!noble) { console.log(" npm install noble") } -var apps; +var apps = []; function ERROR(msg) { console.error(msg); process.exit(1); } -try { - apps = JSON.parse(require("fs").readFileSync(__dirname+"/../apps.json")); -} catch(e) { - ERROR("'apps.json' could not be loaded"); -} +var apps = []; +var dirs = require("fs").readdirSync(APPSDIR, {withFileTypes: true}); +dirs.forEach(dir => { + var appsFile; + if (dir.name.startsWith("_example") || !dir.isDirectory()) + return; + try { + appsFile = require("fs").readFileSync(APPSDIR+dir.name+"/metadata.json").toString(); + } catch (e) { + ERROR(dir.name+"/metadata.json does not exist"); + return; + } + apps.push(JSON.parse(appsFile)); +}); var args = process.argv; diff --git a/bin/create_apps_json.sh b/bin/create_apps_json.sh new file mode 100755 index 000000000..665079fc6 --- /dev/null +++ b/bin/create_apps_json.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# ================================================================ +# apps.json used to contain the metadata for every app. Now the +# metadata is stored in each apps's directory - app/yourapp/metadata.js +# +# The app loader still wants all the data in one file, so normally +# with GitHub pages, Jekyll is automatically run and creates a working +# apps.json +# +# However if you're hosting locally, or not on GitHub pages then you +# will want to create apps.json yourself. You can do that by installing +# and running Jekyll, OR the easier option is just to run this script. +# +# If you do this, please do not attempt to commit your modified +# apps.json back into the main BangleApps repository! +# +# You can pass an optional filename to this script, and it will write +# to that instead, apps.local.json is used when opening the loader on localhost +outfile="${1:-apps.json}" + +cd `dirname $0`/.. +echo "[" > "$outfile" +first=1 +for app in apps/*/; do + echo "Processing $app..."; + if [[ "$app" =~ ^apps/_example.* ]]; then + echo "Ignoring $app" + else + if [ $first -eq 1 ]; then + first=0; + else + echo "," >> "$outfile" + fi; + cat ${app}metadata.json >> "$outfile" +# echo ",\"$app\"," >> apps.json # DEBUG ONLY + fi +done +echo "]" >> "$outfile" + +if [ -z "$1" ]; then + # Running with no arguments: prevent accidental commit of modified apps.json. + # You can use `create_apps.json.sh apps.json` if you really want to both + # overwrite and still commit apps.json + git update-index --skip-worktree apps.json + echo "Told git to ignore modified apps.json." + # If you want to unignore it, use + # 'git update-index --no-skip-worktree apps.json' +fi diff --git a/bin/firmwaremaker.js b/bin/firmwaremaker.js index 4bc2a70b2..1c2d9cb77 100755 --- a/bin/firmwaremaker.js +++ b/bin/firmwaremaker.js @@ -10,7 +10,6 @@ var SETTINGS = { var path = require('path'); var ROOTDIR = path.join(__dirname, '..'); var APPDIR = ROOTDIR+'/apps'; -var APPJSON = ROOTDIR+'/apps.json'; var OUTFILE = ROOTDIR+'/firmware.js'; var DEVICE = "BANGLEJS"; var APPS = [ // IDs of apps to install @@ -28,7 +27,6 @@ global.Const = { }; var AppInfo = require(ROOTDIR+"/core/js/appinfo.js"); -var appjson = JSON.parse(fs.readFileSync(APPJSON).toString()); var appfiles = []; function fileGetter(url) { @@ -58,8 +56,11 @@ function fileGetter(url) { } Promise.all(APPS.map(appid => { - var app = appjson.find(app=>app.id==appid); - if (app===undefined) throw new Error(`App ${appid} not found`); + try { + var app = JSON.parse(fs.readFileSync(APPDIR + "/" + appid + "/metadata.json").toString()); + } catch (e) { + throw new Error(`App ${appid} not found`); + } return AppInfo.getFiles(app, { fileGetter : fileGetter, settings : SETTINGS, @@ -76,6 +77,7 @@ Promise.all(APPS.map(appid => { js += `\x10if (E.CRC32(require('Storage').read(${JSON.stringify(file.name)}))!=${file.crc}){console.log("${file.name} invalid");FAIL++}\n`; }*/ }); + // js = js.replace(/\x10/g,""); // remove the echo-off characters (for testing only) fs.writeFileSync(OUTFILE, js); console.log("Output written to "+OUTFILE); }); diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 14ced9ef8..7940e551d 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -16,7 +16,6 @@ var DEVICE = process.argv[2]; var path = require('path'); var ROOTDIR = path.join(__dirname, '..'); var APPDIR = ROOTDIR+'/apps'; -var APPJSON = ROOTDIR+'/apps.json'; var MINIFY = true; var OUTFILE, APPS; @@ -86,7 +85,6 @@ function atob(input) { } var AppInfo = require(ROOTDIR+"/core/js/appinfo.js"); -var appjson = JSON.parse(fs.readFileSync(APPJSON).toString()); var appfiles = []; function fileGetter(url) { @@ -134,8 +132,11 @@ function evaluateFile(file) { } Promise.all(APPS.map(appid => { - var app = appjson.find(app=>app.id==appid); - if (app===undefined) throw new Error(`App ${appid} not found`); + try { + var app = JSON.parse(fs.readFileSync(APPDIR + "/" + appid + "/metadata.json").toString()); + } catch (e) { + throw new Error(`App ${appid} not found`); + } return AppInfo.getFiles(app, { fileGetter : fileGetter, settings : SETTINGS, diff --git a/bin/language_scan.js b/bin/language_scan.js index 2a92fded1..ba0914ad9 100755 --- a/bin/language_scan.js +++ b/bin/language_scan.js @@ -2,15 +2,37 @@ /* Scans for strings that may be in English in each app, and outputs a list of strings that have been found. -Early work towards internationalisation. -See https://github.com/espruino/BangleApps/issues/136 +See https://github.com/espruino/BangleApps/issues/1311 */ +var IGNORE_STRINGS = [ + "5x5","6x8","6x8:2","4x6","12x20","6x15","5x9Numeric7Seg", "Vector", // fonts + "---","...","*","##","00","GPS","ram", + "12hour","rising","falling","title", + "sortorder","tl","tr", + "function","object", // typeof=== + "txt", // layout styles + "play","stop","pause", // music state +]; + +var IGNORE_FUNCTION_PARAMS = [ + "read", + "readJSON", + "require", + "setFont","setUI","setLCDMode", + "on", + "RegExp","sendCommand", + "print","log" +]; +var IGNORE_ARRAY_ACCESS = [ + "WIDGETS" +]; + var BASEDIR = __dirname+"/../"; Espruino = require(BASEDIR+"core/lib/espruinotools.js"); var fs = require("fs"); - var APPSDIR = BASEDIR+"apps/"; + function ERROR(s) { console.error("ERROR: "+s); process.exit(1); @@ -18,6 +40,9 @@ function ERROR(s) { function WARN(s) { console.log("Warning: "+s); } +function log(s) { + console.log(s); +} var appsFile, apps; try { @@ -32,38 +57,120 @@ try{ } // Given a string value, work out if it's obviously not a text string -function isNotString(s) { +function isNotString(s, wasFnCall, wasArrayAccess) { + if (s=="") return true; + // wasFnCall is set to the function name if 's' is the first argument to a function + if (wasFnCall && IGNORE_FUNCTION_PARAMS.includes(wasFnCall)) return true; + if (wasArrayAccess && IGNORE_ARRAY_ACCESS.includes(wasArrayAccess)) return true; + if (s=="Storage") console.log("isNotString",s,wasFnCall); + if (s.length<2) return true; // too short if (s.length>40) return true; // too long if (s[0]=="#") return true; // a color if (s.endsWith(".json") || s.endsWith(".img")) return true; // a filename if (s.endsWith("=")) return true; // probably base64 if (s.startsWith("BTN")) return true; // button name + if (IGNORE_STRINGS.includes(s)) return true; // one we know to ignore return false; } -var textStrings = []; +function getTextFromString(s) { + return s.replace(/^([.<>\-\n ]*)([^<>\!\?]*?)([.<>\!\?\-\n ]*)$/,"$2"); +} -console.log("Scanning..."); +// A string that *could* be translated? +var untranslatedStrings = []; +// Strings that are marked with 'LANG' +var translatedStrings = []; + +function addString(list, str, file) { + str = getTextFromString(str); + var entry = list.find(e => e.str==str); + if (!entry) { + entry = { str:str, uses:0, files : [] }; + list.push(entry); + } + entry.uses++; + if (!entry.files.includes(file)) + entry.files.push(file) +} + +console.log("Scanning apps..."); +//apps = apps.filter(a=>a.id=="wid_edit"); apps.forEach((app,appIdx) => { var appDir = APPSDIR+app.id+"/"; app.storage.forEach((file) => { if (!file.url || !file.name.endsWith(".js")) return; - var fileContents = fs.readFileSync(appDir+file.url).toString(); + var filePath = appDir+file.url; + var shortFilePath = "apps/"+app.id+"/"+file.url; + var fileContents = fs.readFileSync(filePath).toString(); var lex = Espruino.Core.Utils.getLexer(fileContents); + var lastIdx = 0; + var wasFnCall = undefined; // set to 'setFont' if we're at something like setFont(".." + var wasArrayAccess = undefined; // set to 'WIDGETS' if we're at something like WIDGETS[".." var tok = lex.next(); while (tok!==undefined) { + var previousString = fileContents.substring(lastIdx, tok.startIdx); if (tok.type=="STRING") { - if (!isNotString(tok.value)) { - //console.log(tok.str); - if (!textStrings.includes(tok.value)) - textStrings.push(tok.value); + if (previousString.includes("/*LANG*/")) { // translated! + addString(translatedStrings, tok.value, shortFilePath); + } else { // untranslated - potential to translate? + if (!isNotString(tok.value, wasFnCall, wasArrayAccess)) { + addString(untranslatedStrings, tok.value, shortFilePath); + } } + } else { + if (tok.value!="(") wasFnCall=undefined; + if (tok.value!="[") wasArrayAccess=undefined; } + //console.log(wasFnCall,tok.type,tok.value); + if (tok.type=="ID") { + wasFnCall = tok.value; + wasArrayAccess = tok.value; + } + lastIdx = tok.endIdx; tok = lex.next(); } }); }); -console.log("Done"); -textStrings.sort(); -console.log(textStrings.join("\n")); +untranslatedStrings.sort((a,b)=>a.uses - b.uses); +translatedStrings.sort((a,b)=>a.uses - b.uses); + + +var report = ""; + +log("Translated Strings that are not tagged with LANG"); +log("================================================================="); +log(""); +log("Maybe we should add /*LANG*/ to these automatically?"); +log(""); +log(untranslatedStrings.filter(e => translatedStrings.find(t=>t.str==e.str)).map(e=>`${JSON.stringify(e.str)} (${e.files.join(",")})`).join("\n")); +log(""); +//process.exit(1); +log("Possible English Strings that could be translated"); +log("================================================================="); +log(""); +log("Add these to IGNORE_STRINGS if they don't make sense..."); +log(""); + // ignore ones only used once or twice +log(untranslatedStrings.filter(e => e.uses>2).filter(e => !translatedStrings.find(t=>t.str==e.str)).map(e=>`${JSON.stringify(e.str)} (${e.uses} uses)`).join("\n")); +log(""); +//process.exit(1); + +var languages = JSON.parse(fs.readFileSync(BASEDIR+"/lang/index.json").toString()); +languages.forEach(language => { + if (language.code=="en_GB") { + console.log("Ignoring "+language.code); + return; + } + console.log("Scanning "+language.code); + log(language.code); + log("=========="); + var translations = JSON.parse(fs.readFileSync(BASEDIR+"/lang/"+language.url).toString()); + translatedStrings.forEach(str => { + if (!translations.GLOBAL[str.str]) + console.log(`Missing translation for ${JSON.stringify(str)}`); + }); + log(""); +}); +console.log("Done."); diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index 98fd1275f..363e86922 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -26,29 +26,35 @@ function WARN(s) { console.log("Warning: "+s); } -var appsFile, apps; -try { - appsFile = fs.readFileSync(BASEDIR+"apps.json").toString(); -} catch (e) { - ERROR("apps.json not found"); -} -try{ - apps = JSON.parse(appsFile); -} catch (e) { - console.log(e); - var m = e.toString().match(/in JSON at position (\d+)/); - if (m) { - var char = parseInt(m[1]); - console.log("==============================================="); - console.log("LINE "+appsFile.substr(0,char).split("\n").length); - console.log("==============================================="); - console.log(appsFile.substr(char-10, 20)); - console.log("==============================================="); +var apps = []; +var dirs = fs.readdirSync(APPSDIR, {withFileTypes: true}); +dirs.forEach(dir => { + var appsFile; + if (dir.name.startsWith("_example") || !dir.isDirectory()) + return; + try { + appsFile = fs.readFileSync(APPSDIR+dir.name+"/metadata.json").toString(); + } catch (e) { + ERROR(dir.name+"/metadata.json does not exist"); + return; } - console.log(m); - ERROR("apps.json not valid JSON"); - -} + try{ + apps.push(JSON.parse(appsFile)); + } catch (e) { + console.log(e); + var m = e.toString().match(/in JSON at position (\d+)/); + if (m) { + var char = parseInt(m[1]); + console.log("==============================================="); + console.log("LINE "+appsFile.substr(0,char).split("\n").length); + console.log("==============================================="); + console.log(appsFile.substr(char-10, 20)); + console.log("==============================================="); + } + console.log(m); + ERROR(dir.name+"/metadata.json not valid JSON"); + } +}); const APP_KEYS = [ 'id', 'name', 'shortName', 'version', 'icon', 'screenshots', 'description', 'tags', 'type', diff --git a/core b/core index 649489412..c243e6e71 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 649489412e27ef770bc0c8ed12cfca6a17a98c0d +Subproject commit c243e6e71f88358de720ad16ba8515b32b8d650f diff --git a/index.html b/index.html index e22a1f9e7..64bf1d44c 100644 --- a/index.html +++ b/index.html @@ -51,8 +51,8 @@
  • My Apps
  • -
  • - About +
  • + More...
  • @@ -111,7 +111,7 @@ -