Merge remote-tracking branch 'upstream/master'

pull/1052/head
Sebastian Di Luzio 2022-01-20 21:03:57 +01:00
commit c825c8ec86
453 changed files with 7218 additions and 6579 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ appdates.csv
_config.yml _config.yml
tests/Layout/bin/tmp.* tests/Layout/bin/tmp.*
tests/Layout/testresult.bmp tests/Layout/testresult.bmp
apps.json

View File

@ -12,7 +12,7 @@ and that it is not licensed in another way that would make this impossible.
## How does it work? ## 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/<id>` which is uploaded * Each element references an app in `apps/<id>` which is uploaded
* When it starts, BangleAppLoader checks the JSON and compares * When it starts, BangleAppLoader checks the JSON and compares
it with the files it sees in the watch's storage. 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 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. try and keep filenames short to avoid overflowing the buffer.
* Create a folder called `apps/<id>`, lets assume `apps/myappid` * Create a folder called `apps/<id>`, 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 * `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" * 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", { "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 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` 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 or `apps/_example_widget` to `apps/myappid`, and edit `apps/myappid/metadata.json` accordingly.
`apps.json`.
**Note:** the max filename length is 28 chars, so we suggest an app ID of under **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. 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. 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.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-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 * `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. 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 2. set _X_ Use Compression
3. set _X_ Transparency (optional) 3. set _X_ Transparency (optional)
4. set Diffusion: _flat_ 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_ 6. set Output as: _Image String_
Replace this line with the image converter output: 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==")) 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. 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 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) 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 * `widget.js` - widget code
Widgets are just small bits of code that run whenever an app that supports them 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 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 widget bar at the top of the screen they can add themselves to the global
the global `WIDGETS` array with: `WIDGETS` array with:
``` ```
WIDGETS["mywidget"]={ 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 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 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 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 // if this is 'bootloader' then it's code that is run at boot time, but is not in a menu
"version":"1.23", "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", "files:"file1,file2,file3",
// added by BangleApps loader on upload - lists all files // added by BangleApps loader on upload - lists all files
// that belong to the app so it can be deleted // 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 { "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 * 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 * 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. which names an HTML file in that app's folder.
When `custom` is defined, the 'upload' button is replaced by a customize 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 In that HTML file you're then responsible for handling a button
press and calling `sendCustomizedApp` with your own customised press and calling `sendCustomizedApp` with your own customised
version of what's in `apps.json`: version of what's in `metadata.json`:
``` ```
<html> <html>
@ -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 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. 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. which names an HTML file in that app's folder.
When `interface` is defined, a `Download from App` button is added to When `interface` is defined, a `Download from App` button is added to
@ -401,7 +403,7 @@ Example `settings.js`
E.showMenu(appMenu) 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. It should also add `myappid.json` to `data`, to make sure it is cleaned up when the app is uninstalled.
```json ```json
{ "id": "myappid", { "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 | | areas | as rectangle or point |
| :-:| :-: | | :-:| :-: |
| Widget | (0,0,239,23) | | Widget | (0,0,239,23) |
| Widget bottom bar (optional) | (0,216,239,239) | | Apps | (0,24,239,239) |
| Apps | (0,24,239,239) (see below) |
| BTN1 | (230, 55) | | BTN1 | (230, 55) |
| BTN2 | (230, 140) | | BTN2 | (230, 140) |
| BTN3 | (230, 210) | | BTN3 | (230, 210) |
| BTN4 | (0,0,119, 239)| | BTN4 | (0,0,119, 239)|
| BTN5 | (120,0,239,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()`. - Use `g.setFontAlign(0, 0, 3)` to draw rotated string to BTN1-BTN3 with `g.drawString()`.
- For BTN4-5 the touch area is named - For BTN4-5 the touch area is named

5711
apps.json

File diff suppressed because it is too large Load Diff

View File

@ -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}]
}

17
apps/93dub/metadata.json Normal file
View File

@ -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}
]
}

View File

@ -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}
]
}

15
apps/HRV/metadata.json Normal file
View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -1,4 +1,3 @@
// Create an entry in apps.json as follows:
{ "id": "7chname", { "id": "7chname",
"name": "My app's human readable name", "name": "My app's human readable name",
"shortName":"Short Name", "shortName":"Short Name",

View File

@ -1,4 +1,3 @@
// Create an entry in apps.json as follows:
{ "id": "7chname", { "id": "7chname",
"name": "My widget's human readable name", "name": "My widget's human readable name",
"shortName":"Short Name", "shortName":"Short Name",

View File

@ -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}
]
}

View File

@ -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}
]
}

17
apps/about/metadata.json Normal file
View File

@ -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
}

View File

@ -101,8 +101,8 @@
<script> <script>
$(function () { $(function () {
let ClockSize, ClockSizeURL let ClockSize, ClockSizeURL
let ClockFace, ClockFaceNumerals, ClockFaceDots, ClockFaceURL let ClockFace, ClockFaceURL, ClockFaceNumerals, ClockFaceDots
let ClockHands, SecondHand, ClockHandsURL, FillColor let ClockHands, ClockHandsURL, SecondHand, FillColor
let ComplicationTL, ComplicationTLURL let ComplicationTL, ComplicationTLURL
let ComplicationT, ComplicationTURL let ComplicationT, ComplicationTURL
let ComplicationTR, ComplicationTRURL let ComplicationTR, ComplicationTRURL
@ -118,8 +118,8 @@
function backupConfiguration () { function backupConfiguration () {
let Configuration = { let Configuration = {
ClockSize, ClockSizeURL, ClockSize, ClockSizeURL,
ClockFace, ClockFaceNumerals, ClockFaceDots, ClockFaceURL, ClockFace, ClockFaceURL, ClockFaceNumerals, ClockFaceDots,
ClockHands, SecondHand, ClockHandsURL, FillColor, ClockHands, ClockHandsURL, SecondHand, FillColor,
ComplicationTL, ComplicationTLURL, ComplicationTL, ComplicationTLURL,
ComplicationT, ComplicationTURL, ComplicationT, ComplicationTURL,
ComplicationTR, ComplicationTRURL, ComplicationTR, ComplicationTRURL,
@ -130,7 +130,7 @@
ComplicationBR, ComplicationBRURL, ComplicationBR, ComplicationBRURL,
Foreground, Background Foreground, Background
} }
try { try {
localStorage.setItem('ac_ac',JSON.stringify(Configuration)) localStorage.setItem('ac_ac',JSON.stringify(Configuration))
} catch (Signal) { } catch (Signal) {
@ -311,11 +311,11 @@
function chosenClockFace () { function chosenClockFace () {
switch (ClockFace) { switch (ClockFace) {
case 'none': return "undefined" case 'none': return "undefined"
case 'four-fold': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-four-fold-clock-face/main/ClockFace.js')" case 'four-numbered': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-four-numbered-clock-face/main/ClockFace.js')"
case 'twelve-fold': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-twelve-fold-clock-face/main/ClockFace.js')" case 'twelve-numbered': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-twelve-numbered-clock-face/main/ClockFace.js')"
case 'rainbow': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-rainbow-clock-face/main/ClockFace.js')" case 'rainbow': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-rainbow-clock-face/main/ClockFace.js')"
case 'custom': return "require('" + ClockFaceURL + "')" case 'custom': return "require('" + ClockFaceURL + "')"
} }
} }
@ -412,7 +412,7 @@ console.log(AppSource)
} }
$('input[type="radio"]').on('change',retrieveAndValidateInputs) $('input[type="radio"]').on('change',retrieveAndValidateInputs)
$('input[type="url"]'). on('change',retrieveAndValidateInputs) $('input[type="url"]'). on('input', retrieveAndValidateInputs)
$('select'). on('change',retrieveAndValidateInputs) $('select'). on('change',retrieveAndValidateInputs)
$('#UploadButton').on('click',createAndUploadApp) $('#UploadButton').on('click',createAndUploadApp)
}) })
@ -485,23 +485,23 @@ console.log(AppSource)
<input type="radio" name="clock-face" value="none" checked> <input type="radio" name="clock-face" value="none" checked>
<img src="none.png"/> <img src="none.png"/>
</label><br> </label><br>
none (none)
</td> </td>
<td> <td>
<label class="Preview"> <label class="Preview">
<input type="radio" name="clock-face" value="four-fold"> <input type="radio" name="clock-face" value="four-numbered">
<img src="fourfoldClockFace.png"/> <img src="fournumberedClockFace.png"/>
</label><br> </label><br>
four-fold four-numbered
</td> </td>
<td> <td>
<label class="Preview"> <label class="Preview">
<input type="radio" name="clock-face" value="twelve-fold"> <input type="radio" name="clock-face" value="twelve-numbered">
<img src="twelvefoldClockFace.png"/> <img src="twelvenumberedClockFace.png"/>
</label><br> </label><br>
twelve-fold twelve-numbered
</td> </td>
<td> <td>
@ -521,25 +521,25 @@ console.log(AppSource)
</td> </td>
</tr> </tr>
</tbody></table> </tbody></table>
</p><p>
Clock faces are drawn in the configured foreground and background colors
(you may select them at the end of this form)
</p><p>
"Four-fold" clock faces may draw indian-arabic or roman numerals. Which do you prefer?
</p><p>
<input type="radio" name="clock-face-numerals" value="indian" checked> indian-arabic (3, 6, 9, 12)<br>
<input type="radio" name="clock-face-numerals" value="roman"> roman (III, VI, IX, XII)
</p><p>
The "twelve-fold" and "rainbow"-colored faces may be drawn with or without
dots marking the position of every minute. Which variant do you prefer?
</p><p>
<input type="radio" name="clock-face-dots" value="without-dots" checked> without dots <br>
<input type="radio" name="clock-face-dots" value="with-dots"> with dots
</p><p> </p><p>
If you prefer a "custom" clock face, please enter the URL If you prefer a "custom" clock face, please enter the URL
of its JavaScript module below: of its JavaScript module below:
</p><p> </p><p>
custom URL: <input type="url" id="clock-face-custom-url" size="50"> custom URL: <input type="url" id="clock-face-custom-url" size="50">
</p><p>
Clock faces are drawn in the configured foreground and background colors
(you may select them at the end of this form)
</p><p>
"Four-numbered" clock faces may draw indian-arabic or roman numerals. Which do you prefer?
</p><p>
<input type="radio" name="clock-face-numerals" value="indian" checked> indian-arabic (3, 6, 9, 12)<br>
<input type="radio" name="clock-face-numerals" value="roman"> roman (III, VI, IX, XII)
</p><p>
The "twelve-numbered" and "rainbow"-colored faces may be drawn with or without
dots marking the position of every minute. Which variant do you prefer?
</p><p>
<input type="radio" name="clock-face-dots" value="without-dots" checked> without dots <br>
<input type="radio" name="clock-face-dots" value="with-dots"> with dots
</p> </p>
<h3>Clock Hands</h3> <h3>Clock Hands</h3>
@ -582,6 +582,11 @@ console.log(AppSource)
</td> </td>
</tr> </tr>
</tbody></table> </tbody></table>
</p><p>
If you prefer "custom" clock hands, please enter the URL
of their JavaScript module below:
</p><p>
custom URL: <input type="url" id="clock-hands-custom-url" size="50">
</p><p> </p><p>
Clock hands are drawn in the configured foreground and background colors Clock hands are drawn in the configured foreground and background colors
(you may select them at the end of this form) (you may select them at the end of this form)
@ -631,11 +636,6 @@ console.log(AppSource)
<input type="radio" name="second-hand" value="#FF00FF" class="ColorPatch" style="background:#FF00FF"/> <input type="radio" name="second-hand" value="#FF00FF" class="ColorPatch" style="background:#FF00FF"/>
<input type="radio" name="second-hand" value="#00FFFF" class="ColorPatch" style="background:#00FFFF"/> <input type="radio" name="second-hand" value="#00FFFF" class="ColorPatch" style="background:#00FFFF"/>
<input type="radio" name="second-hand" value="#FFFFFF" class="ColorPatch" style="background:#FFFFFF"/> <input type="radio" name="second-hand" value="#FFFFFF" class="ColorPatch" style="background:#FFFFFF"/>
</p><p>
If you prefer "custom" clock hands, please enter the URL
of their JavaScript module below:
</p><p>
custom URL: <input type="url" id="clock-hands-custom-url" size="50">
</p> </p>
<h3>Complications</h3> <h3>Complications</h3>

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

18
apps/ac_ac/metadata.json Normal file
View File

@ -0,0 +1,18 @@
{ "id": "ac_ac",
"name": "A Configurable Analog Clock",
"shortName":"Configurable Clock",
"version":"0.03",
"description": "AC-AC, a highly customizable analog clock with several clock faces, hands and complications to choose from",
"icon": "app-icon.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"allow_emulator": false,
"screenshots": [{"url":"app-screenshot.png"}],
"readme": "README.md",
"custom": "Customizer.html",
"storage": [
{"name":"ac_ac.app.js","url":"app.js"},
{"name":"ac_ac.img","url":"app-icon.js","evaluate":true}
]
}

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,14 @@
{ "id": "accelgraph",
"name": "Accelerometer Graph",
"shortName":"Accel Graph",
"version":"0.01",
"description": "A simple app to draw a graph of data from the accelerometer on the screen",
"icon": "app.png",
"tags": "tool,debug",
"supports" : ["BANGLEJS","BANGLEJS2"],
"screenshots": [{"url":"screenshot.png"}],
"storage": [
{"name":"accelgraph.app.js","url":"app.js"},
{"name":"accelgraph.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,17 @@
{
"id": "accellog",
"name": "Acceleration Logger",
"shortName": "Accel Log",
"version": "0.03",
"description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC",
"icon": "app.png",
"tags": "outdoor",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"interface": "interface.html",
"storage": [
{"name":"accellog.app.js","url":"app.js"},
{"name":"accellog.img","url":"app-icon.js","evaluate":true}
],
"data": [{"wildcard":"accellog.?.csv"}]
}

View File

@ -0,0 +1,17 @@
{
"id": "accelrec",
"name": "Acceleration Recorder",
"shortName": "Accel Rec",
"version": "0.02",
"description": "This app puts the Bangle's accelerometer into 100Hz mode and reads 2 seconds worth of data after movement starts. The data can then be exported back to the PC.",
"icon": "app.png",
"tags": "",
"supports": ["BANGLEJS"],
"readme": "README.md",
"interface": "interface.html",
"storage": [
{"name":"accelrec.app.js","url":"app.js"},
{"name":"accelrec.img","url":"app-icon.js","evaluate":true}
],
"data": [{"wildcard":"accelrec.?.csv"}]
}

16
apps/aclock/metadata.json Normal file
View File

@ -0,0 +1,16 @@
{
"id": "aclock",
"name": "Analog Clock",
"version": "0.15",
"description": "An Analog Clock",
"icon": "clock-analog.png",
"screenshots": [{"url":"screenshot_analog.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"aclock.app.js","url":"clock-analog.js"},
{"name":"aclock.img","url":"clock-analog-icon.js","evaluate":true}
]
}

View File

@ -1 +1,2 @@
0.01: New App! 0.01: New App!
0.02: Faster maze generation

View File

@ -35,21 +35,56 @@ function Maze(n) {
this.walls[cell] = WALL_RIGHT|WALL_DOWN; this.walls[cell] = WALL_RIGHT|WALL_DOWN;
this.groups[cell] = cell; this.groups[cell] = cell;
} }
// Candidates of walls to break when digging the maze.
// If candidate failed (breaking it would create a loop),
// it would never succeed, so no need to retry it.
let candidates_down = [],
candidates_right = [];
for (let r=0 ; r<n; r++) {
for (let c=0; c<n; c++) {
let cell = n*r+c;
if (r<(n-1)) { // Don't break wall down for bottom row.
candidates_down.push(cell);
}
if (c<(n-1)) { // Don't break wall right for rightmost column.
candidates_right.push(cell);
}
}
}
let from_group, to_group; let from_group, to_group;
let ngroups = n*n; let ngroups = n*n;
while (--ngroups) { while (--ngroups) {
// Abort if BTN1 pressed [grace period for menu] // Abort if BTN1 pressed [grace period for menu]
// (for some reason setWatch() fails inside constructor) // (for some reason setWatch() fails inside constructor)
if (ngroups<n*n-4 && digitalRead(BTN1)) { if (ngroups<n*n-16 && digitalRead(BTN1)) {
aborting = true; aborting = true;
return; return;
} }
from_group = to_group = -1; from_group = to_group = -1;
while (from_group<0) { while (from_group<0) {
if (Math.random()<0.5) { // try to break a wall right let trying_down = false;
let r = Math.floor(Math.random()*n); if (Math.random()<0.5 && candidates_down.length || !candidates_right.length) {
let c = Math.floor(Math.random()*(n-1)); trying_down = true;
let cell = r*n+c; }
let candidates = trying_down ? candidates_down : candidates_right;
candidate_index = Math.floor(Math.random()*candidates.length),
cell = candidates.splice(candidate_index, 1)[0],
r = Math.floor(cell/n),
c = cell%n;
if (trying_down) { // try to break a wall down
if (this.groups[cell]!=this.groups[cell+n]) {
this.walls[cell] &= ~WALL_DOWN;
g.clearRect(
this.margin+c*this.wall_length+1,
this.margin+(r+1)*this.wall_length,
this.margin+(c+1)*this.wall_length-1,
this.margin+(r+1)*this.wall_length
);
g.flip(); // show progress.
from_group = this.groups[cell];
to_group = this.groups[cell+n];
}
} else { // try to break a wall right
if (this.groups[cell]!=this.groups[cell+1]) { if (this.groups[cell]!=this.groups[cell+1]) {
this.walls[cell] &= ~WALL_RIGHT; this.walls[cell] &= ~WALL_RIGHT;
g.clearRect( g.clearRect(
@ -62,21 +97,6 @@ function Maze(n) {
from_group = this.groups[cell]; from_group = this.groups[cell];
to_group = this.groups[cell+1]; to_group = this.groups[cell+1];
} }
} else { // try to break a wall down
let r = Math.floor(Math.random()*(n-1));
let c = Math.floor(Math.random()*n);
let cell = r*n+c;
if (this.groups[cell]!=this.groups[cell+n]) {
this.walls[cell] &= ~WALL_DOWN;
g.clearRect(
this.margin+c*this.wall_length+1,
this.margin+(r+1)*this.wall_length,
this.margin+(c+1)*this.wall_length-1,
this.margin+(r+1)*this.wall_length
);
from_group = this.groups[cell];
to_group = this.groups[cell+n];
}
} }
} }
for (let cell = 0; cell<n*n; cell++) { for (let cell = 0; cell<n*n; cell++) {
@ -253,7 +273,6 @@ let maze_interval = setInterval(
function() { function() {
if (maze) { if (maze) {
if (digitalRead(BTN1) || maze.status==STATUS_ABORTED) { if (digitalRead(BTN1) || maze.status==STATUS_ABORTED) {
console.log(`aborting ${start_time}`);
maze = null; maze = null;
start_time = duration = 0; start_time = duration = 0;
aborting = false; aborting = false;
@ -270,7 +289,7 @@ let maze_interval = setInterval(
duration = Date.now()-start_time; duration = Date.now()-start_time;
g.setFontAlign(0,0).setColor(g.theme.fg); g.setFontAlign(0,0).setColor(g.theme.fg);
g.setFont("Vector",18); g.setFont("Vector",18);
g.drawString(`Solved in\n ${timeToText(duration)} \nClick to play again`, g.getWidth()/2, g.getHeight()/2, true); g.drawString(`Solved ${maze.n}X${maze.n} in\n ${timeToText(duration)} \nClick to play again`, g.getWidth()/2, g.getHeight()/2, true);
} }
} }
}, 25); }, 25);

15
apps/acmaze/metadata.json Normal file
View File

@ -0,0 +1,15 @@
{ "id": "acmaze",
"name": "AccelaMaze",
"shortName":"AccelaMaze",
"version":"0.02",
"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}
]
}

View File

@ -0,0 +1,18 @@
{
"id": "activepedom",
"name": "Active Pedometer",
"shortName": "Active Pedometer",
"version": "0.09",
"description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.",
"icon": "app.png",
"tags": "outdoors,widget",
"supports": ["BANGLEJS"],
"readme": "README.md",
"screenshots": [{"url":"600.png"},{"url":"10600.png"},{"url":"1600.png"}],
"storage": [
{"name":"activepedom.wid.js","url":"widget.js"},
{"name":"activepedom.settings.js","url":"settings.js"},
{"name":"activepedom.img","url":"app-icon.js","evaluate":true},
{"name":"activepedom.app.js","url":"app.js"}
]
}

18
apps/alarm/metadata.json Normal file
View File

@ -0,0 +1,18 @@
{
"id": "alarm",
"name": "Default Alarm & Timer",
"shortName": "Alarms",
"version": "0.14",
"description": "Set and respond to alarms and timers",
"icon": "app.png",
"tags": "tool,alarm,widget",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"alarm.app.js","url":"app.js"},
{"name":"alarm.boot.js","url":"boot.js"},
{"name":"alarm.js","url":"alarm.js"},
{"name":"alarm.img","url":"app-icon.js","evaluate":true},
{"name":"alarm.wid.js","url":"widget.js"}
],
"data": [{"name":"alarm.json"}]
}

View File

@ -0,0 +1,14 @@
{
"id": "alpinenav",
"name": "Alpine Nav",
"version": "0.01",
"description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime",
"icon": "app-icon.png",
"tags": "outdoors,gps",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"alpinenav.app.js","url":"app.js"},
{"name":"alpinenav.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,16 @@
{
"id": "analogimgclk",
"name": "Analog Clock (Image background)",
"shortName": "Analog Clock",
"version": "0.03",
"description": "An analog clock with an image background",
"icon": "app.png",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS"],
"storage": [
{"name":"analogimgclk.app.js","url":"app.js"},
{"name":"analogimgclk.bg.img","url":"bg.img"},
{"name":"analogimgclk.img","url":"app-icon.js","evaluate":true}
]
}

15
apps/andark/metadata.json Normal file
View File

@ -0,0 +1,15 @@
{ "id": "andark",
"name": "Analog Dark",
"shortName":"AnDark",
"version":"0.04",
"description": "analog clock face without disturbing widgets",
"icon": "andark_icon.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"andark.app.js","url":"app.js"},
{"name":"andark.img","url":"app_icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,20 @@
{
"id": "android",
"name": "Android Integration",
"shortName": "Android",
"version": "0.06",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge",
"dependencies": {"messages":"app"},
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"android.app.js","url":"app.js"},
{"name":"android.settings.js","url":"settings.js"},
{"name":"android.img","url":"app-icon.js","evaluate":true},
{"name":"android.boot.js","url":"boot.js"}
],
"data": [{"name":"android.settings.json"}],
"sortorder": -8
}

View File

@ -0,0 +1,21 @@
{
"id": "animals",
"name": "Animals Game",
"version": "0.01",
"description": "Simple toddler's game - displays a different number of animals each time the screen is pressed",
"icon": "animals.png",
"tags": "game",
"supports": ["BANGLEJS"],
"storage": [
{"name":"animals.app.js","url":"animals.js"},
{"name":"animals.img","url":"animals-icon.js","evaluate":true},
{"name":"animals-snake.img","url":"animals-snake.js","evaluate":true},
{"name":"animals-duck.img","url":"animals-duck.js","evaluate":true},
{"name":"animals-swan.img","url":"animals-swan.js","evaluate":true},
{"name":"animals-fox.img","url":"animals-fox.js","evaluate":true},
{"name":"animals-camel.img","url":"animals-camel.js","evaluate":true},
{"name":"animals-pig.img","url":"animals-pig.js","evaluate":true},
{"name":"animals-sheep.img","url":"animals-sheep.js","evaluate":true},
{"name":"animals-mouse.img","url":"animals-mouse.js","evaluate":true}
]
}

View File

@ -0,0 +1,18 @@
{
"id": "animclk",
"name": "Animated Clock",
"shortName": "Anim Clock",
"version": "0.03",
"description": "An animated clock face using Mark Ferrari's amazing 8 bit game art and palette cycling: http://www.markferrari.com/art/8bit-game-art",
"icon": "app.png",
"type": "clock",
"tags": "clock,animated",
"supports": ["BANGLEJS"],
"storage": [
{"name":"animclk.app.js","url":"app.js"},
{"name":"animclk.pixels1","url":"animclk.pixels1"},
{"name":"animclk.pixels2","url":"animclk.pixels2"},
{"name":"animclk.pal","url":"animclk.pal"},
{"name":"animclk.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,19 @@
{
"id": "antonclk",
"name": "Anton Clock",
"version": "0.06",
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
"readme":"README.md",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"antonclk.app.js","url":"app.js"},
{"name":"antonclk.settings.js","url":"settings.js"},
{"name":"antonclk.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"antonclk.json"}]
}

15
apps/arrow/metadata.json Normal file
View File

@ -0,0 +1,15 @@
{
"id": "arrow",
"name": "Arrow Compass",
"version": "0.05",
"description": "Moving arrow compass that points North, shows heading, with tilt correction. Based on jeffmer's Navigation Compass",
"icon": "arrow.png",
"type": "app",
"tags": "tool,outdoors",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"arrow.app.js","url":"app.js"},
{"name":"arrow.img","url":"icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,13 @@
{
"id": "assistedgps",
"name": "Assisted GPS Update (AGPS)",
"version": "0.03",
"description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 or 2 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
"icon": "app.png",
"type": "RAM",
"tags": "tool,outdoors,agps",
"supports": ["BANGLEJS","BANGLEJS2"],
"custom": "custom.html",
"customConnect": true,
"storage": []
}

15
apps/astral/metadata.json Normal file
View File

@ -0,0 +1,15 @@
{
"id": "astral",
"name": "Astral Clock",
"version": "0.03",
"description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.",
"icon": "app-icon.png",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"astral.app.js","url":"app.js"},
{"name":"astral.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,23 @@
{
"id": "astrocalc",
"name": "Astrocalc",
"version": "0.02",
"description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.",
"icon": "astrocalc.png",
"tags": "app,sun,moon,cycles,tool,outdoors",
"supports": ["BANGLEJS"],
"allow_emulator": true,
"storage": [
{"name":"astrocalc.app.js","url":"astrocalc-app.js"},
{"name":"suncalc.js","url":"suncalc.js"},
{"name":"astrocalc.img","url":"astrocalc-icon.js","evaluate":true},
{"name":"first-quarter.img","url":"first-quarter-icon.js","evaluate":true},
{"name":"last-quarter.img","url":"last-quarter-icon.js","evaluate":true},
{"name":"waning-crescent.img","url":"waning-crescent-icon.js","evaluate":true},
{"name":"waning-gibbous.img","url":"waning-gibbous-icon.js","evaluate":true},
{"name":"full.img","url":"full-icon.js","evaluate":true},
{"name":"new.img","url":"new-icon.js","evaluate":true},
{"name":"waxing-gibbous.img","url":"waxing-gibbous-icon.js","evaluate":true},
{"name":"waxing-crescent.img","url":"waxing-crescent-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,15 @@
{
"id": "astroid",
"name": "Asteroids!",
"version": "0.03",
"description": "Retro asteroids game",
"icon": "asteroids.png",
"screenshots": [{"url":"screenshot_asteroids.png"}],
"tags": "game",
"supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"astroid.app.js","url":"asteroids.js"},
{"name":"astroid.img","url":"asteroids-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,19 @@
{
"id": "authentiwatch",
"name": "2FA Authenticator",
"shortName": "AuthWatch",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"version": "0.04",
"description": "Google Authenticator compatible tool.",
"tags": "tool",
"interface": "interface.html",
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"authentiwatch.app.js","url":"app.js"},
{"name":"authentiwatch.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"authentiwatch.json"}]
}

View File

@ -0,0 +1,17 @@
{
"id":"awairmonitor",
"name":"Awair Monitor",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"allow_emulator": true,
"version":"0.03",
"description": "Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awair device.",
"type": "clock",
"tags": "clock,tool,health",
"readme":"README.md",
"supports":["BANGLEJS2"],
"storage": [
{"name":"awairmonitor.app.js","url":"app.js"},
{"name":"awairmonitor.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,16 @@
{
"id": "ballmaze",
"name": "Ball Maze",
"version": "0.02",
"description": "Navigate a ball through a maze by tilting your watch.",
"icon": "icon.png",
"type": "app",
"tags": "game",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"ballmaze.app.js","url":"app.js"},
{"name":"ballmaze.img","url":"icon.js","evaluate":true}
],
"data": [{"name":"ballmaze.json"}]
}

View File

@ -0,0 +1,14 @@
{
"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}
]
}

View File

@ -0,0 +1,17 @@
{
"id": "banglebridge",
"name": "BangleBridge",
"shortName": "BangleBridge",
"version": "0.01",
"description": "Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App",
"icon": "widget.png",
"type": "widget",
"tags": "widget",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"banglebridge.wid.js","url":"widget.js"},
{"name":"banglebridge.watch.img","url":"watch.img"},
{"name":"banglebridge.heart.img","url":"heart.img"}
]
}

View File

@ -0,0 +1,16 @@
{
"id": "banglerun",
"name": "BangleRun",
"shortName": "BangleRun",
"version": "0.10",
"description": "An app for running sessions. Displays info and logs your run for later viewing.",
"icon": "banglerun.png",
"tags": "run,running,fitness,outdoors",
"supports": ["BANGLEJS"],
"interface": "interface.html",
"allow_emulator": false,
"storage": [
{"name":"banglerun.app.js","url":"app.js"},
{"name":"banglerun.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,21 @@
{ "id": "banglexercise",
"name": "BanglExercise",
"shortName":"BanglExercise",
"version":"0.02",
"description": "Can automatically track exercises while wearing the Bangle.js watch.",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "app",
"tags": "sport",
"supports" : ["BANGLEJS2"],
"allow_emulator":true,
"readme": "README.md",
"storage": [
{"name":"banglexercise.app.js","url":"app.js"},
{"name":"banglexercise.img","url":"app-icon.js","evaluate":true},
{"name":"banglexercise.settings.js","url":"settings.js"}
],
"data": [
{"name":"banglexercise.json"}
]
}

View File

@ -0,0 +1,17 @@
{
"id": "barclock",
"name": "Bar Clock",
"version": "0.09",
"description": "A simple digital clock showing seconds as a bar",
"icon": "clock-bar.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"barclock.app.js","url":"clock-bar.js"},
{"name":"barclock.img","url":"clock-bar-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,16 @@
{
"id": "batchart",
"name": "Battery Chart",
"shortName": "Battery Chart",
"version": "0.10",
"description": "A widget and an app for recording and visualizing battery percentage over time.",
"icon": "app.png",
"tags": "app,widget,battery,time,record,chart,tool",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"batchart.wid.js","url":"widget.js"},
{"name":"batchart.app.js","url":"app.js"},
{"name":"batchart.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,17 @@
{
"id": "batclock",
"name": "Bat Clock",
"shortName": "Bat Clock",
"version": "0.02",
"description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.",
"icon": "bat-clock.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"batclock.app.js","url":"bat-clock.app.js"},
{"name":"batclock.img","url":"bat-clock.icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,16 @@
{
"id": "battleship",
"name": "Battleship",
"version": "0.01",
"description": "The classic game of battleship",
"icon": "battleship-icon.png",
"tags": "game",
"supports": ["BANGLEJS"],
"screenshots": [{"url":"bangle1-battle-ship-screenshot.png"}],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"battleship.app.js","url":"battleship.js"},
{"name":"battleship.img","url":"battleship-icon.js","evaluate":true}
]
}

16
apps/bclock/metadata.json Normal file
View File

@ -0,0 +1,16 @@
{
"id": "bclock",
"name": "Binary Clock",
"version": "0.03",
"description": "A simple binary clock watch face",
"icon": "clock-binary.png",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS"],
"allow_emulator": true,
"screenshots": [{"url":"bangle1-binary-clock-screenshot.png"}],
"storage": [
{"name":"bclock.app.js","url":"clock-binary.js"},
{"name":"bclock.img","url":"clock-binary-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,16 @@
{
"id": "beebclock",
"name": "Beeb Clock",
"version": "0.05",
"description": "Clock face that may be coincidentally familiar to BBC viewers",
"icon": "beebclock.png",
"type": "clock",
"tags": "clock",
"screenshots": [{"url":"bangle1-beeb-clock-screenshot.png"}],
"supports": ["BANGLEJS"],
"allow_emulator": true,
"storage": [
{"name":"beebclock.app.js","url":"beebclock.js"},
{"name":"beebclock.img","url":"beebclock-icon.js","evaluate":true}
]
}

14
apps/beer/metadata.json Normal file
View File

@ -0,0 +1,14 @@
{
"id": "beer",
"name": "Beer Compass",
"version": "0.01",
"description": "Uploads all the pubs in an area onto your watch, so it can always point you at the nearest one",
"icon": "app.png",
"tags": "",
"supports": ["BANGLEJS"],
"custom": "custom.html",
"storage": [
{"name":"beer.app.js"},
{"name":"beer.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,16 @@
{
"id": "berlinc",
"name": "Berlin Clock",
"version": "0.05",
"description": "Berlin Clock (see https://en.wikipedia.org/wiki/Mengenlehreuhr)",
"icon": "berlin-clock.png",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true,
"screenshots": [{"url":"berlin-clock-screenshot.png"}],
"storage": [
{"name":"berlinc.app.js","url":"berlin-clock.js"},
{"name":"berlinc.img","url":"berlin-clock-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,15 @@
{
"id": "binclock",
"name": "Binary Clock",
"shortName": "Binary Clock",
"version": "0.03",
"description": "A binary clock with hours and minutes. BTN1 toggles a digital clock.",
"icon": "app.png",
"type": "clock",
"tags": "clock,binary",
"supports": ["BANGLEJS"],
"storage": [
{"name":"binclock.app.js","url":"app.js"},
{"name":"binclock.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,19 @@
{ "id": "binwatch",
"name": "Binary Watch",
"shortName":"BinWatch",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"version":"0.04",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"allow_emulator":true,
"description": "Famous binary watch",
"tags": "clock",
"type": "clock",
"storage": [
{"name":"binwatch.app.js","url":"app.js"},
{"name":"binwatch.bg176.img","url":"Background176_center.img"},
{"name":"binwatch.bg240.img","url":"Background240_center.img"},
{"name":"binwatch.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,16 @@
{
"id": "blackjack",
"name": "Black Jack game",
"shortName": "Black Jack game",
"version": "0.02",
"description": "Simple implementation of card game Black Jack",
"icon": "blackjack.png",
"tags": "game",
"supports": ["BANGLEJS"],
"screenshots": [{"url":"bangle1-black-jack-game-screenshot.png"}],
"allow_emulator": true,
"storage": [
{"name":"blackjack.app.js","url":"blackjack.app.js"},
{"name":"blackjack.img","url":"blackjack-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,15 @@
{
"id": "bledetect",
"name": "BLE Detector",
"shortName": "BLE Detector",
"version": "0.03",
"description": "Detect BLE devices and show some informations.",
"icon": "bledetect.png",
"tags": "app,bluetooth,tool",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"bledetect.app.js","url":"bledetect.js"},
{"name":"bledetect.img","url":"bledetect-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,13 @@
{
"id": "blescan",
"name": "BLE Scanner",
"version": "0.01",
"description": "Scan for advertising BLE devices",
"icon": "blescan.png",
"tags": "bluetooth",
"supports": ["BANGLEJS"],
"storage": [
{"name":"blescan.app.js","url":"blescan.js"},
{"name":"blescan.img","url":"blescan-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,17 @@
{
"id": "blobclk",
"name": "Large Digit Blob Clock",
"shortName": "Blob Clock",
"version": "0.06",
"description": "A clock with big digits",
"icon": "clock-blob.png",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true,
"screenshots": [{"url":"bangle2-large-digit-blob-clock-screenshot.png"},{"url":"bangle1-large-digit-blob-clock-screenshot.png"}],
"storage": [
{"name":"blobclk.app.js","url":"clock-blob.js"},
{"name":"blobclk.img","url":"clock-blob-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,16 @@
{
"id": "bluetoothdock",
"name": "Bluetooth Dock",
"shortName": "Dock",
"version": "0.01",
"description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen",
"icon": "app.png",
"tags": "bluetooth",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"bluetoothdock.app.js","url":"app.js"},
{"name":"bluetoothdock.boot.js","url":"boot.js"},
{"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,17 @@
{
"id": "boldclk",
"name": "Bold Clock",
"version": "0.05",
"description": "Simple, readable and practical clock",
"icon": "bold_clock.png",
"screenshots": [{"url":"screenshot_bold.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"boldclk.app.js","url":"bold_clock.js"},
{"name":"boldclk.img","url":"bold_clock-icon.js","evaluate":true}
]
}

16
apps/boot/metadata.json Normal file
View File

@ -0,0 +1,16 @@
{
"id": "boot",
"name": "Bootloader",
"version": "0.41",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png",
"type": "bootloader",
"tags": "tool,system",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":".boot0","url":"boot0.js"},
{"name":".bootcde","url":"bootloader.js"},
{"name":"bootupdate.js","url":"bootupdate.js"}
],
"sortorder": -10
}

View File

@ -0,0 +1,15 @@
{
"id": "bootgattbat",
"name": "BLE GATT Battery Service",
"shortName": "BLE Battery Service",
"version": "0.01",
"description": "Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth.\n",
"icon": "bluetooth.png",
"type": "bootloader",
"tags": "battery,ble,bluetooth,gatt",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"gattbat.boot.js","url":"boot.js"}
]
}

16
apps/breath/metadata.json Normal file
View File

@ -0,0 +1,16 @@
{
"id": "breath",
"name": "Breathing App",
"shortName": "Breathing App",
"version": "0.01",
"description": "app to aid relaxation and train breath syncronicity using haptics and visualisation, also displays HR",
"icon": "app-icon.png",
"tags": "tools,health",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"breath.app.js","url":"app.js"},
{"name":"breath.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"breath.settings.json","url":"settings.json"}]
}

19
apps/bthrm/metadata.json Normal file
View File

@ -0,0 +1,19 @@
{
"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"}
]
}

View File

@ -0,0 +1,23 @@
{
"id": "buffgym",
"name": "BuffGym",
"version": "0.02",
"description": "BuffGym is the famous 5x5 workout program for the BangleJS",
"icon": "buffgym.png",
"type": "app",
"tags": "tool,outdoors,gym,exercise",
"supports": ["BANGLEJS"],
"readme": "README.md",
"interface": "buffgym.html",
"allow_emulator": false,
"storage": [
{"name":"buffgym.app.js","url":"buffgym.app.js"},
{"name":"buffgym-set.js","url":"buffgym-set.js"},
{"name":"buffgym-exercise.js","url":"buffgym-exercise.js"},
{"name":"buffgym-workout.js","url":"buffgym-workout.js"},
{"name":"buffgym-workout-a.json","url":"buffgym-workout-a.json"},
{"name":"buffgym-workout-b.json","url":"buffgym-workout-b.json"},
{"name":"buffgym-workout-index.json","url":"buffgym-workout-index.json"},
{"name":"buffgym.img","url":"buffgym-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,15 @@
{
"id": "calculator",
"name": "Calculator",
"shortName": "Calculator",
"version": "0.05",
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
"icon": "calculator.png",
"screenshots": [{"url":"screenshot_calculator.png"}],
"tags": "app,tool",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"calculator.app.js","url":"app.js"},
{"name":"calculator.img","url":"calculator-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,18 @@
{
"id": "calendar",
"name": "Calendar",
"version": "0.06",
"description": "Simple calendar",
"icon": "calendar.png",
"screenshots": [{"url":"screenshot_calendar.png"}],
"tags": "calendar",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"calendar.app.js","url":"calendar.js"},
{"name":"calendar.settings.js","url":"settings.js"},
{"name":"calendar.img","url":"calendar-icon.js","evaluate":true}
],
"data": [{"name":"calendar.json"}]
}

View File

@ -0,0 +1,17 @@
{
"id": "carcrazy",
"name": "Car Crazy",
"shortName": "Car Crazy",
"version": "0.03",
"description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.",
"icon": "carcrash.png",
"tags": "game",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"carcrazy.app.js","url":"app.js"},
{"name":"carcrazy.img","url":"app-icon.js","evaluate":true},
{"name":"carcrazy.settings.js","url":"settings.js"}
],
"data": [{"name":"CarCrazy.csv"}]
}

View File

@ -0,0 +1,16 @@
{
"id": "chargeanim",
"name": "Charge Animation",
"version": "0.02",
"description": "When charging, show a sideways charging animation and keep the screen on. When removed from the charger load the clock again.",
"icon": "icon.png",
"tags": "battery",
"supports": ["BANGLEJS", "BANGLEJS2"],
"allow_emulator": true,
"screenshots": [{"url":"bangle2-charge-animation-screenshot.png"},{"url":"bangle-charge-animation-screenshot.png"}],
"storage": [
{"name":"chargeanim.app.js","url":"app.js"},
{"name":"chargeanim.boot.js","url":"boot.js"},
{"name":"chargeanim.img","url":"app-icon.js","evaluate":true}
]
}

16
apps/choozi/metadata.json Normal file
View File

@ -0,0 +1,16 @@
{
"id": "choozi",
"name": "Choozi",
"version": "0.01",
"description": "Choose people or things at random using Bangle.js.",
"icon": "app.png",
"tags": "tool",
"supports": ["BANGLEJS"],
"readme": "README.md",
"allow_emulator": true,
"screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}],
"storage": [
{"name":"choozi.app.js","url":"app.js"},
{"name":"choozi.img","url":"app-icon.js","evaluate":true}
]
}

14
apps/chrono/metadata.json Normal file
View File

@ -0,0 +1,14 @@
{
"id": "chrono",
"name": "Chrono",
"shortName": "Chrono",
"version": "0.01",
"description": "Single click BTN1 to add 5 minutes. Single click BTN2 to add 30 seconds. Single click BTN3 to add 5 seconds. Tap to pause or play to timer. Double click BTN1 to reset. When timer finishes the watch vibrates.",
"icon": "chrono.png",
"tags": "tool",
"supports": ["BANGLEJS"],
"storage": [
{"name":"chrono.app.js","url":"chrono.js"},
{"name":"chrono.img","url":"chrono-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,17 @@
{
"id": "chronowid",
"name": "Chrono Widget",
"shortName": "Chrono Widget",
"version": "0.05",
"description": "Chronometer (timer) which runs as widget.",
"icon": "app.png",
"tags": "tool,widget",
"supports": ["BANGLEJS","BANGLEJS2"],
"screenshots": [{"url":"screenshot.png"}],
"readme": "README.md",
"storage": [
{"name":"chronowid.wid.js","url":"widget.js"},
{"name":"chronowid.app.js","url":"app.js"},
{"name":"chronowid.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -7,3 +7,7 @@
Make circles and text slightly bigger Make circles and text slightly bigger
0.05: Show correct percentage values in circles 0.05: Show correct percentage values in circles
Show humidity as weather circle data Show humidity as weather circle data
0.06: Allow settings empty circles
Support to choose between humidity and wind speed for weather circle progress
Support to show time and progress until next sunrise or sunset
Load daily steps from Bangle health if available

View File

@ -5,23 +5,20 @@ A clock with circles for different data at the bottom in a probably familiar sty
By default the time, date and day of week is shown. By default the time, date and day of week is shown.
It can show the following information (this can be configured): It can show the following information (this can be configured):
* Steps (requires [pedometer widget](https://banglejs.com/apps/#pedometer)) * Steps
* Steps distance (depending on steps) * Steps distance
* Heart rate (automatically updates when screen is on and unlocked) * Heart rate (automatically updates when screen is on and unlocked)
* Battery (including charging status and battery low warning) * Battery (including charging status and battery low warning)
* Weather (requires [weather app](https://banglejs.com/apps/#weather)) * Weather (requires [weather app](https://banglejs.com/apps/#weather))
* Humidity as circle progress * Humidity or wind speed as circle progress
* Temperature inside circle * Temperature inside circle
* Condition as icon below circle * Condition as icon below circle
* Time and progress until next sunrise or sunset (requires [my location app](https://banglejs.com/apps/#mylocation))
## Screenshots ## Screenshots
![Screenshot dark theme](screenshot-dark.png) ![Screenshot dark theme](screenshot-dark.png)
![Screenshot light theme](screenshot-light.png) ![Screenshot light theme](screenshot-light.png)
# TODO
* Add sunrise and sunset
* Display moon instead of sun during night on weather circle
## Creator ## Creator
Marco ([myxor](https://github.com/myxor)) Marco ([myxor](https://github.com/myxor))

View File

@ -1,6 +1,7 @@
const locale = require("locale"); const locale = require("locale");
const heatshrink = require("heatshrink"); const heatshrink = require("heatshrink");
const storage = require("Storage"); const storage = require("Storage");
const SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
const shoesIcon = heatshrink.decompress(atob("h0OwYJGgmAAgUBkgECgVJB4cSoAUDyEBkARDpADBhMAyQRBgVAkgmDhIUDAAuQAgY1DAAYA=")); const shoesIcon = heatshrink.decompress(atob("h0OwYJGgmAAgUBkgECgVJB4cSoAUDyEBkARDpADBhMAyQRBgVAkgmDhIUDAAuQAgY1DAAYA="));
const shoesIconGreen = heatshrink.decompress(atob("h0OwYJGhIEDgVIAgUEyQKDkmACgcggVACIeQAYMSgIRCgmApIbDiQUDAAkBkAFDGoYAD")); const shoesIconGreen = heatshrink.decompress(atob("h0OwYJGhIEDgVIAgUEyQKDkmACgcggVACIeQAYMSgIRCgmApIbDiQUDAAkBkAFDGoYAD"));
@ -11,6 +12,7 @@ const powerIconRed = heatshrink.decompress(atob("h0OwYQNoAEDyAEDkgEDpIFDiVJBweSA
const weatherCloudy = heatshrink.decompress(atob("iEQwYWTgP//+AAoMPAoPwAoN/AocfAgP//0AAgQAB/AFEABgdDAAMDDohMRA")); const weatherCloudy = heatshrink.decompress(atob("iEQwYWTgP//+AAoMPAoPwAoN/AocfAgP//0AAgQAB/AFEABgdDAAMDDohMRA"));
const weatherSunny = heatshrink.decompress(atob("iEQwYLIg3AAgVgAQMMAo8Am3YAgUB23bAoUNAoIUBjYFCsOwBYoFDDpFgHYI1JI4gFGAAYA=")); const weatherSunny = heatshrink.decompress(atob("iEQwYLIg3AAgVgAQMMAo8Am3YAgUB23bAoUNAoIUBjYFCsOwBYoFDDpFgHYI1JI4gFGAAYA="));
const weatherMoon = heatshrink.decompress(atob("iEQwIFCgOAh/wj/4n/8AId//wBBBIoRBCoIZBDoI"));
const weatherPartlyCloudy = heatshrink.decompress(atob("iEQwYQNv0AjgGDn4EDh///gFChwREC4MfxwIBv0//+AC4X4j4FCv/AgfwgED/wIBuAaBBwgFDgP4gf/AAXABwIEBDQQAEA==")); const weatherPartlyCloudy = heatshrink.decompress(atob("iEQwYQNv0AjgGDn4EDh///gFChwREC4MfxwIBv0//+AC4X4j4FCv/AgfwgED/wIBuAaBBwgFDgP4gf/AAXABwIEBDQQAEA=="));
const weatherRainy = heatshrink.decompress(atob("iEQwYLIg/gAgUB///wAFBh/AgfwgED/wIBuEAj4OCv0AjgaCh/4AocAnAFBFIU4EAM//gRBEAIOBhw1C/AmDAosAC4JNIAAg")); const weatherRainy = heatshrink.decompress(atob("iEQwYLIg/gAgUB///wAFBh/AgfwgED/wIBuEAj4OCv0AjgaCh/4AocAnAFBFIU4EAM//gRBEAIOBhw1C/AmDAosAC4JNIAAg"));
const weatherPartlyRainy = heatshrink.decompress(atob("h0OwYJGjkAnAFCj+AAgU//4FCuEA8EAg8ch/4gEB4////AAoIIBCIMD/wgCg4bBg/8BwMD+AgBh4ZBDQf/FIIABh4IBgAA==")); const weatherPartlyRainy = heatshrink.decompress(atob("h0OwYJGjkAnAFCj+AAgU//4FCuEA8EAg8ch/4gEB4////AAoIIBCIMD/wgCg4bBg/8BwMD+AgBh4ZBDQf/FIIABh4IBgAA=="));
@ -18,6 +20,9 @@ const weatherSnowy = heatshrink.decompress(atob("iEQwYROn/8AocH8AECuAFBh0Agf+CIN
const weatherFoggy = heatshrink.decompress(atob("iEQwYROn/8AgUB/EfwAFBh/AgfwgED/wIBuEABwd/4EcDQgFDgE4Fosf///8f//A/Lj/xCQIRNA=")); const weatherFoggy = heatshrink.decompress(atob("iEQwYROn/8AgUB/EfwAFBh/AgfwgED/wIBuEABwd/4EcDQgFDgE4Fosf///8f//A/Lj/xCQIRNA="));
const weatherStormy = heatshrink.decompress(atob("iEQwYLIg/gAgUB///wAFBh/AgfwgED/wIBuEAj4OCv0AjgaCh/4AoX8gE4AoQpBnAdBF4IRBDQMH/kOHgY7DAo4AOA==")); const weatherStormy = heatshrink.decompress(atob("iEQwYLIg/gAgUB///wAFBh/AgfwgED/wIBuEAj4OCv0AjgaCh/4AoX8gE4AoQpBnAdBF4IRBDQMH/kOHgY7DAo4AOA=="));
const sunSetDown = heatshrink.decompress(atob("iEQwIHEgOAAocT5EGtEEkF//wLDg1ggfACoo"));
const sunSetUp = heatshrink.decompress(atob("iEQwIHEgOAAocT5EGtEEkF//wRFgfAg1gBIY"));
let settings; let settings;
function loadSettings() { function loadSettings() {
@ -29,6 +34,7 @@ function loadSettings() {
'stepLength': 0.8, 'stepLength': 0.8,
'batteryWarn': 30, 'batteryWarn': 30,
'showWidgets': false, 'showWidgets': false,
'weatherCircleData': 'humidity',
'circle1': 'hr', 'circle1': 'hr',
'circle2': 'steps', 'circle2': 'steps',
'circle3': 'battery' 'circle3': 'battery'
@ -40,9 +46,21 @@ function loadSettings() {
} }
} }
loadSettings(); loadSettings();
/*
* Read location from myLocation app
*/
function getLocation() {
return storage.readJSON("mylocation.json", 1) || undefined;
}
let location = getLocation();
const showWidgets = settings.showWidgets || false; const showWidgets = settings.showWidgets || false;
let hrtValue; let hrtValue;
let now = Math.round(new Date().getTime() / 1000);
// layout values: // layout values:
const colorFg = g.theme.dark ? '#fff' : '#000'; const colorFg = g.theme.dark ? '#fff' : '#000';
@ -64,7 +82,6 @@ const radiusOuter = 25;
const radiusInner = 20; const radiusInner = 20;
const circleFont = "Vector:15"; const circleFont = "Vector:15";
const circleFontBig = "Vector:16"; const circleFontBig = "Vector:16";
const circleFontSmall = "Vector:13";
function draw() { function draw() {
g.clear(true); g.clear(true);
@ -93,6 +110,7 @@ function draw() {
g.setFontAlign(0, -1); g.setFontAlign(0, -1);
g.setColor(colorFg); g.setColor(colorFg);
g.drawString(locale.time(new Date(), 1), w / 2, h1 + 8); g.drawString(locale.time(new Date(), 1), w / 2, h1 + 8);
now = Math.round(new Date().getTime() / 1000);
// date & dow // date & dow
g.setFont("Vector:21"); g.setFont("Vector:21");
@ -127,19 +145,42 @@ function drawCircle(index) {
case "weather": case "weather":
drawWeather(w); drawWeather(w);
break; break;
case "sunprogress":
drawSunProgress(w);
break;
case "empty":
// we draw nothing here
return;
} }
} }
// serves as cache for quicker lookup of circle positions
let circlePositionsCache = [];
/*
* Looks in the following order if a circle with the given type is somewhere visible/configured
* 1. circlePositionsCache
* 2. settings
* 3. defaultCircleTypes
*
* In case 2 and 3 the circlePositionsCache will be updated
*/
function getCirclePosition(type) { function getCirclePosition(type) {
if (circlePositionsCache[type] >= 0) {
return circlePosX[circlePositionsCache[type]];
}
for (let i = 1; i <= 3; i++) { for (let i = 1; i <= 3; i++) {
const setting = settings['circle' + i]; const setting = settings['circle' + i];
if (setting == type) return circlePosX[i - 1]; if (setting == type) {
circlePositionsCache[type] = i - 1;
return circlePosX[i - 1];
}
} }
for (let i = 0; i < defaultCircleTypes.length; i++) { for (let i = 0; i < defaultCircleTypes.length; i++) {
if (type == defaultCircleTypes[i] && (!settings || settings['circle' + (i + 1)] == undefined)) { if (type == defaultCircleTypes[i] && (!settings || settings['circle' + (i + 1)] == undefined)) {
return circlePosX[i]; circlePositionsCache[type] = i;
} return circlePosX[i];
} }
}
return undefined; return undefined;
} }
@ -147,16 +188,12 @@ function isCircleEnabled(type) {
return getCirclePosition(type) != undefined; return getCirclePosition(type) != undefined;
} }
function drawSteps(w) { function drawSteps(w) {
if (!w) w = getCirclePosition("steps"); if (!w) w = getCirclePosition("steps");
const steps = getSteps(); const steps = getSteps();
// Draw rectangle background: drawCircleBackground(w);
g.setColor(colorBg);
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
g.setColor(colorGrey);
g.fillCircle(w, h3, radiusOuter);
const stepGoal = settings.stepGoal || 10000; const stepGoal = settings.stepGoal || 10000;
if (stepGoal > 0) { if (stepGoal > 0) {
@ -165,15 +202,9 @@ function drawSteps(w) {
drawGauge(w, h3, percent, colorBlue); drawGauge(w, h3, percent, colorBlue);
} }
g.setColor(colorBg); drawInnerCircleAndTriangle(w);
g.fillCircle(w, h3, radiusInner);
g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]); writeCircleText(w, shortValue(steps));
g.setFont(circleFont);
g.setFontAlign(0, 0);
g.setColor(colorFg);
g.drawString(shortValue(steps), w + 2, h3);
g.drawImage(shoesIcon, w - 6, h3 + radiusOuter - 6); g.drawImage(shoesIcon, w - 6, h3 + radiusOuter - 6);
} }
@ -184,12 +215,7 @@ function drawStepsDistance(w) {
const stepDistance = settings.stepLength || 0.8; const stepDistance = settings.stepLength || 0.8;
const stepsDistance = Math.round(steps * stepDistance); const stepsDistance = Math.round(steps * stepDistance);
// Draw rectangle background: drawCircleBackground(w);
g.setColor(colorBg);
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
g.setColor(colorGrey);
g.fillCircle(w, h3, radiusOuter);
const stepDistanceGoal = settings.stepDistanceGoal || 8000; const stepDistanceGoal = settings.stepDistanceGoal || 8000;
if (stepDistanceGoal > 0) { if (stepDistanceGoal > 0) {
@ -198,15 +224,9 @@ function drawStepsDistance(w) {
drawGauge(w, h3, percent, colorGreen); drawGauge(w, h3, percent, colorGreen);
} }
g.setColor(colorBg); drawInnerCircleAndTriangle(w);
g.fillCircle(w, h3, radiusInner);
g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]); writeCircleText(w, shortValue(stepsDistance));
g.setFont(circleFont);
g.setFontAlign(0, 0);
g.setColor(colorFg);
g.drawString(shortValue(stepsDistance), w + 2, h3);
g.drawImage(shoesIconGreen, w - 6, h3 + radiusOuter - 6); g.drawImage(shoesIconGreen, w - 6, h3 + radiusOuter - 6);
} }
@ -214,28 +234,18 @@ function drawStepsDistance(w) {
function drawHeartRate(w) { function drawHeartRate(w) {
if (!w) w = getCirclePosition("hr"); if (!w) w = getCirclePosition("hr");
// Draw rectangle background: drawCircleBackground(w);
g.setColor(colorBg);
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
g.setColor(colorGrey); if (hrtValue != undefined) {
g.fillCircle(w, h3, radiusOuter);
if (hrtValue != undefined && hrtValue > 0) {
const minHR = settings.minHR || 40; const minHR = settings.minHR || 40;
const percent = (hrtValue - minHR) / (settings.maxHR - minHR); const maxHR = settings.maxHR || 200;
const percent = (hrtValue - minHR) / (maxHR - minHR);
drawGauge(w, h3, percent, colorRed); drawGauge(w, h3, percent, colorRed);
} }
g.setColor(colorBg); drawInnerCircleAndTriangle(w);
g.fillCircle(w, h3, radiusInner);
g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]); writeCircleText(w, hrtValue != undefined ? hrtValue : "-");
g.setFont(circleFontBig);
g.setFontAlign(0, 0);
g.setColor(colorFg);
g.drawString(hrtValue != undefined ? hrtValue : "-", w, h3);
g.drawImage(heartIcon, w - 6, h3 + radiusOuter - 6); g.drawImage(heartIcon, w - 6, h3 + radiusOuter - 6);
} }
@ -244,25 +254,14 @@ function drawBattery(w) {
if (!w) w = getCirclePosition("battery"); if (!w) w = getCirclePosition("battery");
const battery = E.getBattery(); const battery = E.getBattery();
// Draw rectangle background: drawCircleBackground(w);
g.setColor(colorBg);
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
g.setColor(colorGrey);
g.fillCircle(w, h3, radiusOuter);
if (battery > 0) { if (battery > 0) {
const percent = battery / 100; const percent = battery / 100;
drawGauge(w, h3, percent, colorYellow); drawGauge(w, h3, percent, colorYellow);
} }
g.setColor(colorBg); drawInnerCircleAndTriangle(w);
g.fillCircle(w, h3, radiusInner);
g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]);
g.setFont(circleFont);
g.setFontAlign(0, 0);
let icon = powerIcon; let icon = powerIcon;
let color = colorFg; let color = colorFg;
@ -275,8 +274,7 @@ function drawBattery(w) {
icon = powerIconRed; icon = powerIconRed;
} }
} }
g.setColor(color); writeCircleText(w, battery + '%');
g.drawString(battery + '%', w, h3);
g.drawImage(icon, w - 6, h3 + radiusOuter - 6); g.drawImage(icon, w - 6, h3 + radiusOuter - 6);
} }
@ -285,30 +283,37 @@ function drawWeather(w) {
if (!w) w = getCirclePosition("weather"); if (!w) w = getCirclePosition("weather");
const weather = getWeather(); const weather = getWeather();
const tempString = weather ? locale.temp(weather.temp - 273.15) : undefined; const tempString = weather ? locale.temp(weather.temp - 273.15) : undefined;
const humidity = weather ? weather.hum : undefined;
const code = weather ? weather.code : -1; const code = weather ? weather.code : -1;
// Draw rectangle background: drawCircleBackground(w);
g.setColor(colorBg);
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
g.setColor(colorGrey); const data = settings.weatherCircleData || "humidity";
g.fillCircle(w, h3, radiusOuter); switch (data) {
case "humidity":
if (humidity >= 0) { const humidity = weather ? weather.hum : undefined;
drawGauge(w, h3, humidity / 100, colorYellow); if (humidity >= 0) {
drawGauge(w, h3, humidity / 100, colorYellow);
}
break;
case "wind":
if (weather) {
const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
if (wind[1] >= 0) {
if (wind[2] == "kmh") {
wind[1] = windAsBeaufort(wind[1]);
}
// wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale)
drawGauge(w, h3, wind[1] / 12, colorYellow);
}
}
break;
case "empty":
break;
} }
g.setColor(colorBg); drawInnerCircleAndTriangle(w);
g.fillCircle(w, h3, radiusInner);
g.fillPoly([w, h3, w - 25, h3 + radiusOuter + 5, w + 25, h3 + radiusOuter + 5]); writeCircleText(w, tempString ? tempString : "?");
const content = tempString ? tempString : "?";
g.setFont(content.length < 4 ? circleFont : circleFontSmall);
g.setFontAlign(0, 0);
g.setColor(colorFg);
g.drawString(content, w, h3);
if (code > 0) { if (code > 0) {
const icon = getWeatherIconByCode(code); const icon = getWeatherIconByCode(code);
@ -316,6 +321,69 @@ function drawWeather(w) {
} }
} }
function drawSunProgress(w) {
if (!w) w = getCirclePosition("sunprogress");
const percent = getSunProgress();
drawCircleBackground(w);
drawGauge(w, h3, percent, colorYellow);
drawInnerCircleAndTriangle(w);
let icon = powerIcon;
let color = colorFg;
if (isDay()) {
// day
color = colorFg;
icon = sunSetDown;
} else {
// night
color = colorGrey;
icon = sunSetUp;
}
g.setColor(color);
let text = "?";
const times = getSunData();
if (times != undefined) {
const sunRise = Math.round(times.sunrise.getTime() / 1000);
const sunSet = Math.round(times.sunset.getTime() / 1000);
if (!isDay()) {
// night
if (now > sunRise) {
// after sunRise
const upcomingSunRise = sunRise + 60 * 60 * 24;
text = formatSeconds(upcomingSunRise - now);
} else {
text = formatSeconds(sunRise - now);
}
} else {
// day, approx sunrise tomorrow:
text = formatSeconds(sunSet - now);
}
}
writeCircleText(w, text);
g.drawImage(icon, w - 6, h3 + radiusOuter - 6);
}
/*
* wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale)
*/
function windAsBeaufort(windInKmh) {
const beaufort = [2, 6, 12, 20, 29, 39, 50, 62, 75, 89, 103, 118];
let l = 0;
while (l < beaufort.length && beaufort[l] < windInKmh) {
l++;
}
return l;
}
/* /*
* Choose weather icon to display based on weather conditition code * Choose weather icon to display based on weather conditition code
* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2 * https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
@ -350,7 +418,7 @@ function getWeatherIconByCode(code) {
case 8: case 8:
switch (code) { switch (code) {
case 800: case 800:
return weatherSunny; return isDay() ? weatherSunny : weatherMoon;
case 801: case 801:
return weatherPartlyCloudy; return weatherPartlyCloudy;
case 802: case 802:
@ -365,32 +433,122 @@ function getWeatherIconByCode(code) {
return undefined; return undefined;
} }
function isDay() {
const times = getSunData();
if (times == undefined) return true;
const sunRise = Math.round(times.sunrise.getTime() / 1000);
const sunSet = Math.round(times.sunset.getTime() / 1000);
return (now > sunRise && now < sunSet);
}
function formatSeconds(s) {
if (s > 60 * 60) { // hours
return Math.round(s / (60 * 60)) + "h";
}
if (s > 60) { // minutes
return Math.round(s / 60) + "m";
}
return "<1m";
}
function getSunData() {
if (location != undefined && location.lat != undefined) {
// get today's sunlight times for lat/lon
return SunCalc.getTimes(new Date(), location.lat, location.lon);
}
return undefined;
}
/*
* Calculated progress of the sun between sunrise and sunset in percent
*
* Taken from rebble app and modified
*/
function getSunProgress() {
const times = getSunData();
if (times == undefined) return 0;
const sunRise = Math.round(times.sunrise.getTime() / 1000);
const sunSet = Math.round(times.sunset.getTime() / 1000);
if (isDay()) {
// during day
const dayLength = sunSet - sunRise;
if (now > sunRise) {
return (now - sunRise) / dayLength;
} else {
return (sunRise - now) / dayLength;
}
} else {
// during night
if (sunSet < sunRise) {
const upcomingSunRise = sunRise + 60 * 60 * 24;
return 1 - (upcomingSunRise - now) / (upcomingSunRise - sunSet);
} else {
const lastSunSet = sunSet - 60 * 60 * 24;
return (now - lastSunSet) / (sunRise - lastSunSet);
}
}
}
/*
* Draws the background and the grey circle
*/
function drawCircleBackground(w) {
// Draw rectangle background:
g.setColor(colorBg);
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
// Draw grey background circle:
g.setColor(colorGrey);
g.fillCircle(w, h3, radiusOuter);
}
function drawInnerCircleAndTriangle(w) {
// Draw inner circle
g.setColor(colorBg);
g.fillCircle(w, h3, radiusInner);
// Draw triangle which covers the bottom of the circle
g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]);
}
function radians(a) { function radians(a) {
return a * Math.PI / 180; return a * Math.PI / 180;
} }
/*
* This draws the actual gauge consisting out of lots of little filled circles
*/
function drawGauge(cx, cy, percent, color) { function drawGauge(cx, cy, percent, color) {
const offset = 15; const offset = 15;
const end = 345; const end = 345;
const r = radiusInner + 3; const radius = radiusInner + 3;
const size = radiusOuter - radiusInner - 2;
if (percent <= 0) return; if (percent <= 0) return;
if (percent > 1) percent = 1; if (percent > 1) percent = 1;
const startrot = -offset; const startRotation = -offset;
const endrot = startrot - ((end - offset) * percent); const endRotation = startRotation - ((end - offset) * percent);
g.setColor(color); g.setColor(color);
const size = radiusOuter - radiusInner - 2; for (let i = startRotation; i > endRotation - size; i -= size) {
// draw gauge x = cx + radius * Math.sin(radians(i));
for (let i = startrot; i > endrot - size; i -= size) { y = cy + radius * Math.cos(radians(i));
x = cx + r * Math.sin(radians(i));
y = cy + r * Math.cos(radians(i));
g.fillCircle(x, y, size); g.fillCircle(x, y, size);
} }
} }
function writeCircleText(w, content) {
if (content == undefined) return;
g.setFont(content.length < 4 ? circleFontBig : circleFont);
g.setFontAlign(0, 0);
g.setColor(colorFg);
g.drawString(content, w, h3);
}
function shortValue(v) { function shortValue(v) {
if (isNaN(v)) return '-'; if (isNaN(v)) return '-';
if (v <= 999) return v; if (v <= 999) return v;
@ -405,6 +563,9 @@ function shortValue(v) {
} }
function getSteps() { function getSteps() {
if (Bangle.getHealthStatus) {
return Bangle.getHealthStatus("day").steps;
}
if (WIDGETS && WIDGETS.wpedom !== undefined) { if (WIDGETS && WIDGETS.wpedom !== undefined) {
return WIDGETS.wpedom.getSteps(); return WIDGETS.wpedom.getSteps();
} }

View File

@ -0,0 +1,21 @@
{ "id": "circlesclock",
"name": "Circles clock",
"shortName":"Circles clock",
"version":"0.06",
"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"}],
"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"}
]
}

View File

@ -6,8 +6,12 @@
settings[key] = value; settings[key] = value;
storage.write(SETTINGS_FILE, settings); storage.write(SETTINGS_FILE, settings);
} }
var valuesCircleTypes = ["steps", "stepsDist", "hr", "battery", "weather"];
var namesCircleTypes = ["steps", "distance", "heart", "battery", "weather"]; const valuesCircleTypes = ["steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "empty"];
const namesCircleTypes = ["steps", "distance", "heart", "battery", "weather", "sun progress", "empty"];
const weatherData = ["humidity", "wind", "empty"];
E.showMenu({ E.showMenu({
'': { 'title': 'circlesclock' }, '': { 'title': 'circlesclock' },
'< Back': back, '< Back': back,
@ -76,21 +80,27 @@
format: () => (settings.showWidgets ? 'Yes' : 'No'), format: () => (settings.showWidgets ? 'Yes' : 'No'),
onchange: x => save('showWidgets', x), onchange: x => save('showWidgets', x),
}, },
'weather circle': {
value: settings.weatherCircleData ? weatherData.indexOf(settings.weatherCircleData) : 0,
min: 0, max: 2,
format: v => weatherData[v],
onchange: x => save('weatherCircleData', weatherData[x]),
},
'left': { 'left': {
value: settings.circle1 ? valuesCircleTypes.indexOf(settings.circle1) : 0, value: settings.circle1 ? valuesCircleTypes.indexOf(settings.circle1) : 0,
min: 0, max: 4, min: 0, max: 6,
format: v => namesCircleTypes[v], format: v => namesCircleTypes[v],
onchange: x => save('circle1', valuesCircleTypes[x]), onchange: x => save('circle1', valuesCircleTypes[x]),
}, },
'middle': { 'middle': {
value: settings.circle2 ? valuesCircleTypes.indexOf(settings.circle2) : 2, value: settings.circle2 ? valuesCircleTypes.indexOf(settings.circle2) : 2,
min: 0, max: 4, min: 0, max: 6,
format: v => namesCircleTypes[v], format: v => namesCircleTypes[v],
onchange: x => save('circle2', valuesCircleTypes[x]), onchange: x => save('circle2', valuesCircleTypes[x]),
}, },
'right': { 'right': {
value: settings.circle3 ? valuesCircleTypes.indexOf(settings.circle3) : 3, value: settings.circle3 ? valuesCircleTypes.indexOf(settings.circle3) : 3,
min: 0, max: 4, min: 0, max: 6,
format: v => namesCircleTypes[v], format: v => namesCircleTypes[v],
onchange: x => save('circle3', valuesCircleTypes[x]), onchange: x => save('circle3', valuesCircleTypes[x]),
} }

View File

@ -0,0 +1,13 @@
{
"id": "clickms",
"name": "Click Master",
"version": "0.01",
"description": "Get several friends to start the game, then compete to see who can press BTN1 the most!",
"icon": "click-master.png",
"tags": "game",
"supports": ["BANGLEJS"],
"storage": [
{"name":"clickms.app.js","url":"click-master.js"},
{"name":"clickms.img","url":"click-master-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,17 @@
{
"id": "cliclockJS2Enhanced",
"name": "Commandline-Clock JS2 Enhanced",
"shortName": "CLI-Clock JS2",
"version": "0.03",
"description": "Simple CLI-Styled Clock with enhancements. Modes that are hard to use and unneded are removed (BPM, battery info, memory ect) credit to hughbarney for the original code and design. Also added HID media controlls, just swipe on the clock face to controll the media! Gadgetbride support coming soon(hopefully) Thanks to t0m1o1 for media controls!",
"icon": "app.png",
"screenshots": [{"url":"screengrab.png"}],
"type": "clock",
"tags": "clock,cli,command,bash,shell",
"supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"cliclockJS2Enhanced.app.js","url":"app.js"},
{"name":"cliclockJS2Enhanced.img","url":"app.icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,19 @@
{
"id": "clicompleteclk",
"name": "CLI complete clock",
"shortName":"CLI cmplt clock",
"version":"0.03",
"description": "Command line styled clock with lots of information",
"icon": "app.png",
"allow_emulator": true,
"type": "clock",
"tags": "clock,cli,command,bash,shell,weather,hrt",
"supports" : ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"clicompleteclk.app.js","url":"app.js"},
{"name":"clicompleteclk.img","url":"app-icon.js","evaluate":true},
{"name":"clicompleteclk.settings.js","url":"settings.js"}
],
"data": [{"name":"clicompleteclk.json"}]
}

17
apps/cliock/metadata.json Normal file
View File

@ -0,0 +1,17 @@
{
"id": "cliock",
"name": "Commandline-Clock",
"shortName": "CLI-Clock",
"version": "0.15",
"description": "Simple CLI-Styled Clock",
"icon": "app.png",
"screenshots": [{"url":"screenshot_cli.png"}],
"type": "clock",
"tags": "clock,cli,command,bash,shell",
"supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"cliock.app.js","url":"app.js"},
{"name":"cliock.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,17 @@
{
"id": "clock2x3",
"name": "2x3 Pixel Clock",
"version": "0.05",
"description": "This is a simple clock using minimalist 2x3 pixel numerical digits",
"icon": "clock2x3.png",
"screenshots": [{"url":"screenshot_pixel.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"clock2x3.app.js","url":"clock2x3-app.js"},
{"name":"clock2x3.img","url":"clock2x3-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,16 @@
{
"id": "clotris",
"name": "Clock-Tris",
"version": "0.01",
"description": "A fully functional clone of a classic game of falling blocks",
"icon": "clock-tris.png",
"tags": "game",
"supports": ["BANGLEJS"],
"screenshots": [{"url":"bangle1-clock-tris-screenshot.png"}],
"allow_emulator": true,
"storage": [
{"name":"clotris.app.js","url":"clock-tris.js"},
{"name":"clotris.img","url":"clock-tris-icon.js","evaluate":true},
{"name":".trishig","url":"clock-tris-high"}
]
}

View File

@ -0,0 +1,15 @@
{
"id": "color_catalog",
"name": "Colors Catalog",
"shortName": "Colors Catalog",
"version": "0.01",
"description": "Displays RGB565 and RGB888 colors, its name and code in screen.",
"icon": "app.png",
"tags": "Color,input,buttons,touch,UI",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"color_catalog.app.js","url":"app.js"},
{"name":"color_catalog.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,17 @@
{ "id": "colorful_clock",
"name": "Colorful Analog Clock",
"shortName":"Colorful Clock",
"version":"0.03",
"description": "a colorful analog clock",
"icon": "app-icon.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"allow_emulator": true,
"screenshots": [{"url":"app-screenshot.png"}],
"readme": "README.md",
"storage": [
{"name":"colorful_clock.app.js","url":"app.js"},
{"name":"colorful_clock.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,15 @@
{
"id":"colorwheel",
"name":"Color Wheel",
"tags":"app,tool",
"version":"0.01",
"description":"a tappable wheel of good-looking colors",
"readme":"README.md",
"supports":["BANGLEJS2"],
"allow_emulator":true,
"icon":"colorwheel.png",
"storage": [
{"name":"colorwheel.app.js","url":"app.js"},
{"name":"colorwheel.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,14 @@
{
"id": "compass",
"name": "Compass",
"version": "0.05",
"description": "Simple compass that points North",
"icon": "compass.png",
"screenshots": [{"url":"screenshot_compass.png"}],
"tags": "tool,outdoors",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"compass.app.js","url":"compass.js"},
{"name":"compass.img","url":"compass-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,17 @@
{ "id": "configurable_clock",
"name": "Configurable Analog Clock",
"shortName":"Configurable Clock",
"version":"0.02",
"description": "an analog clock with several kinds of faces, hands and colors to choose from",
"icon": "app-icon.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"allow_emulator": true,
"screenshots": [{"url":"app-screenshot.png"}],
"readme": "README.md",
"storage": [
{"name":"configurable_clock.app.js","url":"app.js"},
{"name":"configurable_clock.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,16 @@
{ "id": "contourclock",
"name": "Contour Clock",
"shortName" : "Contour Clock",
"version":"0.01",
"icon": "app.png",
"description": "A Minimalist clockface with large Digits. Looks best with the dark theme",
"screenshots" : [{"url":"screenshot.png"}],
"tags": "clock",
"allow_emulator":true,
"supports" : ["BANGLEJS2"],
"type": "clock",
"storage": [
{"name":"contourclock.app.js","url":"app.js"},
{"name":"contourclock.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,21 @@
{
"id": "coretemp",
"name": "CoreTemp",
"version": "0.03",
"description": "Display CoreTemp device sensor data",
"icon": "coretemp.png",
"type": "app",
"tags": "health",
"readme": "README.md",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"coretemp.wid.js","url":"widget.js"},
{"name":"coretemp.app.js","url":"coretemp.js"},
{"name":"coretemp.recorder.js","url":"recorder.js"},
{"name":"coretemp.settings.js","url":"settings.js"},
{"name":"coretemp.img","url":"coretemp-icon.js","evaluate":true},
{"name":"coretemp.boot.js","url":"boot.js"}
],
"data": [{"name":"coretemp.json","url":"app-settings.json"}],
"screenshots": [{"url":"screenshot.png"}]
}

View File

@ -0,0 +1,14 @@
{
"id": "countdowntimer",
"name": "Countdown Timer",
"version": "0.01",
"description": "A simple countdown timer with a focus on usability",
"icon": "countdowntimer.png",
"tags": "timer,tool",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"countdowntimer.app.js","url":"countdowntimer.js"},
{"name":"countdowntimer.img","url":"countdowntimer-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,15 @@
{
"id": "counter",
"name": "Counter",
"version": "0.03",
"description": "Simple counter",
"icon": "counter_icon.png",
"tags": "tool",
"supports": ["BANGLEJS"],
"screenshots": [{"url":"bangle1-counter-screenshot.png"}],
"allow_emulator": true,
"storage": [
{"name":"counter.app.js","url":"counter.js"},
{"name":"counter.img","url":"counter-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,17 @@
{
"id": "cprassist",
"name": "CPR Assist",
"version": "0.02",
"description": "Provides assistance while performing a CPR",
"icon": "cprassist-icon.png",
"tags": "tool,firstaid",
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"screenshots": [{"url":"bangle1-CPR-assist-screenshot.png"}],
"storage": [
{"name":"cprassist.app.js","url":"cprassist.js"},
{"name":"cprassist.img","url":"cprassist-icon.js","evaluate":true},
{"name":"cprassist.settings.js","url":"settings.js"}
]
}

1
apps/crowclk/ChangeLog Normal file
View File

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

11
apps/crowclk/README.md Normal file
View File

@ -0,0 +1,11 @@
# Crow Clock
Crow Clock features the face of Mystery Science Theater's Crow T. Robot.
The code is based almost entirely on Bold Clock.
## Features
Its got Crow right there on the face! What more do you need?
![](screenshot_crow.png)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkG/4AImcikUzBpIAHmURiIXBAYMjCx0hiU/AwfzA4wWIiYJHmMSCxUxgYLKERH/+UU8kvBY/zp1FBZEhp3uDA/yBQURFw8fl3tBoKJEmQWB9vk+MfFw3/n3SEwVCn/zkgGCkni/4wFFwP/mnjkQRB93UpoDBDoM96f/+JUEmKxB+nhl8ypvtolE73kofyj1PPYKSEWAXy8UimckFoXUiUzkUuC4IqBIwogB8gWBJAPd7pGCSAJECJAfxfAXzokSp3t7wvBAYPkkNNB4ZICRoIACokkFYIAE7viogPDkQCBMYkkonuolFqtVqgGCC4hgB+bEEmlNqEFolAAQfUoQPDLgPyVYku8sArx0BAQQGB6TTFOwYABnvVgFEqnkp1FokAhoXEIoPxeYn0CANEggXBoAGCoYQEibCDVAdFqgtBGIPlPII/EC46oBpqIB73tAYPURwioCVIQwFUQIACAoIuFC5AYBpwXD8idEC5fyd4tPC58093tCoIzBRooXCO43/n3u7wXBDQPiC58uI4vkBw0Ta4oABmq5BAAVFqQXITA0wgAAEgSdGj/yTI0lC4tUC44BBL41ACwcEO43xFoMTU47YC8ne8YNFRoTAG+jXMCgUxPAvzFwS/B9qFGIgXyMA00d4Xe6QLFRghgGGAfkFwxDEJAwkBqtVWY3/iQPEJA3zUwIhGUoQADiIXPkIeGGAkzmYXBkczDIZAHGAUyolE7vuqEA73tA4MjH44gCineaYYXBbQlBPo6SCrwQDFYIFD8qMEAA0hroqEAAXkqR8GRYshqotBAAdViQWLJQcVDIVVqJELGQ0ykUikYsJ"))

153
apps/crowclk/crow_clock.js Normal file
View File

@ -0,0 +1,153 @@
var img = require("heatshrink").decompress(atob("y2WwkG/4A/AFUzAA5H/mUiiIACiAEDkUjJvUzI4UAABMBJoJM2+cyI4qRDTIYLEiU/JORIFawczHwPzAgKiCCAkjSWExaIcSaBvzkKaDTFw0BJAaARmZMDMBxJhZIJ9T+b3DclRJDSSSYHJdRJESSayKDzIAMJLpLCmIgeABAoDET0yiBLkmRyjN0RwEgEjEsHyEoMBEr/zkMAgtFSsAlBqlVJYIleXIMFp3uJb5JC73kqDieXAVU9wABJb0hineEYPkgEAcTfzkCUBJIRLdSYNVJIQjCcTjeBgFdJQhLCmZJeSwcTN7LeBiUUJQvtJa5JBqlOEQvkiUQgMvSjMCmZKGJYcjJLnu6sjkJ5BSjMj+ZKHcYURJaJJCbooACoM/GAKWXSgQEBkq5CO41FqJLPmURqjcGEQRKBGIqUUMYchOoPkgpLG6hLBFwJJT91ArwDBoQyHSib5DmJGB8lRijFG8lViUzD5EzkMVoj7IJQVSGZCURgBhDJQXtiUhPo/kGgMjJgvzSQVNCo3UEAJwCibJISiL3E+RvD+cVGo/tolBiUimYABmSSCVQy1Cka7DJQY1CAwgANkCUEM4MdXQcxiorBHA9EqMRAAMVqgQJqLUBJQXlFwqWBn5JP+SUBCYnzihKEkRLJTIQABSI5JEJQQHBJQv/kMAj7fRVIxKCoM/mIBBJZQAL8lBifzFIMlEgaCHJJyoJEoVAicxOQMyitNJKXUoMSn5KBDYJKCHA0ggKeFABEySg6eBZYNAiMhDwQvBqjWG7oABJI1EqMiOIPziUhgoKBqTOPAA7yBLY8xbAPRkURBoZLCcgXUolVAAdEUYXkotRia7C+cRiUUJRLPBgTPGb5//+Ne93lkchNIZ9CJgNFIoQADJoRIBCAJiEgMzJQPkRZDhO+SlJ+RKCl/ygMjJQfzVgNURoQAE6lFqB+BmI1C+UQifzJRfxcJswb5BABjpKBj/yiURJYJKBcQRIGAAdESoS7BLwMRifyrpuCaJMCJRbfJDIJxCJQMTmRLB+cRitOJJQAB8lFiMvmUhkfyWgK5B6rVJkEAHhDfMAAK8D+YPBJYMiJJxLCqqtCn5KEoI+JmI9LBgJjJ/8lJQoUBitNH4nUogAEJYrjBIQPxl8xMYJKKcJkgb5IABmonBqMzJQLeBJIhICA4fdJgxLBJQUjkIiBqQwJHxZWMRoInBqERJQMxqg5DIAPebo4KBd4fUSwMxiMQp3tJRbUK+TsLBoKVCkMBkUVIYSSBcYoAF9qYEoMSkIBB73kGJg/JKpQYDrouBmcVqqCCSRSYHCAPkqtQkclAoJKLapTrBC5QYBihKBn4DBQgwANUwJcCosvJQR8LIAU/KiBKHmZKCbhhLJAYPlkckA4JKMaxDqKJQgoB6MhqhJVAAPkJYPliS3DGRZBIKZAAGXwPVgtOGQQAH7oADbASXGMQNFghKOa4MDBAswdI6uHp1FrvtJIjOBqoALogUEohmBqvuoQxMJQMBBAsQgQXMJQNEbwVO9xNBqtQgAANgpNBToRQBotUiQyNkMARonzgAXO+VVO4LFB8lFJAdURINVIokVBAIQEDYIZBSwUTPp0Al45EgAXO+UF7x8B9tFIAdUHAI5BIIcFbYYSEJYQLBoAyPgEfAwfxKIoXKb4Q2FgpJCJYoSBAAdALwiWC8pKO+cQCAkxgLnEJRVeFYPtRQZAGIIRTF93ldYgcBcIJKQgQGDkBKPmIpB73kGgpKFUIMEBArrEgFe9xMBJRxECAohQEJRx/EgpAFIII9CT44ACK4NN9xKPbQibBgZKXRYXkoiMETwYKDoJKGSqBKGCx/yr3kpxKHoEFIodVAgYKDdQIWENYIzQgEvAgcfCx8UJQLTBAAVUbQQECJQoKEJQldNIQ4CJRxcCJ4gAM+cU91EaYJ+EoCaEJQgKCgpKFM4NOoLOCGZpKDmMACx//ktOonuJRZXBJQoKBJQcF7wdBqIyP/5KWmNeYQQ0DaobbEAgZcBrxPDcwPtpvkG4QAOiECG4UBCyDhD9qWCgpBBorfDKwNUAoQKBBwIUE6lOosvGaEgJQUgJSLhCFwKFCQwSeBIgblDBQLXBUgSUCMwNRZCH/mBGCJwYAPmQvC93UJYJBDIQRUCqhSDKYSfCMoTfRJQReBJSbhCGAJLDbAVEqoAFLYJUBqEFCAKcCoLfR/8xJQPziBKS/8kIQXeGoJACotVqlNR4SlBA4JUCJ4QXCoLfRJQsDJSUlolNPoSSEJAbnEBQNUIoIRBbwNEqJKS+MAJQT4S/8hiMVGQQ/Dc4IAHBYNdJIVE9tBiMSJSkvJSvzmcxineGwVFTQZLJpwSB91FiUzmYxS+RKXJgUhZwPUaQJJKAANFqpJDSSRKdJYdEbxQAD9tVptFiJJVJThLCShziDSaxKGj4cWJYMVShoACqMjFi5KDgBKY+UUJJ/u6rCXJT0xSiDhBqRK1kveJSHuoM/JTUQWa/zb4/UogABprhHl5Kz+VeJI7oCJgLhGFrBKbmJBCHgZEF8gNF9tSJWcl7w8ERwwHBJYtBn5KyiqNLBAVNJTnxJTXziiUMBI/ll5KXn5KBgZKWjo5D9qUHT45KXmMBVwMggT8WrzfMAAThE8jEWJUPUJJLhFJS8wJQcBJSyPEHwjhHJT6ZBJS1U7xKPCAhKWbgcxgBKWitVqlN9qaEJQ9O6lFqtQJS0QJQiZBACfziEAAAJNBogAJI4QRBY4RKVMQUygEvfqxKCACJ8CPCkAJQXygEffqxKUgJKaZAL9VcAgASYaqQBC4QyBgYcWACp4VmJiEiD+VJV0Bn4FCkAFEJSNVJKlVJSpEFKApKRqlFJKUFohKU+baFcwpKRr3tS6MFp3kJS0DGYj+VJQPu8lQJKHu8sfFikACwnzJSvzinuJZ8FpoSB8rCUbI7nFJSfu6lUogAJotUCIVFJS0/A4kggIHFAB0lHAXuogEDAA3kbwIABoIrUIQKdNNJ45DJRneAgVSYKjYH+UAiZKUrxKPAYYqUIJBTBD6sUHQXUJRRWD8oqU+LXHTxC1OJQfkcoZKKoIzGABswgM/BI0hgAJHABklIwRKKBQftqIpURYIWHmKfHABsxrqKGJQ3eAYTfU+cACxHyiAhVcJrfZRQMjGZCgJABkhaQaWHKYfkqQnUkEBCxMxgJtU+SWK8hSDop9IXpiJBGZTsJSxtNbAZEDJIPeSjA9MK5gkLiqSDJYIACJIXuoKUUaYMAaZbtLEplUbgft7pIDbwMSEiiHBHhhYBcKvzkJLEAAlFiJuVb4LSM+ThWJYbjDTIRJBYxbfYcIUAOSpLCitUogACqsSkYgWb5rhZFQcAgtVqEAgKTWb4USGB8CSyywCAAZ6OABMhb5wwDOy5Kdb6CnSJU0xgETLsRKjGwMADCJeSJUUyZiaWYJTfzDgMjCyUhSyxKb+UAgQXT+SWWfIIADgSUqPwaWTmZKGmZnSmSsWSx3zmczkUiiIACiDgFBQYQBCgIiLPioADkMAiSKHmUikMRitVqtEAAVFJQkFBQYQBqMRikikZOHV4MCSiqWEfQaPBRoJGBHANN73uAAfVJQkEBYnt6hPCJwJNBIQfzV4IuDSzAjBmaPBIxBKPAAhOCiMSIgQtEAC5nCicyiNUog2JJSZNDopMBmSUbfocAgtNGhhKVAAPkqATBiZJaWgcFpxKk91AgEBbzIAD+S1BqneGZvlJQlUJJ1FJILebAAcyJYQ0N9tEqoABqirO6jfBiRJe//zcQVNZh4AQ8hJBgTedJYkgJYKCOJKbegAAfycQKXeJM5LFohJa6hJoAAMyJYVU7xJXohJCiZJmJYkAqtEACtFJIc/JVBLEJoQARI4IABboJJqJYzlBbZ9VJIhIrAAXzkJMEolNI5HUJAkAiSSsTA0Rco1UogACbYsAiLctTBBMGABEBiMimZIzAAczmUhJpBHBiUjJHCZEmczkQAFI4La0AGoA=="));
var hour_hand = {
width : 61, height : 8, bpp : 1,
transparent : 0,
buffer : E.toArrayBuffer(atob("/////////////////////////////////////////////////////////////////////////////////w=="))
};
var minute_hand = {
width : 110, height : 4, bpp : 1,
transparent : 0,
buffer : E.toArrayBuffer(atob("/////////////////////////////////////////////////////////////////////////w=="))
};
//g.fillRect(0,24,239,239); // Apps area
let intervalRef = null;
const p180 = Math.PI/180;
const clock_center = {x:Math.floor((g.getWidth()-1)/2), y:24+Math.floor((g.getHeight()-25)/2)};
// ={ x: 119, y: 131 }
const radius = Math.floor((g.getWidth()-24+1)/2); // =108
let tick0 = Graphics.createArrayBuffer(30,8,1,{msb:true});
tick0.fillRect(0,0,tick0.getWidth()-1, tick0.getHeight()-1);
let tick5 = Graphics.createArrayBuffer(20,6,1,{msb:true});
tick5.fillRect(0,0,tick5.getWidth()-1, tick5.getHeight()-1);
let tick1 = Graphics.createArrayBuffer(8,4,1,{msb:true});
tick1.fillRect(0,0,tick1.getWidth()-1, tick1.getHeight()-1);
// Adjust hand lengths to be within 'tick' points
minute_hand.width=radius-tick1.getWidth()-6;
hour_hand.width=radius-tick5.getWidth()-6;
function big_wheel_x(angle){
return clock_center.x + radius * Math.cos(angle*p180);
}
function big_wheel_y(angle){
return clock_center.y + radius * Math.sin(angle*p180);
}
function rotate_around_x(center_x, angle, tick){
return center_x + Math.cos(angle*p180) * tick.getWidth()/2;
}
function rotate_around_y(center_y, angle, tick){
return center_y + Math.sin(angle*p180) * tick.getWidth()/2;
}
function hour_pos_x(angle){
return clock_center.x + Math.cos(angle*p180) * hour_hand.width/2;
}
function hour_pos_y(angle){
return clock_center.y + Math.sin(angle*p180) * hour_hand.width/2;
}
function minute_pos_x(angle){
return clock_center.x + Math.cos(angle*p180) * minute_hand.width/2;
}
function minute_pos_y(angle){
return clock_center.y + Math.sin(angle*p180) * minute_hand.width/2;
}
function minute_angle(date){
//let minutes = date.getMinutes() + date.getSeconds()/60;
let minutes = date.getMinutes();
return 6*minutes - 90;
}
function hour_angle(date){
let hours= date.getHours() + date.getMinutes()/60;
return 30*hours - 90;
}
function draw_clock(){
//console.log("draw_clock");
let date = new Date();
g.reset();
g.clearRect(0,24,239,239); // clear app area
g.drawImage(img, 12, 24);
// draw cross lines for testing
// g.setColor(1,0,0);
// g.drawLine(clock_center.x - radius, clock_center.y, clock_center.x + radius, clock_center.y);
// g.drawLine(clock_center.x, clock_center.y - radius, clock_center.x, clock_center.y + radius);
g.setColor(g.theme.fg);
let ticks = [0, 90, 180, 270];
ticks.forEach((item)=>{
let agl = item+180;
g.drawImage(tick0.asImage(), rotate_around_x(big_wheel_x(item), agl, tick0), rotate_around_y(big_wheel_y(item), agl, tick0), {rotate:agl*p180});
});
ticks = [30, 60, 120, 150, 210, 240, 300, 330];
ticks.forEach((item)=>{
let agl = item+180;
g.drawImage(tick5.asImage(), rotate_around_x(big_wheel_x(item), agl, tick5), rotate_around_y(big_wheel_y(item), agl, tick5), {rotate:agl*p180});
});
let hour_agl = hour_angle(date);
let minute_agl = minute_angle(date);
g.drawImage(hour_hand, hour_pos_x(hour_agl), hour_pos_y(hour_agl), {rotate:hour_agl*p180}); //
g.drawImage(minute_hand, minute_pos_x(minute_agl), minute_pos_y(minute_agl), {rotate:minute_agl*p180}); //
g.setColor(g.theme.fg);
g.fillCircle(clock_center.x, clock_center.y, 6);
g.setColor(g.theme.bg);
g.fillCircle(clock_center.x, clock_center.y, 3);
// draw minute ticks. Takes long time to draw!
g.setColor(g.theme.fg);
for (var i=0; i<60; i++){
let agl = i*6+180;
g.drawImage(tick1.asImage(), rotate_around_x(big_wheel_x(i*6), agl, tick1), rotate_around_y(big_wheel_y(i*6), agl, tick1), {rotate:agl*p180});
}
g.flip();
//console.log(date);
}
function clearTimers(){
//console.log("clearTimers");
if(intervalRef) {
clearInterval(intervalRef);
intervalRef = null;
//console.log("interval is cleared");
}
}
function startTimers(){
//console.log("startTimers");
if(intervalRef) clearTimers();
intervalRef = setInterval(draw_clock, 60*1000);
//console.log("interval is set");
draw_clock();
}
Bangle.on('lcdPower', (on) => {
if (on) {
//console.log("lcdPower: on");
Bangle.drawWidgets();
startTimers();
} else {
//console.log("lcdPower: off");
clearTimers();
}
});
Bangle.on('faceUp',function(up){
//console.log("faceUp: " + up + " LCD: " + Bangle.isLCDOn());
if (up && !Bangle.isLCDOn()) {
//console.log("faceUp and LCD off");
clearTimers();
Bangle.setLCDPower(true);
}
});
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
startTimers();
// Show launcher when button pressed
Bangle.setUI("clock");

BIN
apps/crowclk/crow_clock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,17 @@
{
"id": "crowclk",
"name": "Crow Clock",
"version": "0.01",
"description": "A simple clock based on Bold Clock that has MST3K's Crow T. Robot for a face",
"icon": "crow_clock.png",
"screenshots": [{"url":"screenshot_crow.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"crowclk.app.js","url":"crow_clock.js"},
{"name":"crowclk.img","url":"crow_clock-icon.js","evaluate":true}
]
}

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