diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..345bce54f
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: espruino
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: ['http://www.espruino.com/Donate']# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
index 1eb009153..7c0cfca3a 100644
--- a/.github/workflows/nodejs.yml
+++ b/.github/workflows/nodejs.yml
@@ -1,4 +1,4 @@
-name: Node CI
+name: build
on: [push, pull_request]
@@ -6,29 +6,22 @@ jobs:
build:
runs-on: ubuntu-latest
- strategy:
- matrix:
- node-version: [16.x]
-
steps:
- name: Checkout repository and submodules
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
with:
submodules: recursive
- - name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v1
+ - name: Use Node.js 16.x
+ uses: actions/setup-node@v3
with:
- node-version: ${{ matrix.node-version }}
- - name: install testing dependencies
- run: npm i
- - name: test all apps and widgets
- run: npm run test
- - name: install typescript dependencies
+ node-version: 16.x
+ - name: Install testing dependencies
+ run: npm ci
+ - name: Test all apps and widgets
+ run: npm test
+ - name: Install typescript dependencies
working-directory: ./typescript
run: npm ci
- - name: build types
+ - name: Build all TS apps and widgets
working-directory: ./typescript
- run: npm run build:types
- - name: build all TS apps and widgets
- working-directory: ./typescript
- run: npm run build
\ No newline at end of file
+ run: npm run build
diff --git a/.gitignore b/.gitignore
index fce2efb1a..f4588ac6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,5 @@
.htaccess
node_modules
-package-lock.json
.DS_Store
*.js.bak
appdates.csv
@@ -11,3 +10,7 @@ tests/Layout/bin/tmp.*
tests/Layout/testresult.bmp
apps.local.json
_site
+.jekyll-cache
+.owncloudsync.log
+Desktop.ini
+.sync_*.db*
diff --git a/README.md b/README.md
index 78dd1b492..d2f7022e9 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
Bangle.js App Loader (and Apps)
================================
-[](https://app.travis-ci.com/github/espruino/BangleApps)
+[](https://github.com/espruino/BangleApps/actions/workflows/nodejs.yml)
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/)
@@ -186,12 +186,12 @@ The widget example is available in [`apps/_example_widget`](apps/_example_widget
Widgets are just small bits of code that run whenever an app that supports them
calls `Bangle.loadWidgets()`. If they want to display something in the 24px high
-widget bar at the top of the screen they can add themselves to the global
+widget bar at the top of the screen they can add themselves to the global
`WIDGETS` array with:
```
WIDGETS["mywidget"]={
- area:"tl", // tl (top left), tr (top right)
+ area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
sortorder:0, // (Optional) determines order of widgets in the same corner
width: 24, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
draw:draw // called to draw the widget
@@ -226,10 +226,8 @@ and which gives information about the app for the Launcher.
"name":"Short Name", // for Bangle.js menu
"icon":"*myappid", // for Bangle.js menu
"src":"-myappid", // source file
- "type":"widget/clock/app/bootloader", // optional, default "app"
- // if this is 'widget' then it's not displayed in the menu
- // 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
+ "type":"widget/clock/app/bootloader/...", // optional, default "app"
+ // see 'type' in 'metadata.json format' below for more options/info
"version":"1.23",
// added by BangleApps loader on upload based on metadata.json
"files:"file1,file2,file3",
@@ -252,17 +250,24 @@ and which gives information about the app for the Launcher.
"version": "0v01", // the version of this app
"description": "...", // long description (can contain markdown)
"icon": "icon.png", // icon in apps/
- "screenshots" : [ { url:"screenshot.png" } ], // optional screenshot for app
+ "screenshots" : [ { "url":"screenshot.png" } ], // optional screenshot for app
"type":"...", // optional(if app) -
// 'app' - an application
// 'clock' - a clock - required for clocks to automatically start
// 'widget' - a widget
- // 'launch' - replacement launcher app
- // 'bootloader' - code that runs at startup only
+ // 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js'
+ // 'settings' - apps that appear in Settings->Apps (with appname.settings.js) but that have no 'app.js'
// 'RAM' - code that runs and doesn't upload anything to storage
+ // 'launch' - replacement 'Launcher'
+ // 'textinput' - provides a 'textinput' library that allows text to be input on the Bangle
+ // 'scheduler' - provides 'sched' library and boot code for scheduling alarms/timers
+ // (currently only 'sched' app)
+ // 'notify' - provides 'notify' library for showing notifications
+ // 'locale' - provides 'locale' library for language-specific date/distance/etc
+ // (a version of 'locale' is included in the firmware)
"tags": "", // comma separated tag list for searching
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
- "dependencies" : { "notify":"type" } // optional, app 'types' we depend on
+ "dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above)
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
"readme": "README.md", // if supplied, a link to a markdown-style text file
@@ -319,7 +324,7 @@ and which gives information about the app for the Launcher.
```
* name, icon and description present the app in the app loader.
-* tags is used for grouping apps in the library, separate multiple entries by comma. Known tags are `tool`, `system`, `clock`, `game`, `sound`, `gps`, `widget`, `launcher` or empty.
+* tags is used for grouping apps in the library, separate multiple entries by comma. Known tags are `tool`, `system`, `clock`, `game`, `sound`, `gps`, `widget`, `launcher`, `bluetooth` or empty.
* 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
@@ -415,7 +420,7 @@ Example `settings.js`
// make sure to enclose the function in parentheses
(function(back) {
let settings = require('Storage').readJSON('myappid.json',1)||{};
- if (typeof settings.monkeys !== "number") settings.monkeys = 12; // default value
+ if (typeof settings.monkeys !== "number") settings.monkeys = 12; // default value
function save(key, value) {
settings[key] = value;
require('Storage').write('myappid.json', settings);
diff --git a/_config.yml b/_config.yml
index 2f7efbeab..c74188174 100644
--- a/_config.yml
+++ b/_config.yml
@@ -1 +1 @@
-theme: jekyll-theme-minimal
\ No newline at end of file
+theme: jekyll-theme-slate
\ No newline at end of file
diff --git a/android.html b/android.html
new file mode 100644
index 000000000..93999008f
--- /dev/null
+++ b/android.html
@@ -0,0 +1,352 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Bangle.js App Loader
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Default
+ Clocks
+ Games
+ Tools
+ Widgets
+ Bluetooth
+ Outdoors
+ Favourites
+
+
+ Sort by:
+ None
+ New
+ Updated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/2ofthemclk/ChangeLog b/apps/2ofthemclk/ChangeLog
new file mode 100644
index 000000000..2286a7f70
--- /dev/null
+++ b/apps/2ofthemclk/ChangeLog
@@ -0,0 +1 @@
+0.01: New App!
\ No newline at end of file
diff --git a/apps/2ofthemclk/README.md b/apps/2ofthemclk/README.md
new file mode 100644
index 000000000..7ac2cf779
--- /dev/null
+++ b/apps/2ofthemclk/README.md
@@ -0,0 +1,11 @@
+# two of them clock
+
+You can now wear teh memez on your wrist.
+
+
+
+Also serves as an example of displaying seconds only when unlocked or charging and only refreshing on the minute otherwise.
+Widgets not supported
+
+## Creator
+- [Kilrah](https://github.com/kilrah)
diff --git a/apps/2ofthemclk/app-icon.js b/apps/2ofthemclk/app-icon.js
new file mode 100644
index 000000000..9bfb8a550
--- /dev/null
+++ b/apps/2ofthemclk/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwgZC/AH4ADkAPOgVJkgEBAQQAJiQRByEJgmQCJWSpMEAQMkyQJCpASHhAOBpAmBJJgjBCIUJCRg4CCIJxFMQ2SoARCkmACI0EBAJHCCIMLj4RFiUBskAgIXBEAU5A4P34CtCiEJsEJ/AHBCgOBAoQAEi0H////HciQsBwywICIXWzkG4A+BEY0gif46dt6/cgnIgkWnHfLIP/MoUWwHbpvC/kAjEEj0HNYQCCkEfGgP/64RB2EAifHLwMAjg1CCIMD/0H/0B8EAh+HgeAkARCE4IjC/4jBYIMPLIcIAYUPB4OBCIQABhu/AoShCHYIRBx6QBDgUw2//8OHPwcJ39//ILBCIU9LgMBSQgsBJAYRBkE/CIIABgRHD3wRFkk/2zBDAYU//3b/oRB8ARBj6ABgEE7YREEYf+oMkSwINCyClCn//z//+4RBgMkgU3EgUcwFJgEeboOXCIP2EYJCDAAVJkkGWoIuBgf2EYQPDkECCIOGd4ffyEJkgFBAAcSoEkwQCBhw+BwQaByVAkGAKwIFBBANLkEQgAyBCIVIkBpBgmSBYOQoApBgcgiQRCAQIyCCgsSjIFBCIcgRgJNCCgQyBpAgDAQT2BCgIOBBAQUCCIpfBCIwCKP4QRNpCSDCLyJBCIbjBTwYRLboJ0BCI4QD"))
diff --git a/apps/2ofthemclk/app.js b/apps/2ofthemclk/app.js
new file mode 100644
index 000000000..78415fba7
--- /dev/null
+++ b/apps/2ofthemclk/app.js
@@ -0,0 +1,90 @@
+const img = require("heatshrink").decompress(atob("2GwgZC/ADEIAQMBgEQAgMChEEAoQA4wACEAHKDBAQYA9X/YACgQFGkBH1HAQFCwEIgkAAQVAH2GAAwtBQwcQgMAwQ/vGQI4CAQRHCwFAkACCQYTRFAEqzBAopHCgMEAQiSHAEsQAYT1ChBEEPoMQBAMQAoQRCX9BEDOgR6BHwkgwVBQw4aEIlKGCPoWCI4REBBAMEQYUCH856CNwJ9CO4YCBHwOSgCGBRIREBJQICBAAOAH0UQGQJ0CHYTCDAQOQpKABZAICCZAREBQccgIgJHBoJrBXIQFBIISDFwALBKwTEkd4RrBPobIBHAMkIgICBQYoODSoIfBRIYmEACjjBMoIgBwAsEF4YFDkmSAoUBBATgDDQIjEADSqBEwI4BgJxCPoUEiVIkmCpI7BoLCBpBTEC4MChAjEfy6hCL4J3EAQMJHwLCDBwJEBQYMkiQODI4JcBQAICDEwKMCRK4dBYoJrCHYMgyB3BHYWShKGCBweAJoLXDQAJiBZAQAVC4ZZBU4KADPQR3BQYLCByA+BBAWCJoabCUIR6BcwQIFaIQAPC4KABwC8CoC/DPQSDDoICBHwREBJQabBHwRHBNYSqDZYQAMCIQCBQYT7DAQKADQwVJAQY7BAoJEBHAJZCQwYmBEANANATXBI4IvBPpJQFiEAPoYCBWwg7CBYIFCAQIFGcAYCBX4ImBIgQaBBwLDPwCDCoMAEYS/BfwJECoJEDAQuCSQLOBBwJ9DQYY7BHwaDDABYRBQwIgBDQMSVoMSHwK5BIgKDDIIyACKwMEyDLChKGEZwckIgWCIhwXBYQY7BEwJHBHZICHSoSeCMogCCQYYOBYho+BTwQmBQYQvCQAUSIJyACZAKDCMobsDEANAH5hTDTYYmDF4ICBQaICCLgSeBEAKACJohKCRgJBIwCeBC4IdBEALsBQAYCCHx4XBLIRlBkkQRgRBBdgMEJoSDMgJEBKwRcCU4J9SAQYaCGoI4BAoUQVobFBJQL4BGoICBAA4UDL4QsCiRBVAQjdBiS8CAQMShLRDfAUQH44+BC4sgyAjBHzC/CGoKnCySqCagJuCiEIQYNAQZIRCyAXBQALFXQxBHEaIQIBQYZNBH4sSQYVAKwIgBIgQ+ZMQYFBHwMBVQiGCGoILBIAsEgBNBTwWCZASAdMQkSVQaDBKAOCXIVBZALLBIIY+BwBWBQYbCfQYbCBEwRHBRIRxBQwMCQgiYDCIOShBcCQbxEBEYJHChIIBAQMQQYK5CwFAH4JHBiCYCQYg+eUgaDBAQYLCeoR6BAQUBQYcCQYJNCCgRBgQAZrBBAZ0BYoUQAQMAQYaJBKwLaDDQgCdQA6MCBYI4BiEJIgKDCYoVAHwIOBCgRBggTvChLCDRIMEGQTFBAQKDCgAOBhI+kyVIj4mBgkCRgzFGgRHBQYRKBKAJcCIkP/+UIJpB3BJoMQAoOQoBBBgCYDQclJgF+YQZEIkkQKAKDBgGAQAKSBQYNBIMfgh0BHY2SXgI1BQANIgjLCQYKPBLJZBcgPBggIDoI+BHYL4CgUBBAI/BwDIBKYUSIMsAnApFfYQABHwOQoEEiACCZYKPBQctIGwSDEfYIAEX4L7BiQLCQYTFmpEDwAuBgSAByVAIIoABhMAYoKDBH0xBHGoJ6BABMkYoJTBAoTFqgf+H5QABkGQYQOAQdNPGQUcvxBMgGAoMEiVBkmQExYOBILkfwP4jhAKgmShEEYRxNBCIJEBiVJkBKSo4zDv//8CDLFgTdBkmCExWCC4gFBIIM5IK8fwDHMEx0EDBEB/5BQoED+EAgbFBQZIjBAYK2CVozRBAoMSLRUPDoJBPvEAvw1FEZJNBgI4DAQa2CAoKeKgf+g/kIJ84//+IIX4AYMHgfx4Ech0HjgqFiUJDol/8AFBkANBUJDsC8ECIJuT/H/MQ0H8f+n//x/AFQwmFyE4j4FBL4PHKwwAB/gmBJoNBTAYCIyP4CgJBG//wn/j+CvIgLFFgFwIIMAnAUHHwU4AQMIQZmf/5BIIgjyJII0OAQMPTA8B/4CBBYjILv//cZAIBMQRBNyAHCn41B/AXEMQMP/AcHhJBIk//KwIAJQwRBRKwN/4BBC8Fx4Hj/5BBJoJBNpEDCgQAWII8AMgP4j+PAgIADYQMPOIsBQZEfwE4IMEfHooAD/EDwE/dIqDI/g4QEYJBLg//+ARBIJKDBAA8EII1HcAZBN/kBEwxBFMQSDKDQSzEuACBIIySDIJsHgEOQZkcIJeOn4XCGQhHBiRBGOgZALuAaBIJeSg4vCSoJBHwP48F/45xDvxBKDoKCMTwQAHEAeRaIccIIuP/5rBg///x0DgeB44EBIIpKBv//8EPfAwAGQZeTDQN/DoU///x44+Bn6tCj/+cwJBCwAFCIImRQAMPIIIaBH5UHgf+IJf4jggDg59C8EOAoKhBj47DEwYCBhIgE/4RBn4gEIJKnBIJkfXwL4C/kBJQJBB8fwCwSJDAAK8BIIrmBIIIdBBgP/DQYAGBZDmIF4XgIIMD+E4XgIKBSQJuFHAMBIIn4/4UBAQf/45oCAAb1BVQRBJyCtCNwQCBuE48eABAQgCFYJHCAAMcgBBEyE/OIUfWwIVBLIxiBWwOOYpZZBhwJBgPAgF+JQU/IIopDMoK8CEAeAagUHgf/4BBCGw0B/5BNDAIODHwN+AgNxLgI1B8CDJUgoLDMoZ6BGwxoBj6VBIJORLYXAfwMHHAPAjgjBHYMHB4RBFEAwLFh6kBgJBDZAhBSNwMH/l/HwP/QYKtEIIk/KwIgEiE4BYM4YoP48DXBEwM/8Y7Dn//IJ74BhxBBh4IDAAPHHwIgBIIUB47RCgiDDHwIvCUIIvCL4P/YoqJEIJn/+I+FAAPwZwMBQwK5EIIxxCx0AU4Q0HQYjmLyAOBIJXgZwILBZwIgEBYJBDkGAg68BgDFBIIMB/BBIQZhBEbod/IIZfDAARBEgYFBkCDDg6/EHwMBYQhHDeQJBLwYwC4ED/wMBh4ICEwYOCIIg4DQYYvCa4JfB/CGBUIQmCIIU4IJmBDAP+O4SkCIIhHDIIs4IJH/xyDCMoP/46YD+IpBQZ2AHwPxX5H4AQofHIIcEIIQgBHwKGCHYQABuBZCn6MDII4gBIIahC//wC4KhBJYZBBMQomBgIgCQYMAUgQPDgLIBTYbgGQZJBCx40BRIQmCZAYAEFIyDDEALCC/8cgLmCQYhBQBwRBDHwLIHAAakGh0BIIQHCIIX/wLaDI4IFD8FwSQhBGpBBC8ZBB/kOHYSkCIIccv4CBDokcuADBQYjjDMQP8QY4IBI4ZBHSQZiEfwaDD+BfCFgIgHkCDEgEPIIUAbRDOKIIOQAofjHoWOAwJ6BYov8uLFHAALFCQYL1Bh/4L4Uf+A7Hv5BPL4UAv0HfwcBAgQADgEONgxBCAgI7BUIYADLIrjFIIyYDnEfQALpDPoStCNYTODcxCDBeohBFboKPBAQLpBIJRnD/ED/wUB+IgDjkOn7RDQYTwHIIQgC8FwIIOP45BEgCGBFgJ3DIJQoDDYPwC4OPHAIICJoStHIIsgCgPgagMfGoV/Rgf8v4eBIKK8Cx/AHAUD+E/JQM4BAXjU4yDFC4LXCQwIQEcYRfIB4cgA4pWCEYImCIIKtBwBEBJQaDKgE4GoPHGQ6hDIBIABIIk/gfxfYl+BQNx4DIDQZNJII6hBwLCDUghALQY3jGgZZCL4OPEwKPEjggGhJBEn4gBHwaDFDQ4gGIIcOYooAB+ChD/gLEj+AIJVwa4SbBACpfCIIkPEQLIEAA/HVQ7FDkkOQYP+n/gEwRBZcYI+LR4ofEgRBDkBBB8EB//gHR1wAomCIIgODX4YAJR4TFGQYihB/hBBYipiCA4hfBnBALF4SDCOgcSQYhBCAQPj+P4HRcfMQkEL4QOEOgPAgF/IJMDAYWAnDFIQYRQBg/jx4jBABUDIJAIEGIRxC8YmBIAnAAgaDBAAYgCAQSSEgeOXxn8AohBDyQHCHAYRFI4ILBPoMfx4PBwPAYpBlBn6kDABscAogdDyVIA4IgBMQICBn7sB444B45KBIgPxYoU/EIZBHa4JBEuA/HBAV+AwUBIIlJgg4C/yGD8F/YgjLFwP4IJTCB/4OEIJFxEALUDII0gh4wC/ACCEwIABX4MfIwgOCageCIImQnEcLgMcHxDCCv4vBWwIABhJBFkkBF4SGBGQI7Dd4JAGAAR6BgAgFyUHgBlCGQYAHBwMcAwYdFpIIBWwaADAAWOn4FD+ILFgEEIK8AnAEDgRBGQYLCD8AjC/w+BYoIAC/jXDAAXAQY9ADoZBMAAhBHgDjBAQSzBRIYAFeoQADGQKDHgH8gBcCMoI9KgbjCwRBGkBfBUgX8uEHj+BX4f8v0AJAxBJpBBBL4JBBQZ8SII0EgIsDAQUDX4fHRgMAj5BF/0AcwyDBAAQUBIJ5fHQYKhBO4j1BXgXwnEcNwRBHkAjGo62DIIPjMoLFJ+EBL45BD/+BHwfgZYLpCjkP/DFIEY8/GYYXBfYYAJIJMkBgLmBEAQ0DIITCGIIcBU42TX4kBMoIAMhJBLEAg0DJQM4j/wYoXHB4IFBQZGBIIgAOiA+HAQSbBj6eEIQfx/8AuEHRIUAhwOBIJGRIKcCIJWQg7FDX4wsBv5EBJouAU5E/KYYAOHxBBDn5BFgb7DgEcPoJNGIJMAIIsggFAHw0B4EEIJeSQYx9C/+OnAIE/BNCCgIjJHYL7FpCMGgP4HxSkD//+QY/x4AJDh7IBJoPgiQjKiAVCBAVIYhCDOgeAII4IFwPH/+P/DpMHYRQDIJDgIAQtATYx6BjgHEIIP4SoRlNI45BVpDyBAAf8boLgBX4IADg8f/0BHyBrDIA0CLJ5BEg4FDh/AEQrPB5JBbgBBPYocOgEfwAFDII3+HyICCDgsHgESCJFBBApBEg8cAgMgQY0H//yIKl4IQvgIJICGHYUAgPH/gXBglwQY0cdJ4CFo5fF8C/KIIy8Dg/kIIMn+CnHHyQCCC4IdFwTFPn+OC4U/GoUjII0AghBbnAdKoAIGxwaBv/4JQZBHHyYCCp/gd4fACJOQYo0kh//gfwgIICng/FgPBIK1AIIcAhIRKNY+R/EfwAXDn+AEIccuPJIK1INAIeBuBBJkGQYo4aBkECBAZBBnCEEHygmDgFx4BrBBxFBkkAIJACBiQFDkaDCh/AjgLEASsCgEgHAQLFyCDBYpCVIP4RBdyDCJOgNBgiDLAQkncwQABjg+XaJpNBfYMQQaH+H4MB4HgIMgsBiUJgGAQZ8kxxBBh0AghBjQAICBoDFBQZ8kx/Aj/+gKAjkmCoKABAQMAhAXPyZBB+DCkAQMJkkCgEgwSDQyVIkA+kpMEiEIgACBkCDRAVB9BgMEYYMAQwJB4QYNAQYUEgTLBQfGAQIQABwA+2AQNIkiDCgEIZYMIIO8JQIkBgjC3AQNBgkQIISDByBB3HAKDEoKA3AQOCgEgIAWChJB6hCDEhBB5iFAQf6DDgMAgSD7YYcEiVJkBB3kGAIIaDBYvMgH4cIkGCaIpBypBBEAAI7zAQtBH4kBgkSYocgIOdIQQrFBoIOEI4YCuoBAEoKDBpMEBwTLzQY5BBHeICFIAqDByAODkBBywRAEgUBgi/zAQmQoBBDiFAPosEIOaDFhI7zAQpAEgkSH26DIBw4JBhEEiFIkGQAQRBmpA/EAAVAkGAhEAwUAA=="));
+
+var battery = E.getBattery();
+
+// Positions on screen
+const timeX = 88, timeY = 52;
+const dateX = 5, dateY = 180;
+const battX = 172, battY = 175;
+
+// Draw on every second if unlocked or charging, minute otherwise, start at with seconds on load
+var drawTimeout;
+var drawInterval = 1000;
+
+// schedule a draw for the next interval
+function queueDraw() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ }, drawInterval - (Date.now() % drawInterval));
+}
+
+// Update display and timeout on lock/unlock and charge state change
+Bangle.on('lock',on=>{
+ draw();
+});
+
+Bangle.on('charging',charging=>{
+ draw();
+});
+
+function draw() {
+ // work out how to display the current time
+ var d = new Date();
+ var h = d.getHours(), m = d.getMinutes();
+ var time = (" "+h).substr(-2) + ":" + ("0"+m).substr(-2);
+ var seconds = ("0"+d.getSeconds()).substr(-2);
+
+ // g.clear(); // Unneeded if background image takes the whole screen
+
+ // Draw background
+ g.drawImage(img);
+ g.setColor(1, 1, 1);
+
+ // draw the current time
+ g.setFontAlign(1,1); // align right bottom
+ g.setFont("6x15",3);
+ g.drawString(time, timeX, timeY, false);
+
+ // Draw battery %
+ g.setFont("6x15",1);
+ var battStr = "";
+ if(Bangle.isCharging()) {
+ battStr = "+";
+ }
+ g.drawString(battStr + battery + "%", battX, battY, false);
+
+ // Draw date
+ g.setFontAlign(-1,1); // align left bottom
+ g.setFont("6x15",2);
+ var dateStr = require("locale").date(d)+" ";
+ g.drawString(dateStr, dateX, dateY, false);
+
+ // draw the seconds only if unlocked, set next timeout
+ if(!Bangle.isLocked() || Bangle.isCharging()) {
+ drawInterval = 1000;
+ g.setFont("6x15",2);
+ g.drawString(seconds, timeX+2, timeY-4, false);
+ }
+ else
+ drawInterval = 60000;
+
+ // Schedule next draw
+ queueDraw();
+ // console.log("Draw " + time + ":" + seconds);
+}
+
+function refreshBattery() {
+ battery = E.getBattery();
+}
+
+// Only update displayed battery level every minute as it fluctuates a lot
+var batteryInterval = setInterval(refreshBattery, 60000);
+
+Bangle.setUI("clock");
+Bangle.setLocked(false);
+// Clear the screen once, at startup
+g.clear();
+// draw immediately at first
+draw();
diff --git a/apps/2ofthemclk/app.png b/apps/2ofthemclk/app.png
new file mode 100644
index 000000000..d304f27d9
Binary files /dev/null and b/apps/2ofthemclk/app.png differ
diff --git a/apps/2ofthemclk/bg.png b/apps/2ofthemclk/bg.png
new file mode 100644
index 000000000..5f65ca3c7
Binary files /dev/null and b/apps/2ofthemclk/bg.png differ
diff --git a/apps/2ofthemclk/metadata.json b/apps/2ofthemclk/metadata.json
new file mode 100644
index 000000000..fa02b3e2f
--- /dev/null
+++ b/apps/2ofthemclk/metadata.json
@@ -0,0 +1,17 @@
+{
+ "id": "2ofthemclk",
+ "name": "two of them clock",
+ "version": "0.01",
+ "description": "You can now wear teh memez on your wrist.",
+ "readme": "README.md",
+ "icon": "app.png",
+ "screenshots": [{"url":"screenshot.png"}],
+ "type": "clock",
+ "tags": "clock",
+ "supports": ["BANGLEJS2"],
+ "allow_emulator": true,
+ "storage": [
+ {"name":"2ofthemclk.app.js","url":"app.js"},
+ {"name":"2ofthemclk.img","url":"app-icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/2ofthemclk/screenshot.png b/apps/2ofthemclk/screenshot.png
new file mode 100644
index 000000000..b9a80a2c5
Binary files /dev/null and b/apps/2ofthemclk/screenshot.png differ
diff --git a/apps/7x7dotsclock/7x7dotsclock.app.js b/apps/7x7dotsclock/7x7dotsclock.app.js
index aa174b2d2..aa6672a4f 100644
--- a/apps/7x7dotsclock/7x7dotsclock.app.js
+++ b/apps/7x7dotsclock/7x7dotsclock.app.js
@@ -149,11 +149,11 @@ function drawHSeg(x1,y1,x2,y2,Num,Color,Size) {
if (Color == "fg") {
g.setColor(g.theme.fg);
} else {
- g.setColor(mColor[0],mColor[1],mColor[2]);
+ g.setColor(mColor[0],mColor[1],mColor[2]);
}
g.fillCircle(x1+Dx+(i-1)*(x2-x1)/7,y1+Dy+(j-1)*(y2-y1)/7,Size);
} else {
- g.setColor(bColor[0],bColor[1],bColor[2]);
+ g.setColor(bColor[0],bColor[1],bColor[2]);
g.fillCircle(x1+Dx+(i-1)*(x2-x1)/7,y1+Dy+(j-1)*(y2-y1)/7,1);
}
}
@@ -166,7 +166,7 @@ function drawSSeg(x1,y1,x2,y2,Num,Color,Size) {
for (let j = 1; j < 8; j++) {
if (Font[Num][j-1][i-1] == 1) {
if (Color == "fg") {
- g.setColor(sColor[0],sColor[1],sColor[2]);
+ g.setColor(sColor[0],sColor[1],sColor[2]);
} else {
g.setColor(g.theme.fg);
//g.setColor(0.7,0.7,0.7);
@@ -253,8 +253,8 @@ function actions(v){
if(BTN1.read() === true) {
print("BTN pressed");
Bangle.showLauncher();
- }
-
+ }
+
if(v==-1){
print("up swipe event");
if(settings.swupApp != "") load(settings.swupApp);
@@ -269,7 +269,7 @@ function actions(v){
}
// Get Messages status
-var messages = require("Storage").readJSON("messages.json",1)||[];
+var messages_installed = require("Storage").read("messages") !== undefined;
//var BTconnected = NRF.getSecurityStatus().connected;
//NRF.on('connect',BTconnected = NRF.getSecurityStatus().connected);
@@ -289,27 +289,27 @@ function drawWidgeds() {
g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f"));
else
g.setColor(g.theme.dark ? "#666" : "#999");
- g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),x1Bt,y1Bt);
+ g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),x1Bt,y1Bt);
+
-
//Battery
//print(E.getBattery());
//print(Bangle.isCharging());
-
+
var x1B = 130;
var y1B = 2;
var x2B = x1B + 20;
var y2B = y1B + 15;
-
+
g.setColor(g.theme.bg);
g.clearRect(x1B,y1B,x2B,y2B);
-
+
g.setColor(g.theme.fg);
g.drawRect(x1B,y1B,x2B,y2B);
g.fillRect(x1B,y1B,x1B+(E.getBattery()*(x2B-x1B)/100),y2B);
g.fillRect(x2B,y1B+(y2B-y1B)/2-3,x2B+4,y1B+(y2B-y1B)/2+3);
-
+
//Messages
@@ -318,25 +318,25 @@ function drawWidgeds() {
var x2M = x1M + 25;
var y2M = y2B;
- if (messages.some(m=>m.new)) {
+ if (messages_installed && require("messages").status() == "new") {
g.setColor(g.theme.fg);
g.fillRect(x1M,y1M,x2M,y2M);
g.setColor(g.theme.bg);
g.drawLine(x1M,y1M,x1M+(x2M-x1M)/2,y1M+(y2M-y1M)/2);
g.drawLine(x1M+(x2M-x1M)/2,y1M+(y2M-y1M)/2,x2M,y1M);
}
-
+
var strDow = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
var d = new Date();
var dow = d.getDay(),day = d.getDate(), month = d.getMonth() + 1, year = d.getFullYear();
print(strDow[dow] + ' ' + day + '.' + month + ' ' + year);
-
+
g.setColor(g.theme.fg);
g.setFontAlign(-1, -1,0);
g.setFont("Vector", 20);
g.drawString(strDow[dow] + ' ' + day, 0, 0, true);
-
+
}
@@ -354,7 +354,7 @@ function SetFull(on) {
} else {
Ys = 30;
Bangle.setUI("updown",actions);
- Bangle.on('swipe', function(direction) {
+ Bangle.on('swipe', function(direction) {
switch (direction) {
case 1:
print("swipe left event");
@@ -362,7 +362,7 @@ function SetFull(on) {
print(settings.swleftApp);
break;
case -1:
- print("swipe right event");
+ print("swipe right event");
if(settings.swrightApp != "") load(settings.swrightApp);
print(settings.swrightApp);
break;
@@ -374,7 +374,7 @@ function SetFull(on) {
SegH = (Ye-Ys)/2;
Dy = SegH/16;
-
+
draw();
if (on != true) {
diff --git a/apps/7x7dotsclock/ChangeLog b/apps/7x7dotsclock/ChangeLog
index d2c98a472..5e8e48b0b 100644
--- a/apps/7x7dotsclock/ChangeLog
+++ b/apps/7x7dotsclock/ChangeLog
@@ -1,2 +1,3 @@
0.01: Initial version for upload
-0.02: better theme support, configurable colors, small improvements
+0.02: Better theme support, configurable colors, small improvements
+0.03: Use `messages` library to check for new messages
\ No newline at end of file
diff --git a/apps/7x7dotsclock/metadata.json b/apps/7x7dotsclock/metadata.json
index 41f0836d3..ba1996544 100644
--- a/apps/7x7dotsclock/metadata.json
+++ b/apps/7x7dotsclock/metadata.json
@@ -1,7 +1,7 @@
{ "id": "7x7dotsclock",
"name": "7x7 Dots Clock",
"shortName":"7x7 Dots Clock",
- "version":"0.02",
+ "version":"0.03",
"description": "A clock with a big 7x7 dots Font",
"icon": "dotsfontclock.png",
"tags": "clock",
diff --git a/apps/90sclk/ChangeLog b/apps/90sclk/ChangeLog
index feb008f5f..057d6ff73 100644
--- a/apps/90sclk/ChangeLog
+++ b/apps/90sclk/ChangeLog
@@ -1,2 +1,3 @@
0.01: New App!
-0.02: Fullscreen settings.
\ No newline at end of file
+0.02: Fullscreen settings.
+0.03: Tell clock widgets to hide.
diff --git a/apps/90sclk/app.js b/apps/90sclk/app.js
index 6babbfec2..351c235e0 100644
--- a/apps/90sclk/app.js
+++ b/apps/90sclk/app.js
@@ -115,6 +115,9 @@ function draw() {
}
}
+// Show launcher when middle button pressed
+Bangle.setUI("clock");
+
Bangle.loadWidgets();
// Clear the screen once, at startup
@@ -140,5 +143,3 @@ Bangle.on('lock', function(isLocked) {
});
-// Show launcher when middle button pressed
-Bangle.setUI("clock");
diff --git a/apps/90sclk/metadata.json b/apps/90sclk/metadata.json
index fb2824a6f..59b627427 100644
--- a/apps/90sclk/metadata.json
+++ b/apps/90sclk/metadata.json
@@ -1,7 +1,7 @@
{
"id": "90sclk",
"name": "90s Clock",
- "version": "0.02",
+ "version": "0.03",
"description": "A 90s style watch-face",
"readme": "README.md",
"icon": "app.png",
diff --git a/apps/UI4swatch/Changelog b/apps/UI4swatch/ChangeLog
similarity index 100%
rename from apps/UI4swatch/Changelog
rename to apps/UI4swatch/ChangeLog
diff --git a/apps/_example_widget/widget.js b/apps/_example_widget/widget.js
index f7aed6991..226aea589 100644
--- a/apps/_example_widget/widget.js
+++ b/apps/_example_widget/widget.js
@@ -9,7 +9,7 @@ currently-running apps */
// add your widget
WIDGETS["mywidget"]={
- area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
+ area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right), be aware that not all apps support widgets at the bottom of the screen
width: 28, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
draw:draw // called to draw the widget
};
diff --git a/apps/activityreminder/ChangeLog b/apps/activityreminder/ChangeLog
index 53e29a66d..c913481ff 100644
--- a/apps/activityreminder/ChangeLog
+++ b/apps/activityreminder/ChangeLog
@@ -1,2 +1,9 @@
0.01: New App!
0.02: Fix the settings bug and some tweaking
+0.03: Do not alarm while charging
+0.04: Obey system quiet mode
+0.05: Battery optimisation, add the pause option, bug fixes
+0.06: Add a temperature threshold to detect (and not alert) if the BJS isn't worn. Better support for the peoples using the app at night
+0.07: Fix bug on the cutting edge firmware
+0.08: Use default Bangle formatter for booleans
+0.09: New app screen (instead of showing settings or the alert) and some optimisations
\ No newline at end of file
diff --git a/apps/activityreminder/README.md b/apps/activityreminder/README.md
index 1e643fb54..0c79b4141 100644
--- a/apps/activityreminder/README.md
+++ b/apps/activityreminder/README.md
@@ -1,13 +1,15 @@
# Activity reminder
A reminder to take short walks for the ones with a sedentary lifestyle.
-The alert will popup only if you didn't take your short walk yet
+The alert will popup only if you didn't take your short walk yet.
Different settings can be personalized:
- Enable : Enable/Disable the app
- Start hour: Hour to start the reminder
- End hour: Hour to end the reminder
-- Max inactivity: Maximum inactivity time to allow before the alert. From 15 to 60 min
-- Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 15 min
+- Max inactivity: Maximum inactivity time to allow before the alert. From 15 to 120 min
+- Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 60 min
+- Pause delay: Same as Dismiss delay but longer (usefull for meetings and such). From 30 to 240 min
- Min steps: Minimal amount of steps to count as an activity
+- Temp Threshold: Temperature threshold to determine if the watch is worn
diff --git a/apps/activityreminder/alert.js b/apps/activityreminder/alert.js
new file mode 100644
index 000000000..96a9b76c4
--- /dev/null
+++ b/apps/activityreminder/alert.js
@@ -0,0 +1,37 @@
+(function () {
+ // load variable before defining functions cause it can trigger a ReferenceError
+ const activityreminder = require("activityreminder");
+ const storage = require("Storage");
+ let activityreminder_data = activityreminder.loadData();
+
+ function run() {
+ E.showPrompt("Inactivity detected", {
+ title: "Activity reminder",
+ buttons: { "Ok": 1, "Dismiss": 2, "Pause": 3 }
+ }).then(function (v) {
+ if (v == 1) {
+ activityreminder_data.okDate = new Date();
+ }
+ if (v == 2) {
+ activityreminder_data.dismissDate = new Date();
+ }
+ if (v == 3) {
+ activityreminder_data.pauseDate = new Date();
+ }
+ activityreminder.saveData(activityreminder_data);
+ load();
+ });
+
+ // Obey system quiet mode:
+ if (!(storage.readJSON('setting.json', 1) || {}).quiet) {
+ Bangle.buzz(400);
+ }
+ setTimeout(load, 20000);
+ }
+
+ g.clear();
+ Bangle.loadWidgets();
+ Bangle.drawWidgets();
+ run();
+
+})();
\ No newline at end of file
diff --git a/apps/activityreminder/app.js b/apps/activityreminder/app.js
index 310dc10b0..97f03ce97 100644
--- a/apps/activityreminder/app.js
+++ b/apps/activityreminder/app.js
@@ -1,37 +1,54 @@
-function drawAlert(){
- E.showPrompt("Inactivity detected",{
- title:"Activity reminder",
- buttons : {"Ok": true,"Dismiss": false}
- }).then(function(v) {
- if(v == true){
- stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin - 3);
- require("activityreminder").saveStepsArray(stepsArray);
- }
- if(v == false){
- stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin - activityreminder.dismissDelayMin);
- require("activityreminder").saveStepsArray(stepsArray);
- }
- load();
- });
+(function () {
+ // load variable before defining functions cause it can trigger a ReferenceError
+ const activityreminder = require("activityreminder");
+ let activityreminder_data = activityreminder.loadData();
+ let W = g.getWidth();
+ // let H = g.getHeight();
+
+ function getHoursMins(date){
+ var h = date.getHours();
+ var m = date.getMinutes();
+ return (""+h).substr(-2) + ":" + ("0"+m).substr(-2);
+ }
+
+ function drawData(name, value, y){
+ g.drawString(name, 10, y);
+ g.drawString(value, 100, y);
+ }
- Bangle.buzz(400);
- setTimeout(load, 20000);
-}
+ function drawInfo() {
+ var h=18, y = h;
+ g.setColor(g.theme.fg);
+ g.setFont("Vector",h).setFontAlign(-1,-1);
-function run(){
- if(stepsArray.length == activityreminder.maxInnactivityMin){
- if (stepsArray[0] - stepsArray[stepsArray.length-1] < activityreminder.minSteps){
- drawAlert();
- }
- }else{
- eval(require("Storage").read("activityreminder.settings.js"))(()=>load());
- }
-}
+ // Header
+ g.drawLine(0,25,W,25);
+ g.drawLine(0,26,W,26);
+
+ g.drawString("Current Cycle", 10, y+=h);
+ drawData("Start", getHoursMins(activityreminder_data.stepsDate), y+=h);
+ drawData("Steps", getCurrentSteps(), y+=h);
+ /*
+ g.drawString("Button Press", 10, y+=h*2);
+ drawData("Ok", getHoursMins(activityreminder_data.okDate), y+=h);
+ drawData("Dismiss", getHoursMins(activityreminder_data.dismissDate), y+=h);
+ drawData("Pause", getHoursMins(activityreminder_data.pauseDate), y+=h);
+ */
+ }
-g.clear();
-Bangle.loadWidgets();
-Bangle.drawWidgets();
-activityreminder = require("activityreminder").loadSettings();
-stepsArray = require("activityreminder").loadStepsArray();
-run();
+ function getCurrentSteps(){
+ let health = Bangle.getHealthStatus("day");
+ return health.steps - activityreminder_data.stepsOnDate;
+ }
+
+ function run() {
+ g.clear();
+ Bangle.loadWidgets();
+ Bangle.drawWidgets();
+ drawInfo();
+ }
+
+ run();
+
+})();
\ No newline at end of file
diff --git a/apps/activityreminder/boot.js b/apps/activityreminder/boot.js
index 0f89bf543..5a11d73b8 100644
--- a/apps/activityreminder/boot.js
+++ b/apps/activityreminder/boot.js
@@ -1,29 +1,81 @@
-function run(){
- var now = new Date();
- var h = now.getHours();
- if(h >= activityreminder.startHour && h < activityreminder.endHour){
- var health = Bangle.getHealthStatus("day");
- stepsArray.unshift(health.steps);
- stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin);
- require("activityreminder").saveStepsArray(stepsArray);
+(function () {
+ // load variable before defining functions cause it can trigger a ReferenceError
+ const activityreminder = require("activityreminder");
+ const activityreminder_settings = activityreminder.loadSettings();
+ let activityreminder_data = activityreminder.loadData();
+
+ if (activityreminder_data.firstLoad) {
+ activityreminder_data.firstLoad = false;
+ activityreminder.saveData(activityreminder_data);
+ }
+
+ function run() {
+ if (isNotWorn()) return;
+ let now = new Date();
+ let h = now.getHours();
+
+ if (isDuringAlertHours(h)) {
+ let health = Bangle.getHealthStatus("day");
+ if (health.steps - activityreminder_data.stepsOnDate >= activityreminder_settings.minSteps // more steps made than needed
+ || health.steps < activityreminder_data.stepsOnDate) { // new day or reboot of the watch
+ activityreminder_data.stepsOnDate = health.steps;
+ activityreminder_data.stepsDate = now;
+ activityreminder.saveData(activityreminder_data);
+ /* todo in a futur release
+ Add settimer to trigger like 30 secs after going in this part cause the person have been walking
+ (pass some argument to run() to handle long walks and not triggering so often)
+ */
+ }
+
+ if (mustAlert(now)) {
+ load('activityreminder.alert.js');
+ }
}
- else{
- if(stepsArray != []){
- stepsArray = [];
- require("activityreminder").saveStepsArray(stepsArray);
- }
+
+ }
+
+ function isNotWorn() {
+ return (Bangle.isCharging() || activityreminder_settings.tempThreshold >= E.getTemperature());
+ }
+
+ function isDuringAlertHours(h) {
+ if (activityreminder_settings.startHour < activityreminder_settings.endHour) { // not passing through midnight
+ return (h >= activityreminder_settings.startHour && h < activityreminder_settings.endHour);
+ } else { // passing through midnight
+ return (h >= activityreminder_settings.startHour || h < activityreminder_settings.endHour);
}
- if(stepsArray.length >= activityreminder.maxInnactivityMin){
- if (stepsArray[0] - stepsArray[stepsArray.length-1] < activityreminder.minSteps){
- load('activityreminder.app.js');
- }
+ }
+
+ function mustAlert(now) {
+ if ((now - activityreminder_data.stepsDate) / 60000 > activityreminder_settings.maxInnactivityMin) { // inactivity detected
+ if ((now - activityreminder_data.okDate) / 60000 > 3 && // last alert anwsered with ok was more than 3 min ago
+ (now - activityreminder_data.dismissDate) / 60000 > activityreminder_settings.dismissDelayMin && // last alert was more than dismissDelayMin ago
+ (now - activityreminder_data.pauseDate) / 60000 > activityreminder_settings.pauseDelayMin) { // last alert was more than pauseDelayMin ago
+ return true;
+ }
}
-}
+ return false;
+ }
+
+ Bangle.on('midnight', function () {
+ /*
+ Usefull trick to have the app working smothly for people using it at night
+ */
+ let now = new Date();
+ let h = now.getHours();
+ if (activityreminder_settings.enabled && isDuringAlertHours(h)) {
+ // updating only the steps and keeping the original stepsDate on purpose
+ activityreminder_data.stepsOnDate = 0;
+ activityreminder.saveData(activityreminder_data);
+ }
+ });
-activityreminder = require("activityreminder").loadSettings();
-if(activityreminder.enabled) {
- stepsArray = require("activityreminder").loadStepsArray();
+ if (activityreminder_settings.enabled) {
setInterval(run, 60000);
-}
-
+ /* todo in a futur release
+ increase setInterval time to something that is still sensible (5 mins ?)
+ when we added a settimer
+ */
+ }
+})();
diff --git a/apps/activityreminder/lib.js b/apps/activityreminder/lib.js
index 712842fba..a5c35190c 100644
--- a/apps/activityreminder/lib.js
+++ b/apps/activityreminder/lib.js
@@ -1,22 +1,44 @@
-exports.loadSettings = function() {
- return Object.assign({
- enabled: true,
- startHour: 9,
- endHour: 20,
- maxInnactivityMin: 30,
- dismissDelayMin: 15,
- minSteps: 50
- }, require("Storage").readJSON("activityreminder.s.json", true) || {});
+exports.loadSettings = function () {
+ return Object.assign({
+ enabled: true,
+ startHour: 9,
+ endHour: 20,
+ maxInnactivityMin: 30,
+ dismissDelayMin: 15,
+ pauseDelayMin: 120,
+ minSteps: 50,
+ tempThreshold: 27
+ }, require("Storage").readJSON("activityreminder.s.json", true) || {});
};
-exports.writeSettings = function(settings){
- require("Storage").writeJSON("activityreminder.s.json", settings);
+exports.writeSettings = function (settings) {
+ require("Storage").writeJSON("activityreminder.s.json", settings);
};
-exports.saveStepsArray = function(stepsArray) {
- require("Storage").writeJSON("activityreminder.sa.json", stepsArray);
+exports.saveData = function (data) {
+ require("Storage").writeJSON("activityreminder.data.json", data);
};
-exports.loadStepsArray = function(){
- return require("Storage").readJSON("activityreminder.sa.json") || [];
-};
\ No newline at end of file
+exports.loadData = function () {
+ let health = Bangle.getHealthStatus("day");
+ let data = Object.assign({
+ firstLoad: true,
+ stepsDate: new Date(),
+ stepsOnDate: health.steps,
+ okDate: new Date(1970),
+ dismissDate: new Date(1970),
+ pauseDate: new Date(1970),
+ },
+ require("Storage").readJSON("activityreminder.data.json") || {});
+
+ if (typeof (data.stepsDate) == "string")
+ data.stepsDate = new Date(data.stepsDate);
+ if (typeof (data.okDate) == "string")
+ data.okDate = new Date(data.okDate);
+ if (typeof (data.dismissDate) == "string")
+ data.dismissDate = new Date(data.dismissDate);
+ if (typeof (data.pauseDate) == "string")
+ data.pauseDate = new Date(data.pauseDate);
+
+ return data;
+};
diff --git a/apps/activityreminder/metadata.json b/apps/activityreminder/metadata.json
index eba5de105..9e8c552b1 100644
--- a/apps/activityreminder/metadata.json
+++ b/apps/activityreminder/metadata.json
@@ -3,7 +3,7 @@
"name": "Activity Reminder",
"shortName":"Activity Reminder",
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
- "version":"0.02",
+ "version":"0.09",
"icon": "app.png",
"type": "app",
"tags": "tool,activity",
@@ -13,11 +13,12 @@
{"name": "activityreminder.app.js", "url":"app.js"},
{"name": "activityreminder.boot.js", "url": "boot.js"},
{"name": "activityreminder.settings.js", "url": "settings.js"},
+ {"name": "activityreminder.alert.js", "url": "alert.js"},
{"name": "activityreminder", "url": "lib.js"},
{"name": "activityreminder.img", "url": "app-icon.js", "evaluate": true}
],
"data": [
{"name": "activityreminder.s.json"},
- {"name": "activityreminder.sa.json"}
+ {"name": "activityreminder.data.json", "storageFile": true}
]
}
diff --git a/apps/activityreminder/settings.js b/apps/activityreminder/settings.js
index 9b9a0ecd8..051c0dcd8 100644
--- a/apps/activityreminder/settings.js
+++ b/apps/activityreminder/settings.js
@@ -1,17 +1,17 @@
-(function(back) {
+(function (back) {
// Load settings
- var settings = require("activityreminder").loadSettings();
+ const activityreminder = require("activityreminder");
+ let settings = activityreminder.loadSettings();
- // Show the menu
- E.showMenu({
- "" : { "title" : "Activity Reminder" },
- "< Back" : () => back(),
+ function getMainMenu(){
+ var mainMenu = {
+ "": { "title": "Activity Reminder" },
+ "< Back": () => back(),
'Enable': {
value: settings.enabled,
- format: v => v?"Yes":"No",
onchange: v => {
settings.enabled = v;
- require("activityreminder").writeSettings(settings);
+ activityreminder.writeSettings(settings);
}
},
'Start hour': {
@@ -19,46 +19,68 @@
min: 0, max: 24,
onchange: v => {
settings.startHour = v;
- require("activityreminder").writeSettings(settings);
+ activityreminder.writeSettings(settings);
}
- },
- 'End hour': {
+ },
+ 'End hour': {
value: settings.endHour,
min: 0, max: 24,
onchange: v => {
settings.endHour = v;
- require("activityreminder").writeSettings(settings);
+ activityreminder.writeSettings(settings);
}
- },
- 'Max inactivity': {
+ },
+ 'Max inactivity': {
value: settings.maxInnactivityMin,
min: 15, max: 120,
onchange: v => {
settings.maxInnactivityMin = v;
- require("activityreminder").writeSettings(settings);
+ activityreminder.writeSettings(settings);
},
- format: x => {
- return x + " min";
- }
- },
- 'Dismiss delay': {
+ format: x => x + "m"
+ },
+ 'Dismiss delay': {
value: settings.dismissDelayMin,
- min: 5, max: 15,
+ min: 5, max: 60,
onchange: v => {
settings.dismissDelayMin = v;
- require("activityreminder").writeSettings(settings);
+ activityreminder.writeSettings(settings);
+ },
+ format: x => x + "m"
+ },
+ 'Pause delay': {
+ value: settings.pauseDelayMin,
+ min: 30, max: 240, step: 5,
+ onchange: v => {
+ settings.pauseDelayMin = v;
+ activityreminder.writeSettings(settings);
},
format: x => {
- return x + " min";
+ return x + "m";
}
- },
- 'Min steps': {
+ },
+ 'Min steps': {
value: settings.minSteps,
- min: 10, max: 500,
+ min: 10, max: 500, step: 10,
onchange: v => {
settings.minSteps = v;
- require("activityreminder").writeSettings(settings);
+ activityreminder.writeSettings(settings);
}
- }
- });
+ },
+ 'Temp Threshold': {
+ value: settings.tempThreshold,
+ min: 20, max: 40, step: 0.5,
+ format: v => v + "°C",
+ onchange: v => {
+ settings.tempThreshold = v;
+ activityreminder.writeSettings(settings);
+ }
+ }
+ };
+
+ return mainMenu;
+ }
+
+ // Show the menu
+ E.showMenu(getMainMenu());
})
diff --git a/apps/advcasio/ChangeLog b/apps/advcasio/ChangeLog
new file mode 100644
index 000000000..a1b528cf6
--- /dev/null
+++ b/apps/advcasio/ChangeLog
@@ -0,0 +1,3 @@
+0.01: AdvCasio first version
+0.02: Remove un-needed fonts to improve memory usage
+0.03: Tell clock widgets to hide.
diff --git a/apps/advcasio/README.md b/apps/advcasio/README.md
new file mode 100644
index 000000000..3ce771497
--- /dev/null
+++ b/apps/advcasio/README.md
@@ -0,0 +1,62 @@
+# Adv Casio Clock
+
+
+
+An over-engineered clock inspired by Casio watches.
+It has a dedicated timer, a scratchpad and can display the weather condition 4 days ahead.
+It uses a custom web app to update its content.
+Forked from the awesome Cassio Watch.
+
+## Todo
+
+- Improving quality of the background images, right now it is quite blurry.
+- Improving screenshots quality.
+- Improving web app look.
+- Improving bangle app performances (using functions for images and specialized array).
+
+## Functionalities
+
+- Current time
+- Current day and month
+- Footsteps
+- Battery
+- Simple Timer embedded
+- Weather forecast (7 days)
+- Scratchpad
+
+## Screenshots
+Clock:
+
+
+
+
+Web interface to update weather & scratchpad
+https://dotgreg.github.io/advCasioBangleClock
+
+
+
+## Usage
+### How to update the tasks list / weather
+- you will need a free openweathermap.org api key .
+- go to https://dotgreg.github.io/advCasioBangleClock/
+ - Alternatively you can install it on your own server/heroku/service/github pages, the web-app code is here
+- fill the location and the api key (it will be saved on your browser, no need to do it each time)
+- edit the scratchpad with what you want
+- click on sync
+- reload your clock!
+
+### How to start/stop the timer
+- swipe up : add time (+5min)
+- swipe down : remove time (-5min)
+- swipe right : start timer
+- swipe left : stop timer
+
+## Links
+### Issues, suggestions and bugtracker
+https://github.com/dotgreg/advCasioBangleClock/issues
+
+### Code repository (bangle app and web app)
+https://github.com/dotgreg/advCasioBangleClock
+
+### Creator
+https://github.com/dotgreg
diff --git a/apps/advcasio/app-icon.js b/apps/advcasio/app-icon.js
new file mode 100644
index 000000000..2471ceac7
--- /dev/null
+++ b/apps/advcasio/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwghC/AH4A/AGsCmUQC6kf/8wC6k///wgEv//zD4PxAQIJBABP//4QBC4IcBh/yEQIXKgP/l4rBl/yGAMP/4iBKJUC/5gBIAQVBBAMR/8gC5IQBAAMQC4IVBFoMjAYIXNmAXBgYXCPgQAJl/xHwPwj/yn5kC/55BUxSlC+JiBVgQ5BUxiDBUIIXBIQQXBcCoA/AH4ADXAUgUAUQBAkPeoTDFgIHBAALQEA4XwC4IOEAAQRBbAQBBCAIgBEYMQC4TnEC4XyeQgBDAAMwC4pIDC4kDAgJLD//xC5QIBNQISCFYIZCC4aEBAQRCDAAPyl4hBOIh3Cn53GNgMRiKxGBAR5BAoYA/AH4A/AH4A5A"))
diff --git a/apps/advcasio/app.js b/apps/advcasio/app.js
new file mode 100644
index 000000000..8cb904f90
--- /dev/null
+++ b/apps/advcasio/app.js
@@ -0,0 +1,304 @@
+const storage = require('Storage');
+
+require("Font8x12").add(Graphics);
+require("Font7x11Numeric7Seg").add(Graphics);
+
+function bigThenSmall(big, small, x, y) {
+ g.setFont("7x11Numeric7Seg", 2);
+ g.drawString(big, x, y);
+ x += g.stringWidth(big);
+ g.setFont("8x12");
+ g.drawString(small, x, y);
+}
+
+function getClockBg() {
+ return require("heatshrink").decompress(atob("icVgf/ABv8v4DBx4CB+PH8F+nAGB48fwEHBwXjxwqBuPH//+nAGBBwIjCAwI2D/wGBgIyDI4QGDwAGBHYX/4AGBn4UFEYQpCEYYpCAAMfMhP4FIgABwJ8OEBIA=="));
+}
+
+
+// sun, cloud, rain, thunder
+var iconsWeather = [
+ require("heatshrink").decompress(atob("i8Ugf/ACcfA434BA/AAwsAv0/8F/BAcDwEHHIpECFI3wn4GC/gOC+PAGoXggEH/+ODQgXBGQv/wAbBBAnguEACIn4gfxI4JXFwJmG/kPBA3jSynw")), require("heatshrink").decompress(atob("i0Ugf/AEXggIGE/0A/kPBAmBCIN/A4Y8CgAICwEHBYoUE/ACCj4sDn4CBC4YyDwBrDCgYA3A")), require("heatshrink").decompress(atob("h8Rgf/AAuBAgf8h4FDCwM/AgPA/gFC/0HgEBBQPwnEfDoWAg4jC/gOCAoQmBAQXjFIV//8f//4IQP4j/+gAIB4EcHII4CAoI+DLQJXF/AA==")), require("heatshrink").decompress(atob("h0Pgf/AA8fAYX+g4EC8EBAgXADAeAgAECgAOC/wrCDQIOBBYfwgAaC/kAn4EB/EAv4aDHAeBIg38"))
+];
+
+
+function getBackgroundImage() {
+ return require("heatshrink").decompress(atob("2GwghC/AH4A/AH4AMl////wAwURiQECgUzmcxBQQCBiYUBBARW+LAcCAgcPBYgFBkAIFG7kQiAKIiIKBgISOAAJBD//zKQfxK4vyAoMQCgn/ERBhBBYR5BAwR1DB4Y2DgYPCGIQRCCQcP+EfGJI0FEgRSCGAQCCX4JXCkAhDn4lI+HyK4YWBFIPzJYJXHAIMSK4cwJ4I3CAYMzA4cfcRMBdwytBK4i6FK4IUCMgYAEGIITBK4cCaAPwgJXB+fzK4sAgYtCK5EfA4pXR+AmBaIZYCK6KcCAwSjDEYXx/8vK5QRCK4kPK6cDkJREBIMBfgIrDK5svUAIQBAwIaCK4w+DK4YGBK7IaBboIuCK4gFCJwYBBiBCCCgQhHHYgGDgArBK5IGDAYMgJ4Xwn53BGgLVDmBXKAAinDLpJXCAAYhHR4YODn/wJIPyTYZXDE4RXD+ECNILIDAIPwj4xIAAYNCR4fyVIYLFA4KEBBAglKAGUCmcykEAiMQBIURBYM/BgIUEgcz+bTKAH4A/AH4A/AHP/AGY1d+BWCh5X/LCpW1K74fgG/5X/AH5X/K9Bg/K63wK/5XWgBX/K6pWBK/5XU+BWBh5J/K6auCK/5XTVwRfFAH5XOKwRX/K6auDh5I/K6SuDWP5XSVwYADWX6vXK/5XQWQpW/K6auDJP5XWV35XT+Cu/K7Ku/K65H/K6hW/K7EPI35XWIv5XWAH5X/K/4A/K/5X/K/4A/K9cAAH4A/AFzz/AHRX/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/40VAH4A/AFzLb+EPDm4AdK/5X/K+PwgEAHy5X9HgMAK/5XXH6xX/H65X/K/5X/K98AK7sAgBX3DjBWFO644DSTHwGzJXED4RXaDoLqcK7weWDIQcXK8I6YK77KXK4o8DPbY6ZK7qvDDy6vdR7JXDh60EDyw5BAIRXYSwjMbAgIhUDwJZCHwJX0GwjRWNwIAEHSwBCDSpXFH4pXzDS5XIEARXVSYbQEDaYzCK+6vcKaxXNDypX9HwQkbHS40COSpXKK2A6CHgRXcPIhX0SwpXYVuQ6EgBX/K644YODBXkSDJX/K/5X/DtRX6gA3YOkRWbLDZX4KwYA/AG8F5vdABncKH4AGhpRJAYXNAgPAKP4AF5vMJwoDBAQIKE6BR/AAvc5vO9wAB7oCB9veAoPcAoPcK+kwh8AgcA98An//gH/+sD//wCISgBJ4IABAYpaC9vdK4UP/9AAQNQr/zgHwEYNQFYQAh+EP+FegH+A4QBCMQIKBAAPNK4yxBA4RXCV4YZBE4IjChwCDmApCK8VdmHggHgFYf0SQJXE5nMK4anCAoYHC5pXCaQJXBop+BqAGEK7f/AAQeEKwQrBqCtDAILjBCQfNK4JTCAYZXF7qvD//gV4S2DgEFFIYAECgIACMC8PKoIBB8n1K4ivF5vc5xOCWYZbBAYavHU4RXCr4pEAEMDfoNQGoMEgEwYQPwAoIBBAAPM5ipC7oDCVIIAE7hXCD4SdBiEP+gGBgihCFYIAz5pXBAAnN7oIB7nc5gOBK4QA/K4pNCWgSpCBInNK/4AGhncKIStC7gCBA4QAC4BR/AAysCABZW/AHwA="));
+}
+
+
+
+// schedule a draw for the next minute
+let rocketInterval;
+var drawTimeout;
+function queueDraw() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ }, 60000 - (Date.now() % 60000));
+}
+
+
+function clearIntervals() {
+ if (rocketInterval) clearInterval(rocketInterval);
+ rocketInterval = undefined;
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+}
+
+////////////////////////////////////////////
+// TIMER FUNC
+//
+var timer_time = 0;
+var alreadyListenTouch = false;
+function initTouchTimer () {
+ if (alreadyListenTouch) return;
+ alreadyListenTouch = true;
+
+ Bangle.on('swipe', function(dirX,dirY) {
+ if (canTouch === false) return;
+ var njson = getDataJson();
+ if (!njson) return;
+
+ if (dirX === -1) {
+ timer_time = 0;
+ delete njson.timer;
+ setDataJson(njson);
+ }
+ else if (dirX === 1) {
+ var now = new Date().getTime();
+ njson.timer = now + (timer_time * 1000 * 60);
+ Bangle.setLocked(true);
+ setDataJson(njson);
+ Bangle.buzz(200, 0);
+ timer_time = 0;
+ }
+ else if (dirY === -1) {
+ if (canTouch === false || njson.timer) return;
+ timer_time = timer_time + 5;
+ }
+ else if (dirY === 1) {
+ if (canTouch === false || njson.timer) return;
+ timer_time = timer_time - 5;
+ }
+ draw();
+ });
+}
+setTimeout(() => {
+ initTouchTimer ();
+});
+
+function getTimerTime() {
+ // if timer_time !== -1, take it
+ if (timer_time !== 0) {
+ return timer_time + "m";
+ } else {
+ // else, show diff between njsontime and now
+ var njson = getDataJson();
+ if (!njson) return false;
+ var now = new Date().getTime();
+ var diff = Math.round((njson.timer - now) / (1000 * 60));
+ //console.log(123, njson, diff, now, njson.timer - now);
+ if (diff > 0) return diff + "m";
+ else if (njson.timer) {
+ Bangle.buzz(1000, 1);
+ console.log("END OF TIMER");
+ delete njson.timer;
+ setDataJson(njson);
+ return false;
+ } else {
+ return false;
+ }
+ // if diff is <0, delete timer from json
+ }
+}
+function drawTimer() {
+ //g.drawString(getTimerTime(), 100, 100);
+ g.setFont("8x12", 2);
+ var t = 97;
+ var l = 105;
+ var time = getTimerTime();
+ if (time || timer_time !== 0) g.drawString(time, l+5, t+0);
+ if (time && timer_time === 0) g.drawImage(getClockBg(), l-20, t+2, { scale: 1 });
+}
+
+
+////////////////////////////////////////////
+// DATA READING
+//
+function getDataJson(){
+ var res = {"tasks":"", "weather":[]};
+ try {
+ res = storage.readJSON('advcasio.data.json');
+ } catch(ex) {
+ return res;
+ }
+ return res;
+}
+function setDataJson(resJson){
+ try {
+ res = storage.writeJSON('advcasio.data.json', resJson);
+ } catch(ex) {
+ return res;
+ }
+ return res;
+}
+var dataJson = getDataJson();
+
+////////////////////////////////////////////
+// WEATHER!
+//
+function drawWeather(arr) {
+ g.setFont("6x8", 1);
+ var p = {l: 8, tText: 40, tIcon:20, decal:25};
+ var today = new Date().getTime();
+ var yesterday = today - (1000 * 60 * 60 * 24);
+ var testday = today + (1000 * 60 * 60 * 24 * 2);
+ //12h auj > 12h hier qui est sup a 0h auj
+ //23h59 hier est sup a 0h auj
+ var j = 0;
+ for(var i = 0; i yesterday && j < 4) {
+ g.drawString(arr[i][0], p.l + p.decal*j + 4, p.tText);
+ g.drawImage(iconsWeather[arr[i][1]], p.l + p.decal*j, p.tIcon, { scale: 1 });
+ j++
+ }
+ }
+}
+
+
+////////////////////////////////////////////
+// DRAWING FUNCS
+//
+function drawTasks(str) {
+ g.setFont("6x8", 1);
+ var t = 57;
+ var l = 0;
+ g.drawString(str, l+5, t+0);
+}
+
+function drawSteps() {
+ g.setFont("8x12", 2);
+ var t = 132;
+ var l = 150;
+ g.drawString(getSteps(), l+5, t+0);
+}
+
+
+function drawClock() {
+ g.setFont("7x11Numeric7Seg", 3);
+ g.clearRect(80, 57, 170, 96);
+ g.setColor(255, 255, 255);
+ var l = 77;
+ var t = 57;
+ var w = 170;
+ var h = 116;
+ g.drawRect(l, t, w, h);
+ g.fillRect(l, t, w, h);
+ g.setColor(0, 0, 0);
+ g.drawString(require("locale").time(new Date(), 1), 76, 60);
+
+ // day
+ //g.setFont("8x12", 1);
+ //g.setFont("9x18", 1);
+ //g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 25, 136);
+ g.setFont("8x12", 2);
+ g.drawString(require("locale").dow(new Date(), 2), 18, 130);
+
+ // month
+ g.setFont("8x12");
+ g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 127);
+
+ // day nb
+ g.setFont("8x12", 2);
+ const time = new Date().getDate();
+ g.drawString(time < 10 ? "0" + time : time, 78, 137);
+}
+
+function drawBattery() {
+ bigThenSmall(E.getBattery(), "%", 140, 23);
+}
+
+
+function getSteps() {
+ var steps = 0;
+ try{
+ if (WIDGETS.wpedom !== undefined) {
+ steps = WIDGETS.wpedom.getSteps();
+ } else if (WIDGETS.activepedom !== undefined) {
+ steps = WIDGETS.activepedom.getSteps();
+ } else {
+ steps = Bangle.getHealthStatus("day").steps;
+ }
+ } catch(ex) {
+ // In case we failed, we can only show 0 steps.
+ return "? k";
+ }
+
+ steps = Math.round(steps/1000);
+ return steps + "k";
+}
+
+
+
+function draw() {
+
+ queueDraw();
+
+ g.reset();
+ g.clear();
+ g.setColor(255, 255, 255);
+ g.fillRect(0, 0, g.getWidth(), g.getHeight());
+ let background = getBackgroundImage();
+ g.drawImage(background, 0, 0, { scale: 1 });
+
+
+ g.setColor(0, 0, 0);
+ if(dataJson && dataJson.weather) drawWeather(dataJson.weather);
+ if(dataJson && dataJson.tasks) drawTasks(dataJson.tasks);
+
+
+ g.setFontAlign(0,-1);
+ g.setFont("8x12", 2);
+
+ drawSteps();
+ g.setFontAlign(-1,-1);
+ drawClock();
+ drawBattery();
+ drawTimer();
+ // Hide widgets
+ for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
+}
+
+// save batt power, does not seem to work although...
+var canTouch = true;
+Bangle.on("lcdPower", (on) => {
+ if (on) {
+ draw();
+ } else {
+ canTouch = false;
+ clearIntervals();
+ }
+});
+
+
+Bangle.on("lock", (locked) => {
+ clearIntervals();
+ draw();
+ if (!locked) {
+ canTouch = true;
+ } else {
+ canTouch = false;
+ }
+});
+
+
+Bangle.setUI("clock");
+
+// Load widgets, but don't show them
+Bangle.loadWidgets();
+
+g.reset();
+g.clear();
+draw();
diff --git a/apps/advcasio/app.png b/apps/advcasio/app.png
new file mode 100644
index 000000000..a7c1b2736
Binary files /dev/null and b/apps/advcasio/app.png differ
diff --git a/apps/advcasio/data.json b/apps/advcasio/data.json
new file mode 100644
index 000000000..d986fa09c
--- /dev/null
+++ b/apps/advcasio/data.json
@@ -0,0 +1 @@
+{"tasks":"", "weather":[]};
diff --git a/apps/advcasio/metadata.json b/apps/advcasio/metadata.json
new file mode 100644
index 000000000..152f47132
--- /dev/null
+++ b/apps/advcasio/metadata.json
@@ -0,0 +1,25 @@
+{ "id": "advcasio",
+ "name": "Advanced Casio Clock",
+ "shortName":"advcasio",
+ "version":"0.03",
+ "description": "An over-engineered clock inspired by Casio watches. It has a 4 days weather, a timer using swipe and a scratchpad. Can be updated using a dedicated webapp.",
+ "icon": "app.png",
+ "tags": "clock",
+ "type": "clock",
+ "screenshots": [
+ { "url": "screenshot-clock-1.jpg" },
+ { "url": "screenshot-clock-2.jpg" },
+ { "url": "screenshot-clock-3.jpg" },
+ { "url": "screenshot-webapp.jpg" }
+ ],
+ "supports" : ["BANGLEJS", "BANGLEJS2"],
+ "readme": "README.md",
+ "allow_emulator":true,
+ "storage": [
+ {"name":"advcasio.app.js","url":"app.js"},
+ {"name":"advcasio.img","url":"app-icon.js","evaluate":true}
+ ],
+ "data": [
+ { "name": "advcasio.data.json", "url": "data.json", "storageFile": true }
+ ]
+}
diff --git a/apps/advcasio/screenshot-clock-1.jpg b/apps/advcasio/screenshot-clock-1.jpg
new file mode 100644
index 000000000..7f6f042c9
Binary files /dev/null and b/apps/advcasio/screenshot-clock-1.jpg differ
diff --git a/apps/advcasio/screenshot-clock-2.jpg b/apps/advcasio/screenshot-clock-2.jpg
new file mode 100644
index 000000000..b5f1e38af
Binary files /dev/null and b/apps/advcasio/screenshot-clock-2.jpg differ
diff --git a/apps/advcasio/screenshot-clock-3.jpg b/apps/advcasio/screenshot-clock-3.jpg
new file mode 100644
index 000000000..59389eb31
Binary files /dev/null and b/apps/advcasio/screenshot-clock-3.jpg differ
diff --git a/apps/advcasio/screenshot-webapp.jpg b/apps/advcasio/screenshot-webapp.jpg
new file mode 100644
index 000000000..d67bdba91
Binary files /dev/null and b/apps/advcasio/screenshot-webapp.jpg differ
diff --git a/apps/agenda/ChangeLog b/apps/agenda/ChangeLog
new file mode 100644
index 000000000..2c59c3cc2
--- /dev/null
+++ b/apps/agenda/ChangeLog
@@ -0,0 +1,5 @@
+0.01: Basic agenda with events from GB
+0.02: Added settings page to force calendar sync
+0.03: Disable past events display from settings
+0.04: Added awareness of allDay field
+0.05: Displaying calendar colour and name
diff --git a/apps/agenda/README.md b/apps/agenda/README.md
new file mode 100644
index 000000000..7063a70a2
--- /dev/null
+++ b/apps/agenda/README.md
@@ -0,0 +1,20 @@
+# Agenda
+
+Basic agenda reading the events synchronised from GadgetBridge.
+
+### Functionalities
+
+* List all events in the next week (or whatever is synchronized)
+* Optionally view past events (until GB removes them)
+* Show start time and location of the events in the list
+* Show the colour of the calendar in the list
+* Display description, location and calendar name after tapping on events
+
+### Report a bug
+
+You can easily open an issue in the espruino repo, but I won't be notified and it might take time.
+If you want a (hopefully) quicker response, just report [on my fork](https://github.com/glemco/BangleApps).
+
+### Known Problems
+
+Any all-day event lasts just one day: that is a GB limitation that we will likely fix in the future.
diff --git a/apps/agenda/agenda-icon.js b/apps/agenda/agenda-icon.js
new file mode 100644
index 000000000..891543955
--- /dev/null
+++ b/apps/agenda/agenda-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwg1yhGIxAPMBwIPFhH//GAC5n/C4oHBC5/IGwoXBHQQAKC4OIFAWOxHv9GO9wAKI4XoC4foEIIWLC4IABC4gIBFxnuE4IqBC4gARC4ZzNAAwXaxe7ACO4C625C4m4xIJBzAeCxGbCAOIFgQOBC4pOBxe4AYIPBAYQKCAYYXE3GL/ADBx/oxb3BC4X+xG4xwOBC4uP/YDB54MBf4Po3eM/4XBx/+C4pTBGIIkBLgOYAYIvB9GJBwI6BL45zCL4aCCL4h3GU64ALdYS1CI55bBAAgXFO4mMO4QDBDIO/////YxBU53IxIVB/GfDAWYa5wtC/GPAYWIL4wXBL4oSBC4jcBC4m4QIWYSwWIIQIAG/CnMMAIAC/JLCMIIvMIwZHFJAJfLC5yPHAYIRDAoy/KCIi7BMon4d4+Od4IXBxAZBEQLtB/+YxIXDL4SLCL4WPzAXCNgRFBLIKnKLIrcEI4gXNAAp3CxGZAAzCBC5KnCKAIAICxBlBC4IAJxG/C4/4wAXLhBgD/IcD3AXMGAIqDDgRGNGAoXDFxxhEI4W4FxwwCaoYWBFx4YDAAQWRAEQ"))
diff --git a/apps/agenda/agenda.js b/apps/agenda/agenda.js
new file mode 100644
index 000000000..9cffe0265
--- /dev/null
+++ b/apps/agenda/agenda.js
@@ -0,0 +1,138 @@
+/* CALENDAR is a list of:
+ {id:int,
+ type,
+ timestamp,
+ durationInSeconds,
+ title,
+ description,
+ location,
+ color:int,
+ calName,
+ allDay: bool,
+ }
+*/
+
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+
+var FILE = "android.calendar.json";
+
+var Locale = require("locale");
+
+var fontSmall = "6x8";
+var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2";
+var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2";
+var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4";
+
+//FIXME maybe write the end from GB already? Not durationInSeconds here (or do while receiving?)
+var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[];
+var settings = require("Storage").readJSON("agenda.settings.json",true)||{};
+
+CALENDAR=CALENDAR.sort((a,b)=>a.timestamp - b.timestamp);
+
+function getDate(timestamp) {
+ return new Date(timestamp*1000);
+}
+function formatDateLong(date, includeDay, allDay) {
+ let shortTime = Locale.time(date,1)+Locale.meridian(date);
+ if(allDay) shortTime = "";
+ if(includeDay || allDay)
+ return Locale.date(date)+" "+shortTime;
+ return shortTime;
+}
+function formatDateShort(date, allDay) {
+ return Locale.date(date).replace(/\d\d\d\d/,"")+(allDay?
+ "" : Locale.time(date,1)+Locale.meridian(date));
+}
+
+var lines = [];
+function showEvent(ev) {
+ var bodyFont = fontBig;
+ if(!ev) return;
+ g.setFont(bodyFont);
+ //var lines = [];
+ if (ev.title) lines = g.wrapString(ev.title, g.getWidth()-10);
+ var titleCnt = lines.length;
+ var start = getDate(ev.timestamp);
+ var end = getDate((+ev.timestamp) + (+ev.durationInSeconds));
+ var includeDay = true;
+ if (titleCnt) lines.push(""); // add blank line after title
+ if(start.getDay() == end.getDay() && start.getMonth() == end.getMonth())
+ includeDay = false;
+ if(includeDay || ev.allDay) {
+ lines = lines.concat(
+ /*LANG*/"Start:",
+ g.wrapString(formatDateLong(start, includeDay, ev.allDay), g.getWidth()-10),
+ /*LANG*/"End:",
+ g.wrapString(formatDateLong(end, includeDay, ev.allDay), g.getWidth()-10));
+ } else {
+ lines = lines.concat(
+ g.wrapString(Locale.date(start), g.getWidth()-10),
+ g.wrapString(/*LANG*/"Start"+": "+formatDateLong(start, includeDay, ev.allDay), g.getWidth()-10),
+ g.wrapString(/*LANG*/"End"+": "+formatDateLong(end, includeDay, ev.allDay), g.getWidth()-10));
+ }
+ if(ev.location)
+ lines = lines.concat(/*LANG*/"Location"+": ", g.wrapString(ev.location, g.getWidth()-10));
+ if(ev.description)
+ lines = lines.concat("",g.wrapString(ev.description, g.getWidth()-10));
+ if(ev.calName)
+ lines = lines.concat(/*LANG*/"Calendar"+": ", g.wrapString(ev.calName, g.getWidth()-10));
+ lines = lines.concat(["",/*LANG*/"< Back"]);
+ E.showScroller({
+ h : g.getFontHeight(), // height of each menu item in pixels
+ c : lines.length, // number of menu items
+ // a function to draw a menu item
+ draw : function(idx, r) {
+ // FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12
+ g.setBgColor(idx=lines.length-2)
+ showList();
+ },
+ back : () => showList()
+ });
+}
+
+function showList() {
+ //it might take time for GB to delete old events, decide whether to show them grayed out or hide entirely
+ if(!settings.pastEvents) {
+ let now = new Date();
+ //TODO add threshold here?
+ CALENDAR = CALENDAR.filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000);
+ }
+ if(CALENDAR.length == 0) {
+ E.showMessage("No events");
+ return;
+ }
+ E.showScroller({
+ h : 52,
+ c : Math.max(CALENDAR.length,3), // workaround for 2v10.219 firmware (min 3 not needed for 2v11)
+ draw : function(idx, r) {"ram"
+ var ev = CALENDAR[idx];
+ g.setColor(g.theme.fg);
+ g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
+ if (!ev) return;
+ var isPast = false;
+ var x = r.x+2, title = ev.title;
+ var body = formatDateShort(getDate(ev.timestamp),ev.allDay)+"\n"+(ev.location?ev.location:/*LANG*/"No location");
+ if(settings.pastEvents) isPast = ev.timestamp + ev.durationInSeconds < (new Date())/1000;
+ if (title) g.setFontAlign(-1,-1).setFont(fontBig)
+ .setColor(isPast ? "#888" : g.theme.fg).drawString(title, x+4,r.y+2);
+ if (body) {
+ g.setFontAlign(-1,-1).setFont(fontMedium).setColor(isPast ? "#888" : g.theme.fg);
+ g.drawString(body, x+10,r.y+20);
+ }
+ g.setColor("#888").fillRect(r.x,r.y+r.h-1,r.x+r.w-1,r.y+r.h-1); // dividing line between items
+ if(ev.color) {
+ g.setColor("#"+(0x1000000+Number(ev.color)).toString(16).padStart(6,"0"));
+ g.fillRect(r.x,r.y+4,r.x+3, r.y+r.h-4);
+ }
+ },
+ select : idx => showEvent(CALENDAR[idx]),
+ back : () => load()
+ });
+}
+showList();
diff --git a/apps/agenda/agenda.png b/apps/agenda/agenda.png
new file mode 100644
index 000000000..c850b0e5d
Binary files /dev/null and b/apps/agenda/agenda.png differ
diff --git a/apps/agenda/metadata.json b/apps/agenda/metadata.json
new file mode 100644
index 000000000..982870905
--- /dev/null
+++ b/apps/agenda/metadata.json
@@ -0,0 +1,18 @@
+{
+ "id": "agenda",
+ "name": "Agenda",
+ "version": "0.05",
+ "description": "Simple agenda",
+ "icon": "agenda.png",
+ "screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
+ "tags": "agenda",
+ "supports": ["BANGLEJS","BANGLEJS2"],
+ "readme": "README.md",
+ "allow_emulator": true,
+ "storage": [
+ {"name":"agenda.app.js","url":"agenda.js"},
+ {"name":"agenda.settings.js","url":"settings.js"},
+ {"name":"agenda.img","url":"agenda-icon.js","evaluate":true}
+ ],
+ "data": [{"name":"agenda.settings.json"}]
+}
diff --git a/apps/agenda/screenshot_agenda_event1.png b/apps/agenda/screenshot_agenda_event1.png
new file mode 100644
index 000000000..581da286b
Binary files /dev/null and b/apps/agenda/screenshot_agenda_event1.png differ
diff --git a/apps/agenda/screenshot_agenda_event2.png b/apps/agenda/screenshot_agenda_event2.png
new file mode 100644
index 000000000..f5edcaae8
Binary files /dev/null and b/apps/agenda/screenshot_agenda_event2.png differ
diff --git a/apps/agenda/screenshot_agenda_overview.png b/apps/agenda/screenshot_agenda_overview.png
new file mode 100644
index 000000000..a2030d05f
Binary files /dev/null and b/apps/agenda/screenshot_agenda_overview.png differ
diff --git a/apps/agenda/settings.js b/apps/agenda/settings.js
new file mode 100644
index 000000000..4220fcb63
--- /dev/null
+++ b/apps/agenda/settings.js
@@ -0,0 +1,48 @@
+(function(back) {
+ function gbSend(message) {
+ Bluetooth.println("");
+ Bluetooth.println(JSON.stringify(message));
+ }
+ var settings = require("Storage").readJSON("agenda.settings.json",1)||{};
+ function updateSettings() {
+ require("Storage").writeJSON("agenda.settings.json", settings);
+ }
+ var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[];
+ var mainmenu = {
+ "" : { "title" : "Agenda" },
+ "< Back" : back,
+ /*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?/*LANG*/"Yes":/*LANG*/"No" },
+ /*LANG*/"Force calendar sync" : () => {
+ if(NRF.getSecurityStatus().connected) {
+ E.showPrompt(/*LANG*/"Do you want to also clear the internal database first?", {
+ buttons: {/*LANG*/"Yes": 1, /*LANG*/"No": 2, /*LANG*/"Cancel": 3}
+ }).then((v)=>{
+ switch(v) {
+ case 1:
+ require("Storage").writeJSON("android.calendar.json",[]);
+ CALENDAR = [];
+ /* falls through */
+ case 2:
+ gbSend({t:"force_calendar_sync", ids: CALENDAR.map(e=>e.id)});
+ E.showAlert(/*LANG*/"Request sent to the phone").then(()=>E.showMenu(mainmenu));
+ break;
+ case 3:
+ default:
+ E.showMenu(mainmenu);
+ return;
+ }
+ });
+ } else {
+ E.showAlert(/*LANG*/"You are not connected").then(()=>E.showMenu(mainmenu));
+ }
+ },
+ /*LANG*/"Show past events" : {
+ value : !!settings.pastEvents,
+ onchange: v => {
+ settings.pastEvents = v;
+ updateSettings();
+ }
+ },
+ };
+ E.showMenu(mainmenu);
+})
diff --git a/apps/agpsdata/ChangeLog b/apps/agpsdata/ChangeLog
new file mode 100644
index 000000000..ae26512de
--- /dev/null
+++ b/apps/agpsdata/ChangeLog
@@ -0,0 +1,2 @@
+0.01: First, proof of concept
+0.02: Load AGPS data on app start and automatically in background
diff --git a/apps/agpsdata/README.md b/apps/agpsdata/README.md
new file mode 100644
index 000000000..57bb055a1
--- /dev/null
+++ b/apps/agpsdata/README.md
@@ -0,0 +1,19 @@
+# A-GPS Data
+
+Load assisted GPS (A-GPS) data directly to your Bangle.js using the new http requests on Android GadgetBridge.
+
+Will download A-GPS data in background (if enabled in settings).
+
+The GNSS type can be configured in the settings.
+
+Make sure:
+* your GadgetBridge version supports http requests
+* turn on internet access in GadgetBridge settings
+
+Currently proof of concept on Bangle.js 2 only.
+
+## Creator
+[@pidajo](https://github.com/pidajo)
+
+## Contributor
+[@myxor](https://github.com/myxor)
diff --git a/apps/agpsdata/agpsdata-icon.js b/apps/agpsdata/agpsdata-icon.js
new file mode 100644
index 000000000..1677a2177
--- /dev/null
+++ b/apps/agpsdata/agpsdata-icon.js
@@ -0,0 +1 @@
+atob("MDCEAAAAAAAAAAAAAAAAiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIiAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIiAAAAAAAAAAAAAAAAAAAAAAAAAAACIiIiIAAAAAAAAAAAAAAAAAAAAAAAAAACIiIiIAAAAAAAAAAAAAAAAAAAAAAAAAACIiIiIAAAAAAAAAAAAAAAAAAAAAAAAAAiIiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAiIiIiIgAAAAAAAAAAAAAAAAAAAAAAAAIiIOIiIiAAAAAAAAAAAAAAAAAAAAAAAAIiDOIiIiAAAAAAAAAAAAAAAAAAAAAAAAIiDOIiIiIAAAAAAAAAAAAAAAAAAAAAACIiPOIiIiIAAAAAAAAAAAAAAAAAAAAAAiIj/OIiIiIgAAAAAAAAAAAAAAAAAAAAAiI//OIiIiIgAAAAAAAAAAAAAAAAAAAAAiI//OIiIiIiAAAAAAAAAAAAAAAAAAAAAiD//OIiIiIiAAAAAAAAAAAAAAAAAAAAIiP//OIiIiIiAAAAAAAAAAAAAAAAAAAAIg///OIiIiIiIAAAAAAAAAAAAAAAAAACIj///OIiIiIiIAAAAAAAAAAAAAAAAAACIP///OIiIiIiIgAAAAAAAAAAAAAAAAACI////OIiIiIiIgAAAAAAAAAAAAAAAAAiD////OIiIiIiIiAAAAAAAAAAAAAAAAAiP////OIiIiIiIiAAAAAAAAAAAAAAAAIiP////OIiIiIiIiIAAAAAAAAAAAAAAAIj/////OIiIiIiIiIAAAAAAAAAAAAAACIj/////OIiIiIiIiIgAAAAAAAAAAAAACI//////OIiIiIiIiIgAAAAAAAAAAAAAiI//////OIiIiIiIiIgAAAAAAAAAAAAAiIiIiIiIgzMzMzMziIiAAAAAAAAAAAAAiIiIiIiIj///////+IiAAAAAAAAAAAAIiIiIiIiIj////////4iIAAAAAAAAAAAIiIiIiIiIgzMzMzMzM4iIAAAAAAAAAACIP///////OIiIiIiIiIiIgAAAAAAAAACI////////OIiIiIiIiIiIgAAAAAAAAAiI////////OIiIiIiIiIiIiAAAAAAAAAiP////////OIiIiIiIiIiIiAAAAAAAAIiP////////OIiIiIiIiIiIiIAAAAAAAIj/////////OIiIiIiIiIiIiIAAAAAACIj////////ziIiIiIiIiIiIiIgAAAAACIP///////+IiIiIiIiIiIiIiIgAAAAACI//////84gYiIiIiIiIiIiIiIgAAAAAiD////84iIiAAAiIiIiIiIiIiIiAAAAAiP///ziIiIAAAAAIiIiIiIiIiIiAAAAIg/8ziIiIAAAAAAAAAIiIiIiIiIiIAAAIj/iIiIAAAAAAAAAAAAAIiIiIiIiIAACIiIiBgAAAAAAAAAAAAAAACIiIiIiIgACIiIgAAAAAAAAAAAAAAAAAAACIiIiIgACIgAAAAAAAAAAAAAAAAAAAAAAAiIiIgA==")
diff --git a/apps/agpsdata/agpsdata.png b/apps/agpsdata/agpsdata.png
new file mode 100644
index 000000000..a0f4de4cb
Binary files /dev/null and b/apps/agpsdata/agpsdata.png differ
diff --git a/apps/agpsdata/app.js b/apps/agpsdata/app.js
new file mode 100644
index 000000000..647723bb4
--- /dev/null
+++ b/apps/agpsdata/app.js
@@ -0,0 +1,54 @@
+function display(text1, text2) {
+ g.reset();
+ g.clear();
+ var img = require("Storage").read("agpsdata.img");
+ if (img) {
+ g.drawImage(img, g.getWidth() - 48, g.getHeight() - 48 - 24);
+ }
+ g.setFont("Vector", 18);
+ g.setFontAlign(0, 1);
+ g.drawString(text1, g.getWidth() / 2, g.getHeight() / 3 + 24);
+ if (text2 != undefined) {
+ g.setFont("Vector", 12);
+ g.setFontAlign(-1, -1);
+ g.drawString(text2, 5, g.getHeight() / 3 + 29);
+ }
+ Bangle.drawWidgets();
+}
+
+// Show launcher when middle button pressed
+// Load widgets
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+
+let waiting = false;
+
+function start() {
+ g.reset();
+ g.clear();
+ waiting = false;
+ display("Retry?", "touch to retry");
+ Bangle.on("touch", () => { updateAgps(); });
+}
+
+function updateAgps() {
+ g.reset();
+ g.clear();
+ if (!waiting) {
+ waiting = true;
+ display("Updating A-GPS...");
+ require("agpsdata").pull(function() {
+ waiting = false;
+ display("A-GPS updated.", "touch to close");
+ Bangle.on("touch", () => { load(); });
+ },
+ function(error) {
+ waiting = false;
+ E.showAlert(error, "Error")
+ .then(() => { start(); });
+ });
+ } else {
+ display("Waiting...");
+ }
+}
+updateAgps();
diff --git a/apps/agpsdata/boot.js b/apps/agpsdata/boot.js
new file mode 100644
index 000000000..6415f0b52
--- /dev/null
+++ b/apps/agpsdata/boot.js
@@ -0,0 +1,33 @@
+(function() {
+ let waiting = false;
+ let settings = require("Storage").readJSON("agpsdata.settings.json", 1) || {
+ enabled: true,
+ refresh: 1440
+ };
+
+ if (settings.refresh == undefined) settings.refresh = 1440;
+
+ function successCallback(){
+ waiting = false;
+ }
+
+ function errorCallback(){
+ waiting = false;
+ }
+
+ if (settings.enabled) {
+ let lastUpdate = settings.lastUpdate;
+ if (!lastUpdate || lastUpdate + settings.refresh * 1000 * 60 < Date.now()){
+ if (!waiting){
+ waiting = true;
+ require("agpsdata").pull(successCallback, errorCallback);
+ }
+ }
+ setInterval(() => {
+ if (!waiting && NRF.getSecurityStatus().connected){
+ waiting = true;
+ require("agpsdata").pull(successCallback, errorCallback);
+ }
+ }, settings.refresh * 1000 * 60);
+ }
+})();
diff --git a/apps/agpsdata/default.json b/apps/agpsdata/default.json
new file mode 100644
index 000000000..0b6e0cecf
--- /dev/null
+++ b/apps/agpsdata/default.json
@@ -0,0 +1 @@
+{"enabled":true,"refresh":1440,"gnsstype":1}
diff --git a/apps/agpsdata/lib.js b/apps/agpsdata/lib.js
new file mode 100644
index 000000000..7d9758c0a
--- /dev/null
+++ b/apps/agpsdata/lib.js
@@ -0,0 +1,75 @@
+function readSettings() {
+ settings = Object.assign(
+ require('Storage').readJSON("agpsdata.default.json", true) || {},
+ require('Storage').readJSON(FILE, true) || {});
+}
+
+var FILE = "agpsdata.settings.json";
+var settings;
+readSettings();
+
+function setAGPS(data) {
+ var js = jsFromBase64(data);
+ try {
+ eval(js);
+ return true;
+ }
+ catch(e) {
+ console.log("error:", e);
+ }
+ return false;
+}
+
+function jsFromBase64(b64) {
+ var bin = atob(b64);
+ var chunkSize = 128;
+ var js = "Bangle.setGPSPower(1);\n"; // turn GPS on
+ var gnsstype = settings.gnsstype || 1; // default GPS
+ js += `Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnsstype)}")\n`; // set GNSS mode
+ // What about:
+ // NAV-TIMEUTC (0x01 0x10)
+ // NAV-PV (0x01 0x03)
+ // or AGPS.zip uses AID-INI (0x0B 0x01)
+
+ for (var i=0;i {
+ let result = setAGPS(event.resp);
+ if (result) {
+ updateLastUpdate();
+ if (successCallback) successCallback();
+ } else {
+ console.log("error applying AGPS data");
+ if (failureCallback) failureCallback("Error applying AGPS data");
+ }
+ }).catch((e)=>{
+ console.log("error", e);
+ if (failureCallback) failureCallback(e);
+ });
+ } else {
+ console.log("error: No http method found");
+ if (failureCallback) failureCallback(/*LANG*/"No http method");
+ }
+};
diff --git a/apps/agpsdata/metadata.json b/apps/agpsdata/metadata.json
new file mode 100644
index 000000000..d3863be52
--- /dev/null
+++ b/apps/agpsdata/metadata.json
@@ -0,0 +1,24 @@
+{ "id": "agpsdata",
+ "name": "A-GPS Data",
+ "shortName":"A-GPS Data",
+ "icon": "agpsdata.png",
+ "version":"0.02",
+ "description": "Download assisted GPS (A-GPS) data directly to your Bangle.js **using Gadgetbridge**",
+ "tags": "boot,tool,assisted,gps,agps,http",
+ "allow_emulator":true,
+ "supports": ["BANGLEJS2"],
+ "readme":"README.md",
+ "screenshots" : [ { "url":"screenshot.png" }, { "url":"screenshot2.png" } ],
+ "storage": [
+ {"name":"agpsdata.app.js","url":"app.js"},
+ {"name":"agpsdata.img","url":"agpsdata-icon.js","evaluate":true},
+ {"name":"agpsdata.default.json","url":"default.json"},
+ {"name":"agpsdata.boot.js","url":"boot.js"},
+ {"name":"agpsdata","url":"lib.js"},
+ {"name":"agpsdata.settings.js","url":"settings.js"}
+ ],
+ "data": [
+ {"name": "agpsdata.json"},
+ {"name": "agpsdata.settings.json"}
+ ]
+}
diff --git a/apps/agpsdata/screenshot.png b/apps/agpsdata/screenshot.png
new file mode 100644
index 000000000..1fcb2d8ee
Binary files /dev/null and b/apps/agpsdata/screenshot.png differ
diff --git a/apps/agpsdata/screenshot2.png b/apps/agpsdata/screenshot2.png
new file mode 100644
index 000000000..7c546e4b5
Binary files /dev/null and b/apps/agpsdata/screenshot2.png differ
diff --git a/apps/agpsdata/settings.js b/apps/agpsdata/settings.js
new file mode 100644
index 000000000..80a2f3956
--- /dev/null
+++ b/apps/agpsdata/settings.js
@@ -0,0 +1,71 @@
+(function(back) {
+function writeSettings(key, value) {
+ var s = Object.assign(
+ require('Storage').readJSON(settingsDefaultFile, true) || {},
+ require('Storage').readJSON(settingsFile, true) || {});
+ s[key] = value;
+ require('Storage').writeJSON(settingsFile, s);
+ readSettings();
+}
+
+function readSettings() {
+ settings = Object.assign(
+ require('Storage').readJSON(settingsDefaultFile, true) || {},
+ require('Storage').readJSON(settingsFile, true) || {});
+}
+
+var settingsFile = "agpsdata.settings.json";
+var settingsDefaultFile = "agpsdata.default.json";
+
+var settings;
+readSettings();
+
+const gnsstypes = [
+ "", "GPS", "BDS", "GPS+BDS", "GLONASS", "GPS+GLONASS", "BDS+GLONASS",
+ "GPS+BDS+GLON."
+];
+
+function buildMainMenu() {
+ var mainmenu = {
+ '' : {'title' : 'AGPS download'},
+ '< Back' : back,
+ "Enabled" : {
+ value : !!settings.enabled,
+ onchange : v => { writeSettings("enabled", v); }
+ },
+ "Refresh every" : {
+ value : settings.refresh / 60,
+ min : 1,
+ max : 168,
+ step : 1,
+ format : v => v + "h",
+ onchange : v => { writeSettings("refresh", Math.round(v * 60)); }
+ },
+ "GNSS type" : {
+ value : settings.gnsstype,
+ min : 1,
+ max : 7,
+ step : 1,
+ format : v => gnsstypes[v],
+ onchange : x => writeSettings('gnsstype', x)
+ },
+ "Force refresh" : () => {
+ E.showMessage("Loading A-GPS data");
+ require("agpsdata")
+ .pull(
+ function() {
+ E.showAlert("Success").then(
+ () => { E.showMenu(buildMainMenu()); });
+ },
+ function(error) {
+ E.showAlert(error, "Error")
+ .then(() => { E.showMenu(buildMainMenu()); });
+ });
+ }
+ };
+
+ return mainmenu;
+}
+
+E.showMenu(buildMainMenu());
+});
diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog
index 41dd93081..52ee8bf9c 100644
--- a/apps/alarm/ChangeLog
+++ b/apps/alarm/ChangeLog
@@ -24,3 +24,14 @@
0.23: Fix regression with Days of Week (#1735)
0.24: Automatically save the alarm/timer when the user returns to the main menu using the back arrow
Add "Enable All", "Disable All" and "Remove All" actions
+0.25: Fix redrawing selected Alarm/Timer entry inside edit submenu
+0.26: Add support for Monday as first day of the week (#1780)
+0.27: New UI!
+0.28: Fix bug with alarms not firing when configured to fire only once
+0.29: Fix wrong 'dow' handling in new timer if first day of week is Monday
+0.30: Fix "Enable All"
+0.31: Add seconds to timers
+0.32: Fix wrong hidden filter
+ Add option for auto-delete a timer after it expires
+0.33: Allow hiding timers&alarms
+
diff --git a/apps/alarm/README.md b/apps/alarm/README.md
index e979dbaf1..741946b0c 100644
--- a/apps/alarm/README.md
+++ b/apps/alarm/README.md
@@ -1,7 +1,31 @@
-Alarms & Timers
-===============
+# Alarms & Timers
This app allows you to add/modify any alarms and timers.
-It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched)
-to handle the alarm scheduling in an efficient way that can work alongside other apps.
+It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.
+
+## Menu overview
+
+- `New...`
+ - `New Alarm` → Configure a new alarm
+ - `Repeat` → Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely)
+ - `New Timer` → Configure a new timer
+- `Advanced`
+ - `Scheduler settings` → Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details
+ - `Enable All` → Enable _all_ disabled alarms & timers
+ - `Disable All` → Disable _all_ enabled alarms & timers
+ - `Delete All` → Delete _all_ alarms & timers
+
+## Creator
+
+- [Gordon Williams](https://github.com/gfwilliams)
+
+## Main Contributors
+
+- [Alessandro Cocco](https://github.com/alessandrococco) - New UI, full rewrite, new features
+- [Sabin Iacob](https://github.com/m0n5t3r) - Auto snooze support
+- [storm64](https://github.com/storm64) - Fix redrawing in submenus
+
+## Attributions
+
+All icons used in this app are from [icons8](https://icons8.com).
diff --git a/apps/alarm/app.js b/apps/alarm/app.js
index 3b3421115..ed5aa608a 100644
--- a/apps/alarm/app.js
+++ b/apps/alarm/app.js
@@ -1,240 +1,386 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
+// 0 = Sunday (default), 1 = Monday
+const firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0;
+const WORKDAYS = 62
+const WEEKEND = firstDayOfWeek ? 192 : 65;
+const EVERY_DAY = firstDayOfWeek ? 254 : 127;
+
+const iconAlarmOn = "\0" + atob("GBiBAAAAAAAAAAYAYA4AcBx+ODn/nAP/wAf/4A/n8A/n8B/n+B/n+B/n+B/n+B/h+B/4+A/+8A//8Af/4AP/wAH/gAB+AAAAAAAAAA==");
+const iconAlarmOff = "\0" + (g.theme.dark
+ ? atob("GBjBAP////8AAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg=")
+ : atob("GBjBAP//AAAAAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg="));
+
+const iconTimerOn = "\0" + (g.theme.dark
+ ? atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA=")
+ : atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA="));
+const iconTimerOff = "\0" + (g.theme.dark
+ ? atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg=")
+ : atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg="));
+
// An array of alarm objects (see sched/README.md)
-let alarms = require("sched").getAlarms();
+var alarms = require("sched").getAlarms();
-function getCurrentTime() {
- let time = new Date();
- return (
- time.getHours() * 3600000 +
- time.getMinutes() * 60000 +
- time.getSeconds() * 1000
- );
+function handleFirstDayOfWeek(dow) {
+ if (firstDayOfWeek == 1) {
+ if ((dow & 1) == 1) {
+ // In the scheduler API Sunday is 1.
+ // Here the week starts on Monday and Sunday is ON so
+ // when I read the dow I need to move Sunday to 128...
+ dow += 127;
+ } else if ((dow & 128) == 128) {
+ // ... and then when I write the dow I need to move Sunday back to 1.
+ dow -= 127;
+ }
+ }
+ return dow;
}
-function saveAndReload() {
- require("sched").setAlarms(alarms);
- require("sched").reload();
-}
+// Check the first day of week and update the dow field accordingly (alarms only!)
+alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
function showMainMenu() {
- // Timer img "\0"+atob("DhKBAP////MDDAwwMGGBzgPwB4AeAPwHOBhgwMMzDez////w")
- // Alarm img "\0"+atob("FBSBAABgA4YcMPDGP8Zn/mx/48//PP/zD/8A//AP/wD/8A//AP/wH/+D//w//8AAAADwAAYA")
const menu = {
- '': { 'title': /*LANG*/'Alarms&Timers' },
- /*LANG*/'< Back' : ()=>{load();},
- /*LANG*/'New Alarm': ()=>editAlarm(-1),
- /*LANG*/'New Timer': ()=>editTimer(-1)
+ "": { "title": /*LANG*/"Alarms & Timers" },
+ "< Back": () => load(),
+ /*LANG*/"New...": () => showNewMenu()
};
- alarms.forEach((alarm,idx)=>{
- var type,txt; // a leading space is currently required (JS error in Espruino 2v12)
- if (alarm.timer) {
- type = /*LANG*/"Timer";
- txt = " "+require("sched").formatTime(alarm.timer);
- } else {
- type = /*LANG*/"Alarm";
- txt = " "+require("sched").formatTime(alarm.t);
- }
- if (alarm.rp) txt += "\0"+atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA=");
- // rename duplicate alarms
- if (menu[type+txt]) {
- var n = 2;
- while (menu[type+" "+n+txt]) n++;
- txt = type+" "+n+txt;
- } else txt = type+txt;
- // add to menu
- menu[txt] = {
- value : "\0"+atob(alarm.on?"EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g":"EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"),
- onchange : function() {
- if (alarm.timer) editTimer(idx, alarm);
- else editAlarm(idx, alarm);
- }
+
+ alarms.forEach((e, index) => {
+ var label = e.timer
+ ? require("time_utils").formatDuration(e.timer)
+ : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : "");
+ menu[label] = {
+ value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
+ onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
};
});
- if (alarms.some(e => !e.on)) {
- menu[/*LANG*/"Enable All"] = () => enableAll(true);
- }
- if (alarms.some(e => e.on)) {
- menu[/*LANG*/"Disable All"] = () => enableAll(false);
- }
- if (alarms.length > 0) {
- menu[/*LANG*/"Delete All"] = () => deleteAll();
- }
+ menu[/*LANG*/"Advanced"] = () => showAdvancedMenu();
- if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
- return E.showMenu(menu);
-}
-
-function editDOW(dow, onchange) {
- const menu = {
- '': { 'title': /*LANG*/'Days of Week' },
- /*LANG*/'< Back' : () => onchange(dow)
- };
- for (let i = 0; i < 7; i++) (i => {
- let dayOfWeek = require("locale").dow({ getDay: () => i });
- menu[dayOfWeek] = {
- value: !!(dow&(1< v ? /*LANG*/"Yes" : /*LANG*/"No",
- onchange: v => v ? dow |= 1< showMainMenu(),
+ /*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined),
+ /*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined)
+ });
+}
+
+function showEditAlarmMenu(selectedAlarm, alarmIndex) {
+ var isNew = alarmIndex === undefined;
+
+ var alarm = require("sched").newDefaultAlarm();
+ alarm.dow = handleFirstDayOfWeek(alarm.dow);
+
+ if (selectedAlarm) {
+ Object.assign(alarm, selectedAlarm);
+ }
+
+ var time = require("time_utils").decodeTime(alarm.t);
const menu = {
- '': { 'title': /*LANG*/'Alarm' },
- /*LANG*/'< Back': () => {
- saveAlarm(newAlarm, alarmIndex, a, t);
+ "": { "title": isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm" },
+ "< Back": () => {
+ prepareAlarmForSave(alarm, alarmIndex, time);
+ saveAndReload();
showMainMenu();
},
- /*LANG*/'Hours': {
- value: t.hrs, min : 0, max : 23, wrap : true,
- onchange: v => t.hrs=v
+ /*LANG*/"Hour": {
+ value: time.h,
+ format: v => ("0" + v).substr(-2),
+ min: 0,
+ max: 23,
+ wrap: true,
+ onchange: v => time.h = v
},
- /*LANG*/'Minutes': {
- value: t.mins, min : 0, max : 59, wrap : true,
- onchange: v => t.mins=v
+ /*LANG*/"Minute": {
+ value: time.m,
+ format: v => ("0" + v).substr(-2),
+ min: 0,
+ max: 59,
+ wrap: true,
+ onchange: v => time.m = v
},
- /*LANG*/'Enabled': {
- value: a.on,
- format: v => v ? /*LANG*/"On" : /*LANG*/"Off",
- onchange: v=>a.on=v
+ /*LANG*/"Enabled": {
+ value: alarm.on,
+ onchange: v => alarm.on = v
},
- /*LANG*/'Repeat': {
- value: a.rp,
- format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
- onchange: v => a.rp = v
- },
- /*LANG*/'Days': {
- value: "SMTWTFS".split("").map((d,n)=>a.dow&(1< editDOW(a.dow, d => {
- a.dow = d;
- a.t = require("sched").encodeTime(t);
- editAlarm(alarmIndex, a);
+ /*LANG*/"Repeat": {
+ value: decodeDOW(alarm),
+ onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => {
+ alarm.rp = repeat;
+ alarm.dow = dow;
+ alarm.t = require("time_utils").encodeTime(time);
+ setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
})
},
- /*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
- /*LANG*/'Auto Snooze': {
- value: a.as,
- format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
- onchange: v => a.as = v
+ /*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v),
+ /*LANG*/"Auto Snooze": {
+ value: alarm.as,
+ onchange: v => alarm.as = v
+ },
+ /*LANG*/"Hidden": {
+ value: alarm.hidden || false,
+ onchange: v => alarm.hidden = v
+ },
+ /*LANG*/"Cancel": () => showMainMenu()
+ };
+
+ if (!isNew) {
+ menu[/*LANG*/"Delete"] = () => {
+ E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => {
+ if (confirm) {
+ alarms.splice(alarmIndex, 1);
+ saveAndReload();
+ showMainMenu();
+ } else {
+ alarm.t = require("time_utils").encodeTime(time);
+ setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
+ }
+ });
+ };
+ }
+
+ E.showMenu(menu);
+}
+
+function prepareAlarmForSave(alarm, alarmIndex, time) {
+ alarm.t = require("time_utils").encodeTime(time);
+ alarm.last = alarm.t < require("time_utils").getCurrentTimeMillis() ? new Date().getDate() : 0;
+
+ if (alarmIndex === undefined) {
+ alarms.push(alarm);
+ } else {
+ alarms[alarmIndex] = alarm;
+ }
+}
+
+function saveAndReload() {
+ // Before saving revert the dow to the standard format (alarms only!)
+ alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
+
+ require("sched").setAlarms(alarms);
+ require("sched").reload();
+
+ // Fix after save
+ alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
+}
+
+function decodeDOW(alarm) {
+ return alarm.rp
+ ? require("date_utils")
+ .dows(firstDayOfWeek, 2)
+ .map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_")
+ .join("")
+ .toLowerCase()
+ : "Once"
+}
+
+function showEditRepeatMenu(repeat, dow, dowChangeCallback) {
+ var originalRepeat = repeat;
+ var originalDow = dow;
+ var isCustom = repeat && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY;
+
+ const menu = {
+ "": { "title": /*LANG*/"Repeat Alarm" },
+ "< Back": () => dowChangeCallback(repeat, dow),
+ /*LANG*/"Once": {
+ // The alarm will fire once. Internally it will be saved
+ // as "fire every days" BUT the repeat flag is false so
+ // we avoid messing up with the scheduler.
+ value: !repeat,
+ onchange: () => dowChangeCallback(false, EVERY_DAY)
+ },
+ /*LANG*/"Workdays": {
+ value: repeat && dow == WORKDAYS,
+ onchange: () => dowChangeCallback(true, WORKDAYS)
+ },
+ /*LANG*/"Weekends": {
+ value: repeat && dow == WEEKEND,
+ onchange: () => dowChangeCallback(true, WEEKEND)
+ },
+ /*LANG*/"Every Day": {
+ value: repeat && dow == EVERY_DAY,
+ onchange: () => dowChangeCallback(true, EVERY_DAY)
+ },
+ /*LANG*/"Custom": {
+ value: isCustom ? decodeDOW({ rp: true, dow: dow }) : false,
+ onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalRepeat, originalDow)
}
};
- menu[/*LANG*/"Cancel"] = () => showMainMenu();
-
- if (!newAlarm) {
- menu[/*LANG*/"Delete"] = function () {
- alarms.splice(alarmIndex, 1);
- saveAndReload();
- showMainMenu();
- };
- }
-
- return E.showMenu(menu);
+ E.showMenu(menu);
}
-function saveAlarm(newAlarm, alarmIndex, a, t) {
- a.t = require("sched").encodeTime(t);
- a.last = (a.t < getCurrentTime()) ? (new Date()).getDate() : 0;
-
- if (newAlarm) {
- alarms.push(a);
- } else {
- alarms[alarmIndex] = a;
- }
-
- saveAndReload();
-}
-
-function editTimer(alarmIndex, alarm) {
- let newAlarm = alarmIndex < 0;
- let a = require("sched").newDefaultTimer();
- if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
- if (alarm) Object.assign(a,alarm);
- let t = require("sched").decodeTime(a.timer);
-
+function showCustomDaysMenu(dow, dowChangeCallback, originalRepeat, originalDow) {
const menu = {
- '': { 'title': /*LANG*/'Timer' },
- /*LANG*/'< Back': () => {
- saveTimer(newAlarm, alarmIndex, a, t);
- showMainMenu();
- },
- /*LANG*/'Hours': {
- value: t.hrs, min : 0, max : 23, wrap : true,
- onchange: v => t.hrs=v
- },
- /*LANG*/'Minutes': {
- value: t.mins, min : 0, max : 59, wrap : true,
- onchange: v => t.mins=v
- },
- /*LANG*/'Enabled': {
- value: a.on,
- format: v => v ? /*LANG*/"On" : /*LANG*/"Off",
- onchange: v => a.on = v
- },
- /*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
+ "": { "title": /*LANG*/"Custom Days" },
+ "< Back": () => {
+ // If the user unchecks all the days then we assume repeat = once
+ // and we force the dow to every day.
+ var repeat = dow > 0;
+ dowChangeCallback(repeat, repeat ? dow : EVERY_DAY)
+ }
};
- menu[/*LANG*/"Cancel"] = () => showMainMenu();
-
- if (!newAlarm) {
- menu[/*LANG*/"Delete"] = function() {
- alarms.splice(alarmIndex,1);
- saveAndReload();
- showMainMenu();
+ require("date_utils").dows(firstDayOfWeek).forEach((day, i) => {
+ menu[day] = {
+ value: !!(dow & (1 << (i + firstDayOfWeek))),
+ onchange: v => v ? (dow |= 1 << (i + firstDayOfWeek)) : (dow &= ~(1 << (i + firstDayOfWeek)))
};
- }
- return E.showMenu(menu);
+ });
+
+ menu[/*LANG*/"Cancel"] = () => setTimeout(showEditRepeatMenu, 10, originalRepeat, originalDow, dowChangeCallback)
+
+ E.showMenu(menu);
}
-function saveTimer(newAlarm, alarmIndex, a, t) {
- a.timer = require("sched").encodeTime(t);
- a.t = getCurrentTime() + a.timer;
- a.last = 0;
+function showEditTimerMenu(selectedTimer, timerIndex) {
+ var isNew = timerIndex === undefined;
- if (newAlarm) {
- alarms.push(a);
- } else {
- alarms[alarmIndex] = a;
+ var timer = require("sched").newDefaultTimer();
+
+ if (selectedTimer) {
+ Object.assign(timer, selectedTimer);
}
- saveAndReload();
+ var time = require("time_utils").decodeTime(timer.timer);
+
+ const menu = {
+ "": { "title": isNew ? /*LANG*/"New Timer" : /*LANG*/"Edit Timer" },
+ "< Back": () => {
+ prepareTimerForSave(timer, timerIndex, time);
+ saveAndReload();
+ showMainMenu();
+ },
+ /*LANG*/"Hours": {
+ value: time.h,
+ min: 0,
+ max: 23,
+ wrap: true,
+ onchange: v => time.h = v
+ },
+ /*LANG*/"Minutes": {
+ value: time.m,
+ min: 0,
+ max: 59,
+ wrap: true,
+ onchange: v => time.m = v
+ },
+ /*LANG*/"Seconds": {
+ value: time.s,
+ min: 0,
+ max: 59,
+ step: 1,
+ wrap: true,
+ onchange: v => time.s = v
+ },
+ /*LANG*/"Enabled": {
+ value: timer.on,
+ onchange: v => timer.on = v
+ },
+ /*LANG*/"Delete After Expiration": {
+ value: timer.del,
+ onchange: v => timer.del = v
+ },
+ /*LANG*/"Hidden": {
+ value: timer.hidden || false,
+ onchange: v => timer.hidden = v
+ },
+ /*LANG*/"Vibrate": require("buzz_menu").pattern(timer.vibrate, v => timer.vibrate = v),
+ /*LANG*/"Cancel": () => showMainMenu()
+ };
+
+ if (!isNew) {
+ menu[/*LANG*/"Delete"] = () => {
+ E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {
+ if (confirm) {
+ alarms.splice(timerIndex, 1);
+ saveAndReload();
+ showMainMenu();
+ } else {
+ timer.timer = require("time_utils").encodeTime(time);
+ setTimeout(showEditTimerMenu, 10, timer, timerIndex)
+ }
+ });
+ };
+ }
+
+ E.showMenu(menu);
+}
+
+function prepareTimerForSave(timer, timerIndex, time) {
+ timer.timer = require("time_utils").encodeTime(time);
+ timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer;
+ timer.last = 0;
+
+ if (timerIndex === undefined) {
+ alarms.push(timer);
+ } else {
+ alarms[timerIndex] = timer;
+ }
+}
+
+function showAdvancedMenu() {
+ E.showMenu({
+ "": { "title": /*LANG*/"Advanced" },
+ "< Back": () => showMainMenu(),
+ /*LANG*/"Scheduler Settings": () => eval(require("Storage").read("sched.settings.js"))(() => showAdvancedMenu()),
+ /*LANG*/"Enable All": () => enableAll(true),
+ /*LANG*/"Disable All": () => enableAll(false),
+ /*LANG*/"Delete All": () => deleteAll()
+ });
}
function enableAll(on) {
- E.showPrompt(/*LANG*/"Are you sure?", {
- title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All"
- }).then((confirm) => {
- if (confirm) {
- alarms.forEach(alarm => alarm.on = on);
- saveAndReload();
- }
-
- showMainMenu();
- });
+ if (alarms.filter(e => e.on == !on).length == 0) {
+ E.showAlert(
+ on ? /*LANG*/"Nothing to Enable" : /*LANG*/"Nothing to Disable",
+ on ? /*LANG*/"Enable All" : /*LANG*/"Disable All"
+ ).then(() => showAdvancedMenu());
+ } else {
+ E.showPrompt(/*LANG*/"Are you sure?", { title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All" }).then((confirm) => {
+ if (confirm) {
+ alarms.forEach((alarm, i) => {
+ alarm.on = on;
+ if (on) {
+ if (alarm.timer) {
+ prepareTimerForSave(alarm, i, require("time_utils").decodeTime(alarm.timer))
+ } else {
+ prepareAlarmForSave(alarm, i, require("time_utils").decodeTime(alarm.t))
+ }
+ }
+ });
+ saveAndReload();
+ showMainMenu();
+ } else {
+ showAdvancedMenu();
+ }
+ });
+ }
}
function deleteAll() {
- E.showPrompt(/*LANG*/"Are you sure?", {
- title: /*LANG*/"Delete All"
- }).then((confirm) => {
- if (confirm) {
- alarms = [];
- saveAndReload();
- }
-
- showMainMenu();
- });
+ if (alarms.length == 0) {
+ E.showAlert(/*LANG*/"Nothing to delete", /*LANG*/"Delete All").then(() => showAdvancedMenu());
+ } else {
+ E.showPrompt(/*LANG*/"Are you sure?", {
+ title: /*LANG*/"Delete All"
+ }).then((confirm) => {
+ if (confirm) {
+ alarms = [];
+ saveAndReload();
+ showMainMenu();
+ } else {
+ showAdvancedMenu();
+ }
+ });
+ }
}
showMainMenu();
diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json
index 2084c2a30..31dd58ece 100644
--- a/apps/alarm/metadata.json
+++ b/apps/alarm/metadata.json
@@ -2,16 +2,29 @@
"id": "alarm",
"name": "Alarms & Timers",
"shortName": "Alarms",
- "version": "0.24",
+ "version": "0.33",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm,widget",
- "supports": ["BANGLEJS","BANGLEJS2"],
+ "supports": [ "BANGLEJS", "BANGLEJS2" ],
"readme": "README.md",
- "dependencies": {"scheduler":"type"},
+ "dependencies": { "scheduler":"type" },
"storage": [
- {"name":"alarm.app.js","url":"app.js"},
- {"name":"alarm.img","url":"app-icon.js","evaluate":true},
- {"name":"alarm.wid.js","url":"widget.js"}
+ { "name": "alarm.app.js", "url": "app.js" },
+ { "name": "alarm.img", "url": "app-icon.js", "evaluate": true },
+ { "name": "alarm.wid.js", "url": "widget.js" }
+ ],
+ "screenshots": [
+ { "url": "screenshot-1.png" },
+ { "url": "screenshot-2.png" },
+ { "url": "screenshot-3.png" },
+ { "url": "screenshot-4.png" },
+ { "url": "screenshot-5.png" },
+ { "url": "screenshot-6.png" },
+ { "url": "screenshot-7.png" },
+ { "url": "screenshot-8.png" },
+ { "url": "screenshot-9.png" },
+ { "url": "screenshot-10.png" },
+ { "url": "screenshot-11.png" }
]
}
diff --git a/apps/alarm/screenshot-1.png b/apps/alarm/screenshot-1.png
new file mode 100644
index 000000000..d2bd3a409
Binary files /dev/null and b/apps/alarm/screenshot-1.png differ
diff --git a/apps/alarm/screenshot-10.png b/apps/alarm/screenshot-10.png
new file mode 100644
index 000000000..1e6e516c3
Binary files /dev/null and b/apps/alarm/screenshot-10.png differ
diff --git a/apps/alarm/screenshot-11.png b/apps/alarm/screenshot-11.png
new file mode 100644
index 000000000..197c84194
Binary files /dev/null and b/apps/alarm/screenshot-11.png differ
diff --git a/apps/alarm/screenshot-2.png b/apps/alarm/screenshot-2.png
new file mode 100644
index 000000000..1cbc255a9
Binary files /dev/null and b/apps/alarm/screenshot-2.png differ
diff --git a/apps/alarm/screenshot-3.png b/apps/alarm/screenshot-3.png
new file mode 100644
index 000000000..a165d3594
Binary files /dev/null and b/apps/alarm/screenshot-3.png differ
diff --git a/apps/alarm/screenshot-4.png b/apps/alarm/screenshot-4.png
new file mode 100644
index 000000000..7fd7e99b6
Binary files /dev/null and b/apps/alarm/screenshot-4.png differ
diff --git a/apps/alarm/screenshot-5.png b/apps/alarm/screenshot-5.png
new file mode 100644
index 000000000..4174c5670
Binary files /dev/null and b/apps/alarm/screenshot-5.png differ
diff --git a/apps/alarm/screenshot-6.png b/apps/alarm/screenshot-6.png
new file mode 100644
index 000000000..dc579ca5c
Binary files /dev/null and b/apps/alarm/screenshot-6.png differ
diff --git a/apps/alarm/screenshot-7.png b/apps/alarm/screenshot-7.png
new file mode 100644
index 000000000..49da44710
Binary files /dev/null and b/apps/alarm/screenshot-7.png differ
diff --git a/apps/alarm/screenshot-8.png b/apps/alarm/screenshot-8.png
new file mode 100644
index 000000000..86d69cd93
Binary files /dev/null and b/apps/alarm/screenshot-8.png differ
diff --git a/apps/alarm/screenshot-9.png b/apps/alarm/screenshot-9.png
new file mode 100644
index 000000000..2d8c7fc83
Binary files /dev/null and b/apps/alarm/screenshot-9.png differ
diff --git a/apps/alarm/widget.js b/apps/alarm/widget.js
index 052ac9ebd..964176fc7 100644
--- a/apps/alarm/widget.js
+++ b/apps/alarm/widget.js
@@ -2,7 +2,7 @@ WIDGETS["alarm"]={area:"tl",width:0,draw:function() {
if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
},reload:function() {
// don't include library here as we're trying to use as little RAM as possible
- WIDGETS["alarm"].width = (require('Storage').readJSON('sched.json',1)||[]).some(alarm=>alarm.on&&(alarm.hidden!==false)) ? 24 : 0;
+ WIDGETS["alarm"].width = (require('Storage').readJSON('sched.json',1)||[]).some(alarm=>alarm.on&&(alarm.hidden!==true)) ? 24 : 0;
}
};
WIDGETS["alarm"].reload();
diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog
index 96b50c3a0..0cc7aedd4 100644
--- a/apps/android/ChangeLog
+++ b/apps/android/ChangeLog
@@ -7,3 +7,9 @@
0.06: Option to keep messages after a disconnect (default false) (fix #1186)
0.07: Include charging state in battery updates to phone
0.08: Handling of alarms
+0.09: Alarm vibration, repeat, and auto-snooze now handled by sched
+0.10: Fix SMS bug
+0.12: Use default Bangle formatter for booleans
+0.13: Added Bangle.http function (see Readme file for more info)
+0.14: Fix timeout of http function not being cleaned up
+0.15: Allow method/body/headers to be specified for `http` (needs Gadgetbridge 0.68.0b or later)
diff --git a/apps/android/README.md b/apps/android/README.md
index 580eeec9a..f9ab73699 100644
--- a/apps/android/README.md
+++ b/apps/android/README.md
@@ -21,7 +21,6 @@ of Gadgetbridge - making your phone make noise so you can find it.
* `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js
keep any messages it has received, or should it delete them?
* `Messages` - launches the messages app, showing a list of messages
-* `Alarms` - opens a submenu where you can set default settings for alarms such as vibration pattern, repeat, and auto snooze
## How it works
@@ -33,6 +32,25 @@ Responses are sent back to Gadgetbridge simply as one line of JSON.
More info on message formats on http://www.espruino.com/Gadgetbridge
+## Functions provided
+
+The boot code also provides some useful functions:
+
+* `Bangle.messageResponse = function(msg,response)` - send a yes/no response to a message. `msg` is a message object, and `response` is a boolean.
+* `Bangle.musicControl = function(cmd)` - control music, cmd = `play/pause/next/previous/volumeup/volumedown`
+* `Bangle.http = function(url,options)` - make an HTTPS request to a URL and return a promise with the data. Requires the [internet enabled `Bangle.js Gadgetbridge` app](http://www.espruino.com/Gadgetbridge#http-requests). `options` can contain:
+ * `id` - a custom (string) ID
+ * `timeout` - a timeout for the request in milliseconds (default 30000ms)
+ * `xpath` an xPath query to run on the request (but right now the URL requested must be XML - HTML is rarely XML compliant)
+
+eg:
+
+```
+Bangle.http("https://pur3.co.uk/hello.txt").then(data=>{
+ console.log("Got ",data);
+});
+```
+
## Testing
Bangle.js can only hold one connection open at a time, so it's hard to see
diff --git a/apps/android/boot.js b/apps/android/boot.js
index 9e24c9893..bc8e3032d 100644
--- a/apps/android/boot.js
+++ b/apps/android/boot.js
@@ -3,6 +3,7 @@
Bluetooth.println("");
Bluetooth.println(JSON.stringify(message));
}
+ var lastMsg;
var settings = require("Storage").readJSON("android.settings.json",1)||{};
//default alarm settings
@@ -18,7 +19,17 @@
/* TODO: Call handling, fitness */
var HANDLERS = {
// {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
- "notify" : function() { Object.assign(event,{t:"add",positive:true, negative:true});require("messages").pushMessage(event); },
+ "notify" : function() {
+ Object.assign(event,{t:"add",positive:true, negative:true});
+ // Detect a weird GadgetBridge bug and fix it
+ // For some reason SMS messages send two GB notifications, with different sets of info
+ if (lastMsg && event.body == lastMsg.body && lastMsg.src == undefined && event.src == "Messages") {
+ // Mutate the other message
+ event.id = lastMsg.id;
+ }
+ lastMsg = event;
+ require("messages").pushMessage(event);
+ },
// {t:"notify~",id:int, title:string} // modified
"notify~" : function() { event.t="modify";require("messages").pushMessage(event); },
// {t:"notify-",id:int} // remove
@@ -67,26 +78,93 @@
var dow = event.d[j].rep;
if (!dow) dow = 127; //if no DOW selected, set alarm to all DOW
var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
- var a = {
- id : "gb"+j,
- appid : "gbalarms",
- on : true,
- t : event.d[j].h * 3600000 + event.d[j].m * 60000,
- dow : ((dow&63)<<1) | (dow>>6), // Gadgetbridge sends DOW in a different format
- last : last,
- rp : settings.rp,
- as : settings.as,
- vibrate : settings.vibrate
- };
+ var a = require("sched").newDefaultAlarm();
+ a.id = "gb"+j;
+ a.appid = "gbalarms";
+ a.on = true;
+ a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
+ a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
+ a.last = last;
alarms.push(a);
}
sched.setAlarms(alarms);
sched.reload();
},
+ //TODO perhaps move those in a library (like messages), used also for viewing events?
+ //simple package with events all together
+ "calendarevents" : function() {
+ require("Storage").writeJSON("android.calendar.json", event.events);
+ },
+ //add and remove events based on activity on phone (pebble-like)
+ "calendar" : function() {
+ var cal = require("Storage").readJSON("android.calendar.json",true);
+ if (!cal || !Array.isArray(cal)) cal = [];
+ var i = cal.findIndex(e=>e.id==event.id);
+ if(i<0)
+ cal.push(event);
+ else
+ cal[i] = event;
+ require("Storage").writeJSON("android.calendar.json", cal);
+ },
+ "calendar-" : function() {
+ var cal = require("Storage").readJSON("android.calendar.json",true);
+ //if any of those happen we are out of sync!
+ if (!cal || !Array.isArray(cal)) return;
+ cal = cal.filter(e=>e.id!=event.id);
+ require("Storage").writeJSON("android.calendar.json", cal);
+ },
+ //triggered by GB, send all ids
+ "force_calendar_sync_start" : function() {
+ var cal = require("Storage").readJSON("android.calendar.json",true);
+ if (!cal || !Array.isArray(cal)) cal = [];
+ gbSend({t:"force_calendar_sync", ids: cal.map(e=>e.id)});
+ },
+ "http":function() {
+ //get the promise and call the promise resolve
+ if (Bangle.httpRequest === undefined) return;
+ var request=Bangle.httpRequest[event.id];
+ if (request === undefined) return; //already timedout or wrong id
+ delete Bangle.httpRequest[event.id];
+ clearTimeout(request.t); //t = timeout variable
+ if(event.err!==undefined) //if is error
+ request.j(event.err); //r = reJect function
+ else
+ request.r(event); //r = resolve function
+ }
};
var h = HANDLERS[event.t];
if (h) h(); else console.log("GB Unknown",event);
};
+ // HTTP request handling - see the readme
+ // options = {id,timeout,xpath}
+ Bangle.http = (url,options)=>{
+ options = options||{};
+ if (Bangle.httpRequest === undefined)
+ Bangle.httpRequest={};
+ if (options.id === undefined) {
+ // try and create a unique ID
+ do {
+ options.id = Math.random().toString().substr(2);
+ } while( Bangle.httpRequest[options.id]!==undefined);
+ }
+ //send the request
+ var req = {t: "http", url:url, id:options.id};
+ if (options.xpath) req.xpath = options.xpath;
+ if (options.method) req.method = options.method;
+ if (options.body) req.body = options.body;
+ if (options.headers) req.headers = options.headers;
+ gbSend(req);
+ //create the promise
+ var promise = new Promise(function(resolve,reject) {
+ //save the resolve function in the dictionary and create a timeout (30 seconds default)
+ Bangle.httpRequest[options.id]={r:resolve,j:reject,t:setTimeout(()=>{
+ //if after "timeoutMillisec" it still hasn't answered -> reject
+ delete Bangle.httpRequest[options.id];
+ reject("Timeout");
+ },options.timeout||30000)};
+ });
+ return promise;
+ }
// Battery monitor
function sendBattery() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
diff --git a/apps/android/metadata.json b/apps/android/metadata.json
index 203cd18b1..5d1b2f561 100644
--- a/apps/android/metadata.json
+++ b/apps/android/metadata.json
@@ -2,7 +2,7 @@
"id": "android",
"name": "Android Integration",
"shortName": "Android",
- "version": "0.08",
+ "version": "0.15",
"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",
@@ -15,6 +15,6 @@
{"name":"android.img","url":"app-icon.js","evaluate":true},
{"name":"android.boot.js","url":"boot.js"}
],
- "data": [{"name":"android.settings.json"}],
+ "data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}],
"sortorder": -8
}
diff --git a/apps/android/settings.js b/apps/android/settings.js
index 9f72947ab..c7c34a76f 100644
--- a/apps/android/settings.js
+++ b/apps/android/settings.js
@@ -18,34 +18,12 @@
}),
/*LANG*/"Keep Msgs" : {
value : !!settings.keep,
- format : v=>v?/*LANG*/"Yes":/*LANG*/"No",
onchange: v => {
settings.keep = v;
updateSettings();
}
},
/*LANG*/"Messages" : ()=>load("messages.app.js"),
- /*LANG*/"Alarms" : () => E.showMenu({
- "" : { "title" : /*LANG*/"Alarms" },
- "< Back" : ()=>E.showMenu(mainmenu),
- /*LANG*/"Vibrate": require("buzz_menu").pattern(settings.vibrate, v => {settings.vibrate = v; updateSettings();}),
- /*LANG*/"Repeat": {
- value: settings.rp,
- format : v=>v?/*LANG*/"Yes":/*LANG*/"No",
- onchange: v => {
- settings.rp = v;
- updateSettings();
- }
- },
- /*LANG*/"Auto snooze": {
- value: settings.as,
- format : v=>v?/*LANG*/"Yes":/*LANG*/"No",
- onchange: v => {
- settings.as = v;
- updateSettings();
- }
- },
- })
};
E.showMenu(mainmenu);
})
diff --git a/apps/animclk/ChangeLog b/apps/animclk/ChangeLog
index 348448c34..76d15bdb1 100644
--- a/apps/animclk/ChangeLog
+++ b/apps/animclk/ChangeLog
@@ -1,3 +1,5 @@
0.01: New App!
0.02: Fix bug if image clock wasn't installed
0.03: Update to use setUI
+0.04: Tell clock widgets to hide. Move loadWidgets() so it only runs on
+startup and not on every draw.
diff --git a/apps/animclk/app.js b/apps/animclk/app.js
index 4bf63daf6..bdc399fbe 100644
--- a/apps/animclk/app.js
+++ b/apps/animclk/app.js
@@ -87,7 +87,6 @@ if (g.drawImages) {
draw();
var secondInterval = setInterval(draw,100);
// load widgets
- Bangle.loadWidgets();
Bangle.drawWidgets();
// Stop when LCD goes off
Bangle.on('lcdPower',on=>{
@@ -104,3 +103,5 @@ if (g.drawImages) {
}
// Show launcher when button pressed
Bangle.setUI("clock");
+
+Bangle.loadWidgets();
diff --git a/apps/animclk/metadata.json b/apps/animclk/metadata.json
index 31dfe453f..0b426a37d 100644
--- a/apps/animclk/metadata.json
+++ b/apps/animclk/metadata.json
@@ -2,7 +2,7 @@
"id": "animclk",
"name": "Animated Clock",
"shortName": "Anim Clock",
- "version": "0.03",
+ "version": "0.04",
"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",
diff --git a/apps/antonclk/ChangeLog b/apps/antonclk/ChangeLog
index 73a63f7c7..f7e95b5fa 100644
--- a/apps/antonclk/ChangeLog
+++ b/apps/antonclk/ChangeLog
@@ -9,4 +9,5 @@
when weekday name and calendar weeknumber are on then display is #
week is buffered until date or timezone changes
0.07: align default settings with app.js (otherwise the initial displayed settings will be confusing to users)
-0.08: fixed calendar weeknumber not shortened to two digits
\ No newline at end of file
+0.08: fixed calendar weeknumber not shortened to two digits
+0.09: Use default Bangle formatter for booleans
\ No newline at end of file
diff --git a/apps/antonclk/metadata.json b/apps/antonclk/metadata.json
index c58ee2a1b..16bdf3aa8 100644
--- a/apps/antonclk/metadata.json
+++ b/apps/antonclk/metadata.json
@@ -1,7 +1,7 @@
{
"id": "antonclk",
"name": "Anton Clock",
- "version": "0.08",
+ "version": "0.09",
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
"readme":"README.md",
"icon": "app.png",
diff --git a/apps/antonclk/settings.js b/apps/antonclk/settings.js
index 6882cbd0f..4448c00ed 100644
--- a/apps/antonclk/settings.js
+++ b/apps/antonclk/settings.js
@@ -2,7 +2,6 @@
(function(back) {
var FILE = "antonclk.json";
- // Load settings
var settings = Object.assign({
secondsOnUnlock: false,
}, require('Storage').readJSON(FILE, true) || {});
@@ -41,7 +40,6 @@
"Date": stringInSettings("dateOnMain", ["Long", "Short", "ISO8601"]),
"Show Weekday": {
value: (settings.weekDay !== undefined ? settings.weekDay : true),
- format: v => v ? "On" : "Off",
onchange: v => {
settings.weekDay = v;
writeSettings();
@@ -49,7 +47,6 @@
},
"Show CalWeek": {
value: (settings.calWeek !== undefined ? settings.calWeek : false),
- format: v => v ? "On" : "Off",
onchange: v => {
settings.calWeek = v;
writeSettings();
@@ -57,7 +54,6 @@
},
"Uppercase": {
value: (settings.upperCase !== undefined ? settings.upperCase : true),
- format: v => v ? "On" : "Off",
onchange: v => {
settings.upperCase = v;
writeSettings();
@@ -65,7 +61,6 @@
},
"Vector font": {
value: (settings.vectorFont !== undefined ? settings.vectorFont : false),
- format: v => v ? "On" : "Off",
onchange: v => {
settings.vectorFont = v;
writeSettings();
@@ -82,7 +77,6 @@
"Show": stringInSettings("secondsMode", ["Never", "Unlocked", "Always"]),
"With \":\"": {
value: (settings.secondsWithColon !== undefined ? settings.secondsWithColon : true),
- format: v => v ? "On" : "Off",
onchange: v => {
settings.secondsWithColon = v;
writeSettings();
@@ -90,7 +84,6 @@
},
"Color": {
value: (settings.secondsColoured !== undefined ? settings.secondsColoured : true),
- format: v => v ? "On" : "Off",
onchange: v => {
settings.secondsColoured = v;
writeSettings();
@@ -99,9 +92,6 @@
"Date": stringInSettings("dateOnSecs", ["Year", "Weekday", "No"])
};
- // Actually display the menu
E.showMenu(mainmenu);
});
-
-// end of file
diff --git a/apps/astral/ChangeLog b/apps/astral/ChangeLog
index a51c96760..c93c2b6c2 100644
--- a/apps/astral/ChangeLog
+++ b/apps/astral/ChangeLog
@@ -1,3 +1,4 @@
0.01: Create astral clock app
0.02: Fixed Whirlpool galaxy RA/DA, larger compass display, fixed moonphase overlapping battery widget
0.03: Update to use Bangle.setUI instead of setWatch
+0.04: Tell clock widgets to hide.
diff --git a/apps/astral/app.js b/apps/astral/app.js
index c445463f2..c4339bc09 100644
--- a/apps/astral/app.js
+++ b/apps/astral/app.js
@@ -767,6 +767,24 @@ function draw() {
g.clear();
current_moonphase = getMoonPhase();
+Bangle.setUI("clockupdown", btn => {
+ if (btn==0) {
+ if (!processing) {
+ if (!modeswitch) {
+ modeswitch = true;
+ if (mode == "planetary") mode = "extras";
+ else mode = "planetary";
+ }
+ else
+ modeswitch = false;
+ }
+ } else {
+ if (!processing)
+ ready_to_compute = true;
+ }
+});
+
+
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
@@ -799,23 +817,6 @@ Bangle.setGPSPower(1);
// Show launcher when button pressed
Bangle.setClockMode();
-Bangle.setUI("clockupdown", btn => {
- if (btn==0) {
- if (!processing) {
- if (!modeswitch) {
- modeswitch = true;
- if (mode == "planetary") mode = "extras";
- else mode = "planetary";
- }
- else
- modeswitch = false;
- }
- } else {
- if (!processing)
- ready_to_compute = true;
- }
-});
-
setWatch(function () {
if (!astral_settings.astral_default) {
colours_switched = true;
diff --git a/apps/astral/metadata.json b/apps/astral/metadata.json
index 3317092db..d7120878b 100644
--- a/apps/astral/metadata.json
+++ b/apps/astral/metadata.json
@@ -1,7 +1,7 @@
{
"id": "astral",
"name": "Astral Clock",
- "version": "0.03",
+ "version": "0.04",
"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",
diff --git a/apps/barclock/ChangeLog b/apps/barclock/ChangeLog
index 316660fc6..a00ae9325 100644
--- a/apps/barclock/ChangeLog
+++ b/apps/barclock/ChangeLog
@@ -7,3 +7,9 @@
0.07: Update to use Bangle.setUI instead of setWatch
0.08: Use theme colors, Layout library
0.09: Fix time/date disappearing after fullscreen notification
+0.10: Use ClockFace library
+0.11: Use ClockFace.is12Hour
+0.12: Add settings to hide date,widgets
+0.13: Add font setting
+0.14: Use ClockFace_menu.addItems
+0.15: Add Power saving option
\ No newline at end of file
diff --git a/apps/barclock/README.md b/apps/barclock/README.md
index 4b92313c5..28572e37c 100644
--- a/apps/barclock/README.md
+++ b/apps/barclock/README.md
@@ -4,3 +4,8 @@ A simple digital clock showing seconds as a horizontal bar.
| 24hr style | 12hr style |
| --- | --- |
|  |  |
+
+## Settings
+* `Show date`: display date at the bottom of screen
+* `Font`: choose between bitmap or vector fonts
+* `Power saving`: (Bangle.js 2 only) don't draw the seconds bar while the watch is locked
\ No newline at end of file
diff --git a/apps/barclock/clock-bar.js b/apps/barclock/clock-bar.js
index 5d46a1cb4..5a7dfc8c0 100644
--- a/apps/barclock/clock-bar.js
+++ b/apps/barclock/clock-bar.js
@@ -3,7 +3,6 @@
* A simple digital clock showing seconds as a bar
**/
// Check settings for what type our clock should be
-const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
let locale = require("locale");
{ // add some more info to locale
let date = new Date();
@@ -11,51 +10,25 @@ let locale = require("locale");
date.setMonth(1, 3); // februari: months are zero-indexed
const localized = locale.date(date, true);
locale.dayFirst = /3.*2/.test(localized);
-
- locale.hasMeridian = false;
- if (typeof locale.meridian==="function") { // function does not exist if languages app is not installed
- locale.hasMeridian = (locale.meridian(date)!=="");
- }
+ locale.hasMeridian = (locale.meridian(date)!=="");
}
-Bangle.loadWidgets();
+
+let barW = 0, prevX = 0;
function renderBar(l) {
- if (!this.fraction) {
- // zero-size fillRect stills draws one line of pixels, we don't want that
- return;
- }
- const width = this.fraction*l.w;
- g.fillRect(l.x, l.y, l.x+width-1, l.y+l.height-1);
+ "ram";
+ if (l) prevX = 0; // called from Layout: drawing area was cleared
+ else l = clock.layout.bar;
+ let x2 = l.x+barW;
+ if (clock.powerSave && Bangle.isLocked()) x2 = 0; // hide bar
+ if (x2===prevX) return; // nothing to do
+ if (x2===0) x2--; // don't leave 1px line
+ if (x2 - ) / (5chars * 6px)
- thickness = Math.floor((g.getWidth()-24)/(5*6));
-} else {
- layout.ampm.label = "";
- thickness = Math.floor(g.getWidth()/(5*6));
-}
-layout.bar.height = thickness+1;
-layout.time.font = "6x8:"+thickness;
-layout.update();
-
function timeText(date) {
- if (!is12Hour) {
+ if (!clock.is12Hour) {
return locale.time(date, true);
}
const date12 = new Date(date.getTime());
@@ -68,7 +41,7 @@ function timeText(date) {
return locale.time(date12, true);
}
function ampmText(date) {
- return (is12Hour && locale.hasMeridian)? locale.meridian(date) : "";
+ return (clock.is12Hour && locale.hasMeridian) ? locale.meridian(date) : "";
}
function dateText(date) {
const dayName = locale.dow(date, true),
@@ -78,31 +51,74 @@ function dateText(date) {
return `${dayName} ${dayMonth}`;
}
-draw = function draw(force) {
- if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled
- const date = new Date();
- layout.time.label = timeText(date);
- layout.ampm.label = ampmText(date);
- layout.date.label = dateText(date);
- const SECONDS_PER_MINUTE = 60;
- layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE;
- if (force) {
- Bangle.drawWidgets();
- layout.forgetLazyState();
- }
- layout.render();
- // schedule update at start of next second
- const millis = date.getMilliseconds();
- setTimeout(draw, 1000-millis);
-};
+const ClockFace = require("ClockFace"),
+ clock = new ClockFace({
+ precision: 1,
+ settingsFile: "barclock.settings.json",
+ init: function() {
+ const Layout = require("Layout");
+ this.layout = new Layout({
+ type: "v", c: [
+ {
+ type: "h", c: [
+ {id: "time", label: "88:88", type: "txt", font: "6x8:5", col: g.theme.fg, bgCol: g.theme.bg}, // updated below
+ {id: "ampm", label: " ", type: "txt", font: "6x8:2", col: g.theme.fg, bgCol: g.theme.bg},
+ ],
+ },
+ {id: "bar", type: "custom", fillx: 1, height: 6, col: g.theme.fg2, render: renderBar},
+ this.showDate ? {height: 40} : {},
+ this.showDate ? {id: "date", type: "txt", font: "10%", valign: 1} : {},
+ ],
+ }, {lazy: true});
+ // adjustments based on screen size and whether we display am/pm
+ let thickness; // bar thickness, same as time font "pixel block" size
+ if (this.is12Hour && locale.hasMeridian) {
+ // Maximum font size = ( - ) / (5chars * 6px)
+ thickness = Math.floor((Bangle.appRect.w-24)/(5*6));
+ } else {
+ this.layout.ampm.label = "";
+ thickness = Math.floor(Bangle.appRect.w/(5*6));
+ }
+ let bar = this.layout.bar;
+ bar.height = thickness+1;
+ if (this.font===1) { // vector
+ const B2 = process.env.HWVERSION>1;
+ if (this.is12Hour && locale.hasMeridian) {
+ this.layout.time.font = "Vector:"+(B2 ? 50 : 60);
+ this.layout.ampm.font = "Vector:"+(B2 ? 20 : 40);
+ } else {
+ this.layout.time.font = "Vector:"+(B2 ? 60 : 80);
+ }
+ } else {
+ this.layout.time.font = "6x8:"+thickness;
+ }
+ this.layout.update();
+ bar.y2 = bar.y+bar.height-1;
+ },
+ update: function(date, c) {
+ "ram";
+ if (c.m) this.layout.time.label = timeText(date);
+ if (c.h) this.layout.ampm.label = ampmText(date);
+ if (c.d && this.showDate) this.layout.date.label = dateText(date);
+ if (c.m) this.layout.render();
+ if (c.s) {
+ barW = Math.round(date.getSeconds()/60*this.layout.bar.w);
+ renderBar();
+ }
+ },
+ resume: function() {
+ prevX = 0; // force redraw of bar
+ this.layout.forgetLazyState();
+ },
+ });
-// Show launcher when button pressed
-Bangle.setUI("clock");
-Bangle.on("lcdPower", function(on) {
- if (on) {
- draw(true);
- }
-});
-g.reset().clear();
-Bangle.drawWidgets();
-draw();
+// power saving: only update once a minute while locked, hide bar
+if (clock.powerSave) {
+ Bangle.on("lock", lock => {
+ clock.precision = lock ? 60 : 1;
+ clock.tick();
+ renderBar(); // hide/redraw bar right away
+ });
+}
+
+clock.start();
diff --git a/apps/barclock/metadata.json b/apps/barclock/metadata.json
index 2b7be355f..5b783dbda 100644
--- a/apps/barclock/metadata.json
+++ b/apps/barclock/metadata.json
@@ -1,7 +1,7 @@
{
"id": "barclock",
"name": "Bar Clock",
- "version": "0.09",
+ "version": "0.15",
"description": "A simple digital clock showing seconds as a bar",
"icon": "clock-bar.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],
@@ -12,6 +12,10 @@
"allow_emulator": true,
"storage": [
{"name":"barclock.app.js","url":"clock-bar.js"},
+ {"name":"barclock.settings.js","url":"settings.js"},
{"name":"barclock.img","url":"clock-bar-icon.js","evaluate":true}
+ ],
+ "data": [
+ {"name":"barclock.settings.json"}
]
}
diff --git a/apps/barclock/settings.js b/apps/barclock/settings.js
new file mode 100644
index 000000000..7b88b7021
--- /dev/null
+++ b/apps/barclock/settings.js
@@ -0,0 +1,30 @@
+(function(back) {
+ let s = require("Storage").readJSON("barclock.settings.json", true) || {};
+
+ function save(key, value) {
+ s[key] = value;
+ require("Storage").writeJSON("barclock.settings.json", s);
+ }
+
+ const fonts = [/*LANG*/"Bitmap",/*LANG*/"Vector"];
+ let menu = {
+ "": {"title": /*LANG*/"Bar Clock"},
+ /*LANG*/"< Back": back,
+ /*LANG*/"Font": {
+ value: s.font|0,
+ min: 0, max: 1, wrap: true,
+ format: v => fonts[v],
+ onchange: v => save("font", v),
+ },
+ };
+ let items = {
+ showDate: s.showDate,
+ loadWidgets: s.loadWidgets,
+ };
+ // Power saving for Bangle.js 1 doesn't make sense (no updates while screen is off anyway)
+ if (process.env.HWVERSION>1) {
+ items.powerSave = s.powerSave;
+ }
+ require("ClockFace_menu").addItems(menu, save, items);
+ E.showMenu(menu);
+});
diff --git a/apps/barcode/ChangeLog b/apps/barcode/ChangeLog
new file mode 100644
index 000000000..7ab5d8587
--- /dev/null
+++ b/apps/barcode/ChangeLog
@@ -0,0 +1,10 @@
+0.01: Please forgive me
+0.02: Now tells time!
+0.03: Interaction
+0.04: Shows day of week
+0.05: Shows day of month
+0.06: Updates every 5 minutes when locked, or when unlock occurs. Also shows nr of steps.
+0.07: Step count resets at midnight
+0.08: Step count stored in memory to survive reloads. Now shows step count daily and since last reboot.
+0.09: NOW it really should reset daily (instead of every other day...)
+0.10: Tell clock widgets to hide.
diff --git a/apps/barcode/README.md b/apps/barcode/README.md
new file mode 100644
index 000000000..17b365d45
--- /dev/null
+++ b/apps/barcode/README.md
@@ -0,0 +1,23 @@
+# Barcode clockwatchface
+
+A scannable EAN-8 compatible clockwatchface for your Bangle 2
+
+The format of the bars are
+
+`||HHmm||MMwc||`
+
+* Left section: HHmm
+ * H: Hours
+ * m: Minutes
+* Right section: MM9c
+ * M: Day of month
+ * w: Day of week
+ * c: Calculated EAN-8 digit checksum
+
+Apart from that
+
+* The upper left section displays total number of steps per day
+* The upper right section displays total number of steps from last boot ("stepuptime")
+* The face updates every 5 minutes or on demant by pressing the hardware button
+
+This clockwathface is aware of theme choice, so it will adapt to Light/Dark themes.
diff --git a/apps/barcode/barcode.app.js b/apps/barcode/barcode.app.js
new file mode 100644
index 000000000..0d9df78d5
--- /dev/null
+++ b/apps/barcode/barcode.app.js
@@ -0,0 +1,428 @@
+/* Sizes */
+let checkBarWidth = 10;
+let checkBarHeight = 140;
+
+let digitBarWidth = 14;
+let digitBarHeight = 100;
+
+let textBarWidth = 56;
+let textBarHeight = 20;
+
+let textWidth = 14;
+let textHeight = 20;
+
+/* Offsets */
+var startOffsetX = 17;
+var startOffsetY = 30;
+
+let startBarOffsetX = startOffsetX;
+let startBarOffsetY = startOffsetY;
+
+let upperTextBarLeftOffsetX = startBarOffsetX + checkBarWidth;
+let upperTextBarLeftOffsetY = startOffsetY;
+
+let midBarOffsetX = upperTextBarLeftOffsetX + textBarWidth;
+let midBarOffsetY = startOffsetY;
+
+let upperTextBarRightOffsetX = midBarOffsetX + checkBarWidth;
+let upperTextBarRightOffsetY = startOffsetY;
+
+let endBarOffsetX = upperTextBarRightOffsetX + textBarWidth;
+let endBarOffsetY = startOffsetY;
+
+let leftBarsStartX = startBarOffsetX + checkBarWidth;
+let leftBarsStartY = upperTextBarLeftOffsetY + textBarHeight;
+
+let rightBarsStartX = midBarOffsetX + checkBarWidth;
+let rightBarsStartY = upperTextBarRightOffsetY + textBarHeight;
+
+/* Utilities */
+let stepCount = require("Storage").readJSON("stepCount",1);
+if(stepCount === undefined) stepCount = 0;
+let intCaster = num => Number(num);
+
+var drawTimeout;
+
+function renderWatch(l) {
+ g.setFont("4x6",2);
+
+ // work out how to display the current time
+
+ var d = new Date();
+ var h = d.getHours(), m = d.getMinutes();
+ var time = h + ":" + ("0"+m).substr(-2);
+ //var month = ("0" + (d.getMonth()+1)).slice(-2);
+ var dayOfMonth = ('0' + d.getDate()).slice(-2);
+ var dayOfWeek = d.getDay() || 7;
+ var concatTime = ("0"+h).substr(-2) + ("0"+m).substr(-2) + dayOfMonth + dayOfWeek;
+
+ const chars = String(concatTime).split("").map((concatTime) => {
+ return Number(concatTime);
+ });
+ const checkSum = calculateChecksum(chars);
+ concatTime += checkSum;
+
+ drawCheckBar(startBarOffsetX, startBarOffsetY);
+
+ drawLDigit(chars[0], 0, leftBarsStartY);
+ drawLDigit(chars[1], 1, leftBarsStartY);
+ drawLDigit(chars[2], 2, leftBarsStartY);
+ drawLDigit(chars[3], 3, leftBarsStartY);
+
+ g.drawString(getStepCount(), startOffsetX + checkBarWidth + 3, startOffsetY + 4);
+ g.drawString(concatTime.substring(0,4), startOffsetX + checkBarWidth + 3, startOffsetY + textBarHeight + digitBarHeight + 6);
+
+ drawCheckBar(midBarOffsetX, midBarOffsetY);
+
+ drawRDigit(chars[4], 0, rightBarsStartY);
+ drawRDigit(chars[5], 1, rightBarsStartY);
+ drawRDigit(chars[6], 2, rightBarsStartY);
+ drawRDigit(checkSum, 3, rightBarsStartY);
+
+ g.drawString(Bangle.getStepCount(), midBarOffsetX + checkBarWidth + 3, startOffsetY + 4);
+ g.drawString(concatTime.substring(4), midBarOffsetX + checkBarWidth + 3, startOffsetY + textBarHeight + digitBarHeight + 6);
+
+ drawCheckBar(endBarOffsetX, endBarOffsetY);
+
+ // schedule a draw for the next minute
+ if (drawTimeout) {
+ clearTimeout(drawTimeout);
+ }
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ layout.render(layout.watch);
+ }, (1000 * 60 * 5) - (Date.now() % (1000 * 60 * 5)));
+}
+
+function drawLDigit(digit, index, offsetY) {
+ switch(digit) {
+ case 0:
+ drawLZeroWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 1:
+ drawLOneWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 2:
+ drawLTwoWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 3:
+ drawLThreeWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 4:
+ drawLFourWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 5:
+ drawLFiveWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 6:
+ drawLSixWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 7:
+ drawLSevenWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 8:
+ drawLEightWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 9:
+ drawLNineWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ }
+}
+
+function drawRDigit(digit, index, offsetY) {
+ switch(digit) {
+ case 0:
+ drawRZeroWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 1:
+ drawROneWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 2:
+ drawRTwoWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 3:
+ drawRThreeWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 4:
+ drawRFourWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 5:
+ drawRFiveWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 6:
+ drawRSixWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 7:
+ drawRSevenWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 8:
+ drawREightWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ case 9:
+ drawRNineWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY);
+ break;
+ }
+}
+
+/*
+LEAN
+
+01234567890123
+ xxxx xx
+ xx xxxx
+ xxxxxxxx xx
+ xx xxxx
+ xxxx xx
+ xx xxxxxxxx
+ xxxxxx xxxx
+ xxxx xxxxxx
+ xx xxxx
+ xxxx xx
+*/
+function drawLOneWithOffset(offset, offsetY) {
+ let barOneX = 4;
+ let barTwoX = 12;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+3+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight);
+ //g.drawString("1",offset+3,offsetY+digitHeight+5);
+}
+
+function drawLTwoWithOffset(offset, offsetY) {
+ let barOneX = 4;
+ let barTwoX = 10;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+3+offset,offsetY+digitBarHeight);
+ //g.drawString("2",offset+3,offsetY+digitHeight+5);
+}
+
+function drawLThreeWithOffset(offset, offsetY) {
+ let barOneX = 2;
+ let barTwoX = 12;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+7+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight);
+ //g.drawString("3",offset+3,offsetY+digitHeight+5);
+}
+
+function drawLFourWithOffset(offset, offsetY) {
+ let barOneX = 2;
+ let barTwoX = 10;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+3+offset,offsetY+digitBarHeight);
+ //g.drawString("4",offset+3,offsetY+digitHeight+5);
+}
+
+function drawLFiveWithOffset(offset, offsetY) {
+ let barOneX = 2;
+ let barTwoX = 12;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+3+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight);
+ //g.drawString("5",offset+3,offsetY+digitHeight+5);
+}
+
+function drawLSixWithOffset(offset, offsetY) {
+ let barOneX = 2;
+ let barTwoX = 6;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+7+offset,offsetY+digitBarHeight);
+ //g.drawString("6",offset+3,offsetY+digitHeight+5);
+}
+
+function drawLSevenWithOffset(offset, offsetY) {
+ let barOneX = 2;
+ let barTwoX = 10;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+5+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+3+offset,offsetY+digitBarHeight);
+ //g.drawString("7",offset+3,offsetY+digitHeight+5);
+}
+
+function drawLEightWithOffset(offset, offsetY) {
+ let barOneX = 2;
+ let barTwoX = 8;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+3+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+5+offset,offsetY+digitBarHeight);
+ //g.drawString("8",offset+3,offsetY+digitHeight+5);
+}
+
+function drawLNineWithOffset(offset, offsetY) {
+ let barOneX = 6;
+ let barTwoX = 10;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+3+offset,offsetY+digitBarHeight);
+ //g.drawString("9",offset+3,offsetY+digitHeight+5);
+}
+
+function drawLZeroWithOffset(offset, offsetY) {
+ let barOneX = 6;
+ let barTwoX = 12;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+3+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight);
+ //g.drawString("0",offset+3,offsetY+digitHeight+5);
+}
+
+
+
+/*
+REAN
+
+01234567890123
+xxxx xxxx
+xxxx xxxx
+xx xx
+xx xxxxxx
+xx xxxxxx
+xx xx
+xx xx
+xx xx
+xxxxxx xx
+xxxxxx xx
+
+*/
+function drawROneWithOffset(offset, offsetY) {
+ let barOneX = 0;
+ let barTwoX = 8;
+ g.fillRect(offset+barOneX,offsetY+0,offset+barOneX+3,offsetY+digitBarHeight);
+ g.fillRect(offset+barTwoX,offsetY+0,offset+barTwoX+3,offsetY+digitBarHeight);
+ //g.drawString("1",offset+2,offsetY+textHeight+5);
+}
+
+function drawRTwoWithOffset(offset, offsetY) {
+ let barOneX = 0;
+ let barTwoX = 6;
+ g.fillRect(offset+barOneX,offsetY+0,offset+barOneX+3,offsetY+digitBarHeight);
+ g.fillRect(offset+barTwoX,offsetY+0,offset+barTwoX+3,offsetY+digitBarHeight);
+ //g.drawString("2",offset+2,offsetY+textHeight+5);
+}
+
+function drawRThreeWithOffset(offset, offsetY) {
+ let barOneX = 0;
+ let barTwoX = 10;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight);
+ //g.drawString("3",offset+2,offsetY+textHeight+5);
+}
+
+function drawRFourWithOffset(offset, offsetY) {
+ let barOneX = 0;
+ let barTwoX = 4;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+5+offset,offsetY+digitBarHeight);
+ //g.drawString("4",offset+2,offsetY+textHeight+5);
+}
+
+function drawRFiveWithOffset(offset, offsetY) {
+ let barOneX = 0;
+ let barTwoX = 6;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+5+offset,offsetY+digitBarHeight);
+ //g.drawString("5",offset+2,offsetY+textHeight+5);
+}
+
+function drawRSixWithOffset(offset, offsetY) {
+ let barOneX = 0;
+ let barTwoX = 4;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight);
+ //g.drawString("6",offset+2,offsetY+textHeight+5);
+}
+
+function drawRSevenWithOffset(offset, offsetY) {
+ let barOneX = 0;
+ let barTwoX = 8;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight);
+ //g.drawString("7",offset+2,offsetY+textHeight+5);
+}
+
+function drawREightWithOffset(offset, offsetY) {
+ let barOneX = 0;
+ let barTwoX = 6;
+ g.fillRect(offset+barOneX,offsetY+0,offset+barOneX+1,offsetY+digitBarHeight);
+ g.fillRect(offset+barTwoX,offsetY+0,offset+barTwoX+1,offsetY+digitBarHeight);
+ //g.drawString("8",offset+2,offsetY+textHeight+5);
+}
+
+function drawRNineWithOffset(offset, offsetY) {
+ let barOneX = 0;
+ let barTwoX = 8;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+5+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight);
+ //g.drawString("9",offset+2,offsetY+textHeight+5);
+}
+
+function drawRZeroWithOffset(offset, offsetY) {
+ let barOneX = 0;
+ let barTwoX = 10;
+ g.fillRect(barOneX+offset,offsetY+0,barOneX+5+offset,offsetY+digitBarHeight);
+ g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight);
+ //g.drawString("0",offset+2,offsetY+textHeight+5);
+}
+
+function drawCheckBar(offsetX, offsetY) {
+ const barOneX = offsetX+2;
+ const barOneWidth = 1;
+ const barTwoX = offsetX+6;
+ const barTwoWidth = 1;
+ g.fillRect(barOneX,offsetY,barOneX+barOneWidth,offsetY+checkBarHeight);
+ g.fillRect(barTwoX,offsetY,barTwoX+barTwoWidth,offsetY+checkBarHeight);
+}
+
+function calculateChecksum(digits) {
+ let oddSum = digits[6] + digits[4] + digits[2] + digits[0];
+ let evenSum = digits[5] + digits[3] + digits[1];
+
+ let checkSum = (10 - ((3 * oddSum + evenSum) % 10)) % 10;
+
+ return checkSum;
+}
+
+function storeStepCount() {
+ stepCount = Bangle.getStepCount();
+ require("Storage").writeJSON("stepCount",stepCount);
+}
+
+function getStepCount() {
+ let accumulatedSteps = Bangle.getStepCount();
+ if(accumulatedSteps <= stepCount) {
+ return 0;
+ }
+ return accumulatedSteps - stepCount;
+}
+
+function resetAtMidnight() {
+ let now = new Date();
+ let night = new Date(
+ now.getFullYear(),
+ now.getMonth(),
+ now.getDate(), // the next day, ...
+ 23, 58, 0 // ...at 00:00:00 hours
+);
+ let msToMidnight = night.getTime() - now.getTime();
+
+ setTimeout(function() {
+ storeStepCount(); // <-- This is the function being called at midnight.
+ resetAtMidnight(); // Then, reset again next midnight.
+ }, msToMidnight);
+}
+
+resetAtMidnight();
+
+// The layout, referencing the custom renderer
+var Layout = require("Layout");
+var layout = new Layout( {
+ type:"v", c: [
+ {type:"custom", render:renderWatch, id:"watch", bgCol:g.theme.bg, fillx:1, filly:1 }
+ ]
+});
+
+// Clear the screen once, at startup
+g.clear();
+Bangle.setUI("clock");
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+layout.render();
+
+Bangle.on('lock', function(locked) {
+ if(!locked) {
+ layout.render();
+ }
+});
diff --git a/apps/barcode/barcode.clock.png b/apps/barcode/barcode.clock.png
new file mode 100644
index 000000000..7d249cdeb
Binary files /dev/null and b/apps/barcode/barcode.clock.png differ
diff --git a/apps/barcode/barcode.icon.js b/apps/barcode/barcode.icon.js
new file mode 100644
index 000000000..969943e0e
--- /dev/null
+++ b/apps/barcode/barcode.icon.js
@@ -0,0 +1 @@
+E.toArrayBuffer(atob("MDAE///+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifrV6mlp+rXYiFXZr8k4q8r+if/////+if7+///u//79ie7/7//u///+if/////+ifnP6t+378r+ienvyf/K/7v+if/////+ifrfx6/I78j+ibe/+e/W75n+if/////+iee/+t/Z77f+iervyv/r/7v+if//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////"))
diff --git a/apps/barcode/barcode.icon.png b/apps/barcode/barcode.icon.png
new file mode 100644
index 000000000..43fa77a6f
Binary files /dev/null and b/apps/barcode/barcode.icon.png differ
diff --git a/apps/barcode/metadata.json b/apps/barcode/metadata.json
new file mode 100644
index 000000000..3f6bf06e6
--- /dev/null
+++ b/apps/barcode/metadata.json
@@ -0,0 +1,16 @@
+{ "id": "barcode",
+ "name": "Barcode clock",
+ "shortName":"Barcode clock",
+ "icon": "barcode.icon.png",
+ "version":"0.10",
+ "description": "EAN-8 compatible barcode clock.",
+ "tags": "barcode,ean,ean-8,watchface,clock,clockface",
+ "type": "clock",
+ "supports" : ["BANGLEJS2"],
+ "storage": [
+ {"name":"barcode.app.js","url":"barcode.app.js"},
+ {"name":"barcode.img","url":"barcode.icon.js","evaluate":true}
+ ],
+ "readme":"README.md",
+ "screenshots": [{"url":"barcode.clock.png"}]
+}
diff --git a/apps/batclock/ChangeLog b/apps/batclock/ChangeLog
index e6e21b146..2a2d91b74 100644
--- a/apps/batclock/ChangeLog
+++ b/apps/batclock/ChangeLog
@@ -1,2 +1,3 @@
0.01: App Created!
0.02: Update to use Bangle.setUI instead of setWatch
+0.03: Tell clock widgets to hide.
diff --git a/apps/batclock/bat-clock.app.js b/apps/batclock/bat-clock.app.js
index 31b8f5b9b..dc649160f 100644
--- a/apps/batclock/bat-clock.app.js
+++ b/apps/batclock/bat-clock.app.js
@@ -249,6 +249,9 @@ g.clear();
g.setColor(0, 0.5, 0).drawImage(bg_crack);
g.setColor(1, 1, 1).drawImage(batman);
+// Show launcher when button pressed
+Bangle.setUI("clock");
+
Bangle.loadWidgets();
Bangle.drawWidgets();
@@ -256,5 +259,3 @@ Bangle.drawWidgets();
timeInterval = setInterval(showTime, 1000);
showTime();
-// Show launcher when button pressed
-Bangle.setUI("clock");
diff --git a/apps/batclock/metadata.json b/apps/batclock/metadata.json
index 8aa115780..e6520cb90 100644
--- a/apps/batclock/metadata.json
+++ b/apps/batclock/metadata.json
@@ -2,7 +2,7 @@
"id": "batclock",
"name": "Bat Clock",
"shortName": "Bat Clock",
- "version": "0.02",
+ "version": "0.03",
"description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.",
"icon": "bat-clock.png",
"screenshots": [{"url":"screenshot.png"}],
diff --git a/apps/bclock/ChangeLog b/apps/bclock/ChangeLog
index 5b2cf598c..79c198431 100644
--- a/apps/bclock/ChangeLog
+++ b/apps/bclock/ChangeLog
@@ -1,2 +1,3 @@
0.02: Modified for use with new bootloader and firmware
0.03: Update to use Bangle.setUI instead of setWatch
+0.04: Tell clock widgets to hide.
diff --git a/apps/bclock/clock-binary.js b/apps/bclock/clock-binary.js
index fdf945ee6..c08a7abe6 100644
--- a/apps/bclock/clock-binary.js
+++ b/apps/bclock/clock-binary.js
@@ -100,10 +100,12 @@ Bangle.on('lcdPower', on => {
if (on) drawClock();
});
+// Show launcher when button pressed
+Bangle.setUI("clock");
+
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
setInterval(() => { drawClock(); }, 1000);
drawClock();
-// Show launcher when button pressed
-Bangle.setUI("clock");
+
diff --git a/apps/bclock/metadata.json b/apps/bclock/metadata.json
index 94219a30b..c6a24d89f 100644
--- a/apps/bclock/metadata.json
+++ b/apps/bclock/metadata.json
@@ -1,7 +1,7 @@
{
"id": "bclock",
"name": "Binary Clock",
- "version": "0.03",
+ "version": "0.04",
"description": "A simple binary clock watch face",
"icon": "clock-binary.png",
"type": "clock",
diff --git a/apps/bigdclock/ChangeLog b/apps/bigdclock/ChangeLog
new file mode 100644
index 000000000..b373f1876
--- /dev/null
+++ b/apps/bigdclock/ChangeLog
@@ -0,0 +1,6 @@
+0.01: Initial version
+0.02: setTimeout bug fix; no leading zero on date; lightmode; 12 hour format; cleanup
+0.03: Internationalisation; bug fix - battery icon responds promptly to charging state
+0.04: bug fix
+0.05: proper fix for the race condition in queueDraw()
+0.06: Tell clock widgets to hide.
diff --git a/apps/bigdclock/README.md b/apps/bigdclock/README.md
new file mode 100644
index 000000000..71f4362fa
--- /dev/null
+++ b/apps/bigdclock/README.md
@@ -0,0 +1,14 @@
+# Big Digit Clock
+
+There are a number of big digit clocks available for the Bangle, but this is
+the first which shows all the essential information that a clock needs to show
+in a manner that is easy to read by those with poor eyesight.
+
+The clock shows the time-of-day, the day-of-week and the day-of-month, as well
+as an easy-to-see icon showing the current charge on the battery.
+
+
+
+## Creator
+
+Created by [Deirdre O'Byrne](https://github.com/deirdreobyrne)
diff --git a/apps/bigdclock/bigdclock.app.js b/apps/bigdclock/bigdclock.app.js
new file mode 100644
index 000000000..3adf96984
--- /dev/null
+++ b/apps/bigdclock/bigdclock.app.js
@@ -0,0 +1,92 @@
+//
+
+Graphics.prototype.setFontOpenSans = function(scale) {
+ // Actual height 48 (50 - 3)
+ this.setFontCustom(
+ atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAAAAP8AAAAAAAAB/wAAAAAAAAH/gAAAAAAAAf+AAAAAAAAB/4AAAAAAAAH/gAAAAAAAAf8AAAAAAAAA/wAAAAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAPAAAAAAAAAH8AAAAAAAAD/wAAAAAAAA//AAAAAAAAf/8AAAAAAAP//wAAAAAAH///AAAAAAB///4AAAAAA///8AAAAAAf///AAAAAAH///gAAAAAD///wAAAAAB///4AAAAAAf//+AAAAAAP///AAAAAAH///gAAAAAA///4AAAAAAD//8AAAAAAAP/+AAAAAAAA//gAAAAAAAD/wAAAAAAAAP4AAAAAAAAA8AAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAAAAAAAP///AAAAAAH////gAAAAD/////gAAAAf/////gAAAH//////AAAA//////+AAAD//////8AAAf//////4AAD//4AH//gAAP/gAAAf/AAA/4AAAAf8AAH/AAAAA/4AAf4AAAAB/gAB/AAAAAD+AAH8AAAAAP4AAfwAAAAA/gAB/AAAAAD+AAH8AAAAAP4AAf4AAAAB/gAB/gAAAAH+AAD/gAAAB/wAAP/gAAAf/AAA//4AAf/8AAB///////gAAD//////8AAAH//////wAAAP/////+AAAAf/////wAAAA/////8AAAAAf////AAAAAAf///gAAAAAAB//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAHgAAAAAAAAA/AAAAAAAAAH+AAAAAAAAA/4AAAAAAAAD/AAAAAAAAAf8AAAAAAAAD/gAAAAAAAAf8AAAAAAAAB/gAAAAAAAAP8AAAAAAAAB/wAAAAAAAAP///////AAA///////8AAD///////wAAP///////AAA///////8AAD///////wAAP///////AAA///////8AAD///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAD8AAAOAAAAAfwAAB+AAAAD/AAAH8AAAAf8AAA/wAAAH/wAAH/AAAA//AAAf4AAAH/8AAD/gAAA//wAAP8AAAH//AAA/gAAA//8AAD+AAAH//wAAf4AAA/9/AAB/AAAH/n8AAH8AAA/8fwAAfwAAH/h/AAB/AAA/8H8AAH8AAH/gfwAAfwAA/8B/AAB/gAH/gH8AAH+AB/8AfwAAP8Af/gB/AAA////8AH8AAD////gAfwAAH///8AB/AAAf///gAH8AAA///8AAfwAAB///gAB/AAAD//4AAH8AAAH/+AAAfwAAAH/gAAB/AAAAAAAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAYAAAAB/gAAB4AAAAD+AAAPwAAAAP4AAA/gAAAAfwAAH+AAAAB/AAAf4AAAAH8AAD/AB+AAfwAAP8AH4AA/gAA/gAfgAD+AAH+AB+AAP4AAfwAH4AA/gAB/AAfgAD+AAH8AB+AAP4AAfwAH4AA/gAB/AA/wAD+AAH8AD/AAP4AAfwAP8AA/gAB/AA/wAD+AAH+AH/AAf4AAf4Af+AB/AAA/wH/8AP8AAD////4D/wAAP//+////AAAf//7///4AAB///P///gAAD//8f//8AAAP//h///gAAAf/8D//+AAAA//gH//wAAAA/4AP/8AAAAAAAAP/AAAAAAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAAAA/wAAAAAAAAH/AAAAAAAAB/8AAAAAAAAP/wAAAAAAAD//AAAAAAAAf/8AAAAAAAH//wAAAAAAA/+/AAAAAAAP/z8AAAAAAB/8PwAAAAAAf/g/AAAAAAD/4D8AAAAAAf/APwAAAAAH/4A/AAAAAA/+AD8AAAAAP/wAPwAAAAB/8AA/AAAAAf/gAD8AAAAD/4AAPwAAAA//AAA/AAAAD///////wAAP///////AAA///////8AAD///////wAAP///////AAA///////8AAD///////wAAP///////AAAAAAAA/AAAAAAAAAD8AAAAAAAAAPwAAAAAAAAA/AAAAAAAAAD8AAAAAAAAAPwAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAQAB/gAAAD//gAD+AAA////AAP8AAD///8AAfwAAP///wAB/AAA////AAH8AAD///8AAf4AAP///wAA/gAA////AAD+AAD/+H8AAP4AAP4AfwAA/gAA/gB+AAD+AAD+AH4AAP4AAP4AfwAA/gAA/gB/AAD+AAD+AH8AAP4AAP4AfwAB/gAA/gB/AAH+AAD+AH+AA/wAAP4Af8AD/AAA/gB/4A/8AAD+AD////gAAP4AP///+AAA/gAf///wAAD+AB////AAAP4AD///4AAA/gAH///AAAAAAAP//4AAAAAAAf/+AAAAAAAAf/gAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//AAAAAAAf///gAAAAAP////gAAAAD/////gAAAAf/////AAAAD/////+AAAA//////8AAAD//////4AAAf/8/4//gAAD/8H+Af/AAAP/AfgAf8AAB/wD+AA/wAAH+APwAB/gAA/wB/AAD+AAD+AH4AAP4AAP4AfgAA/gAB/gB+AAD+AAH8AH4AAP4AAfwAfgAA/gAB/AB/AAH+AAH8AH8AAf4AAfwAf4AD/AAB/AB/4A/8AAH8AH////wAAfwAP///+AAB/AA////4AAH8AB////AAAfwAD///4AAA/AAH///AAAAAAAP//4AAAAAAAP/+AAAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAAAAAAA/gAAAAAAAAD+AAAAAAAAAP4AAAAAAAAA/gAAAAAAAAD+AAAAAAAAAP4AAAAADAAA/gAAAAB8AAD+AAAAAfwAAP4AAAAH/AAA/gAAAB/8AAD+AAAAf/wAAP4AAAP//AAA/gAAD//8AAD+AAA///wAAP4AAP//8AAA/gAD///AAAD+AB///wAAAP4Af//4AAAA/gH//+AAAAD+B///gAAAAP4f//wAAAAA/v//8AAAAAD////AAAAAAP///wAAAAAA///4AAAAAAD//+AAAAAAAP//gAAAAAAA//wAAAAAAAD/8AAAAAAAAP/AAAAAAAAA/wAAAAAAAAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAH/wAAAAH8AB//gAAAB/8AP//gAAAf/8B//+AAAB//4P//8AAAP//w///4AAB///n///gAAH//+////AAA/////gf8AAD/h//4AfwAAP4B//AB/gAB/gD/4AD+AAH8AP/gAP4AAfwAf8AA/gAB/AA/wAB+AAH4AD/AAH4AAfwAP8AAfgAB/AB/4AD+AAH8AH/gAP4AAfwA//AA/gAA/gH/+AH+AAD/h//8AfwAAP//+/4D/AAAf//7///8AAB///H///gAAD//4P//+AAAP//g///wAAAf/8B//+AAAA//gD//wAAAA/4AH/+AAAAAAAAH/wAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAAAD//AAAAAAAA///AAAAAAAH//+AAPwAAB///8AA/gAAP///4AD+AAA////wAP4AAH////AA/gAAf///8AD+AAD/4B/4AP4AAP+AB/gA/gAA/gAD+AD+AAH+AAP4AP4AAfwAAfgA/gAB/AAB+AD+AAH8AAH4AP4AAfwAAfgB/AAB/AAB+AH8AAH8AAH4A/wAAf4AA/AH+AAA/gAD8A/4AAD/gAfwH/gAAP/AD+B/8AAAf/g/w//gAAB//////+AAAD//////wAAAH/////+AAAAP/////wAAAAf////8AAAAA/////AAAAAA////wAAAAAAf//4AAAAAAAH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAfgAAAAP8AAD/AAAAA/4AAf8AAAAH/gAB/4AAAAf+AAH/gAAAB/4AAf+AAAAH/gAB/4AAAAf+AAH/AAAAA/wAAP8AAAAB+AAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='),
+ 46,
+ atob("EhklJSUlJSUlJSUlEg=="),
+ 64+(scale<<8)+(1<<16)
+ );
+};
+
+var drawTimeout;
+
+function queueDraw(millis_now) {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function () {
+ drawTimeout = undefined;
+ draw();
+ }, 60000 - (millis_now % 60000));
+}
+
+function draw() {
+ var date = new Date();
+ var h = date.getHours(),
+ m = date.getMinutes();
+ var d = date.getDate(),
+ w = date.getDay(); // d=1..31; w=0..6
+ const level = E.getBattery();
+ const width = level + (level/2);
+ var is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
+ var dows = require("date_utils").dows(0,1);
+
+ g.reset();
+ g.clear();
+
+ g.setFontOpenSans();
+ g.setFontAlign(0, -1);
+ if (is12Hour) {
+ if (h > 12) h -= 12;
+ if (h == 0) h = 12;
+ g.drawString(h + ":" + ("0"+m).substr(-2), g.getWidth() / 2, 30);
+ } else {
+ g.drawString(("0"+h).substr(-2) + ":" + ("0"+m).substr(-2), g.getWidth() / 2, 30);
+ }
+ g.setFontAlign(1, -1);
+ g.drawString(d, g.getWidth() -6, 98);
+ g.setFont('Vector', 52);
+ g.setFontAlign(-1, -1);
+ g.drawString(dows[w].slice(0,2).toUpperCase(), 6, 103);
+
+ g.fillRect(9,159,166,171);
+ g.fillRect(167,163,170,167);
+ if (Bangle.isCharging()) {
+ g.setColor(1,1,0);
+ } else if (level > 40) {
+ g.setColor(0,1,0);
+ } else {
+ g.setColor(1,0,0);
+ }
+ g.fillRect(12,162,12+width,168);
+ if (level < 100) {
+ g.setColor(g.theme.bg);
+ g.fillRect(12+width+1,162,162,168);
+ }
+
+ g.setColor(0, 1, 0);
+ g.fillRect(0, 90, g.getWidth(), 94);
+
+ // widget redraw
+ Bangle.drawWidgets();
+ queueDraw(date.getTime());
+}
+
+Bangle.on('lcdPower', on => {
+ if (on) {
+ draw(); // draw immediately, queue redraw
+ } else { // stop draw timer
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ }
+});
+
+Bangle.on('charging', (charging) => {
+ draw();
+});
+
+Bangle.setUI("clock");
+
+Bangle.loadWidgets();
+draw();
+
diff --git a/apps/bigdclock/bigdclock.icon.js b/apps/bigdclock/bigdclock.icon.js
new file mode 100644
index 000000000..4aaecfa23
--- /dev/null
+++ b/apps/bigdclock/bigdclock.icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwgJC/AAMD4F4AgN4g/D/4FB/E/AoUH/F/AoOAh4FCz4FD4EPAoUHAoOHwAFDx/AAoUfAol/g4RD/w1Cg/B/AFD4fwn4XC4fg8/wAoPH//P7AFE9wFE8YFEEwcf4+BwAFBiACBAoUwAQPAAQMgAQNAArIjFF4sYgEBAoUIAoIRChi3B8AFBg8Ah/wAoIVBjH8ZAXguF+AoSDBn7WEh4FEg4"))
diff --git a/apps/bigdclock/bigdclock.png b/apps/bigdclock/bigdclock.png
new file mode 100644
index 000000000..4da1a9010
Binary files /dev/null and b/apps/bigdclock/bigdclock.png differ
diff --git a/apps/bigdclock/metadata.json b/apps/bigdclock/metadata.json
new file mode 100644
index 000000000..ce91d921e
--- /dev/null
+++ b/apps/bigdclock/metadata.json
@@ -0,0 +1,17 @@
+{ "id": "bigdclock",
+ "name": "Big digit clock containing just the essentials",
+ "shortName":"Big digit clk",
+ "version":"0.06",
+ "description": "A clock containing just the essentials, made as easy to read as possible for those of us that need glasses. It contains the time, the day-of-week, the day-of-month, and the current battery state-of-charge.",
+ "icon": "bigdclock.png",
+ "type": "clock",
+ "tags": "clock",
+ "allow_emulator":true,
+ "supports" : ["BANGLEJS2"],
+ "readme": "README.md",
+ "screenshots": [ { "url":"screenshot.png" } ],
+ "storage": [
+ {"name":"bigdclock.app.js","url":"bigdclock.app.js"},
+ {"name":"bigdclock.img","url":"bigdclock.icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/bigdclock/screenshot.png b/apps/bigdclock/screenshot.png
new file mode 100644
index 000000000..8a12b266e
Binary files /dev/null and b/apps/bigdclock/screenshot.png differ
diff --git a/apps/bikespeedo/ChangeLog b/apps/bikespeedo/ChangeLog
index 2a3023750..10752ee2b 100644
--- a/apps/bikespeedo/ChangeLog
+++ b/apps/bikespeedo/ChangeLog
@@ -1,2 +1,3 @@
0.01: New App!
0.02: Barometer altitude adjustment setting
+0.03: Use default Bangle formatter for booleans
diff --git a/apps/bikespeedo/metadata.json b/apps/bikespeedo/metadata.json
index c3de0487c..80b91427c 100644
--- a/apps/bikespeedo/metadata.json
+++ b/apps/bikespeedo/metadata.json
@@ -2,7 +2,7 @@
"id": "bikespeedo",
"name": "Bike Speedometer (beta)",
"shortName": "Bike Speedometer",
- "version": "0.02",
+ "version": "0.03",
"description": "Shows GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude from internal sources",
"icon": "app.png",
"screenshots": [{"url":"Screenshot.png"}],
diff --git a/apps/bikespeedo/settings.js b/apps/bikespeedo/settings.js
index a3921f4a3..f41524263 100644
--- a/apps/bikespeedo/settings.js
+++ b/apps/bikespeedo/settings.js
@@ -33,12 +33,10 @@
'< Back': function() { E.showMenu(appMenu); },
'Speed' : {
value : settings.spdFilt,
- format : v => v?"On":"Off",
onchange : () => { settings.spdFilt = !settings.spdFilt; writeSettings(); }
},
'Altitude' : {
value : settings.altFilt,
- format : v => v?"On":"Off",
onchange : () => { settings.altFilt = !settings.altFilt; writeSettings(); }
}
};
diff --git a/apps/binclock/ChangeLog b/apps/binclock/ChangeLog
index dc4ed8308..7c31cc0d3 100644
--- a/apps/binclock/ChangeLog
+++ b/apps/binclock/ChangeLog
@@ -1,3 +1,4 @@
0.01: New App!
0.02: Fixed bug where screen didn't clear so incorrect time displayed.
0.03: Update to use Bangle.setUI instead of setWatch
+0.04: Tell clock widgets to hide.
diff --git a/apps/binclock/app.js b/apps/binclock/app.js
index f8cbe8dd5..d9c74e6ce 100644
--- a/apps/binclock/app.js
+++ b/apps/binclock/app.js
@@ -164,9 +164,6 @@ Bangle.on('lcdPower',on=>{
draw(); // draw immediately
}
});
-// Load widgets
-Bangle.loadWidgets();
-Bangle.drawWidgets();
// Show launcher when button pressed
Bangle.setUI("clockupdown", btn=>{
if (btn!=1) return;
@@ -176,3 +173,6 @@ Bangle.setUI("clockupdown", btn=>{
displayTime = 0;
}
});
+// Load widgets
+Bangle.loadWidgets();
+Bangle.drawWidgets();
diff --git a/apps/binclock/metadata.json b/apps/binclock/metadata.json
index d17045868..2ca2755a6 100644
--- a/apps/binclock/metadata.json
+++ b/apps/binclock/metadata.json
@@ -2,7 +2,7 @@
"id": "binclock",
"name": "Binary Clock",
"shortName": "Binary Clock",
- "version": "0.03",
+ "version": "0.04",
"description": "A binary clock with hours and minutes. BTN1 toggles a digital clock.",
"icon": "app.png",
"type": "clock",
diff --git a/apps/binwatch/ChangeLog b/apps/binwatch/ChangeLog
index 1e54f489c..e355155b3 100644
--- a/apps/binwatch/ChangeLog
+++ b/apps/binwatch/ChangeLog
@@ -2,3 +2,5 @@
0.02: first running version for BangleJs2
0.03: corrected icon, added screen shot, extended description
0.04: corrected format of background image (raw binary)
+0.05: move setUI() up before draw() as to not have a false positive 'sanity
+check' when building on github.
diff --git a/apps/binwatch/app.js b/apps/binwatch/app.js
index 28d7a06a5..153bebb32 100644
--- a/apps/binwatch/app.js
+++ b/apps/binwatch/app.js
@@ -334,6 +334,7 @@ function setRuntimeValues(resolution) {
var hour = 0, minute = 1, second = 50;
var batVLevel = 20;
+Bangle.setUI("clock");
function draw() {
var d = new Date();
@@ -371,7 +372,6 @@ function draw() {
}
// Show launcher when button pressed
-Bangle.setUI("clock");
setRuntimeValues(g.getWidth());
g.reset().clear();
Bangle.loadWidgets();
diff --git a/apps/binwatch/metadata.json b/apps/binwatch/metadata.json
index 0b5fb2c72..0b4dbc697 100644
--- a/apps/binwatch/metadata.json
+++ b/apps/binwatch/metadata.json
@@ -3,7 +3,7 @@
"shortName":"BinWatch",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
- "version":"0.04",
+ "version":"0.05",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"allow_emulator":true,
diff --git a/apps/blobclk/ChangeLog b/apps/blobclk/ChangeLog
index 9c4ef5b7b..193eb5024 100644
--- a/apps/blobclk/ChangeLog
+++ b/apps/blobclk/ChangeLog
@@ -5,3 +5,4 @@
0.04: Modified to account for changes in the behavior of Graphics.fillPoly
0.05: Slight increase to draw speed after LCD on
0.06: Update to use Bangle.setUI instead of setWatch, allow themes and different size screens
+0.07: Tell clock widgets to hide.
diff --git a/apps/blobclk/clock-blob.js b/apps/blobclk/clock-blob.js
index c84b8a1e6..d23e18ff9 100644
--- a/apps/blobclk/clock-blob.js
+++ b/apps/blobclk/clock-blob.js
@@ -99,6 +99,10 @@ function startTimers() {
Bangle.drawWidgets();
intervalRef = setInterval(redraw,1000);
}
+
+// Show launcher when button pressed
+Bangle.setUI("clock");
+
Bangle.loadWidgets();
startTimers();
Bangle.on('lcdPower',function(on) {
@@ -108,5 +112,3 @@ Bangle.on('lcdPower',function(on) {
clearTimers();
}
});
-// Show launcher when button pressed
-Bangle.setUI("clock");
diff --git a/apps/blobclk/metadata.json b/apps/blobclk/metadata.json
index 85d7deabe..3ae8de222 100644
--- a/apps/blobclk/metadata.json
+++ b/apps/blobclk/metadata.json
@@ -2,7 +2,7 @@
"id": "blobclk",
"name": "Large Digit Blob Clock",
"shortName": "Blob Clock",
- "version": "0.06",
+ "version": "0.07",
"description": "A clock with big digits",
"icon": "clock-blob.png",
"type": "clock",
diff --git a/apps/boldclk/ChangeLog b/apps/boldclk/ChangeLog
index 30ac31c61..0c6e8cb52 100644
--- a/apps/boldclk/ChangeLog
+++ b/apps/boldclk/ChangeLog
@@ -3,3 +3,4 @@
0.04: Work with themes, smaller screens
0.05: Adjust hand lengths to be within 'tick' points
0.06: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up".
+0.07: Tell clock widgets to hide.
diff --git a/apps/boldclk/bold_clock.js b/apps/boldclk/bold_clock.js
index 9d3ea0756..763530a32 100644
--- a/apps/boldclk/bold_clock.js
+++ b/apps/boldclk/bold_clock.js
@@ -130,9 +130,10 @@ Bangle.on('lcdPower', (on) => {
}
});
+// Show launcher when button pressed
+Bangle.setUI("clock");
+
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
startTimers();
-// Show launcher when button pressed
-Bangle.setUI("clock");
diff --git a/apps/boldclk/metadata.json b/apps/boldclk/metadata.json
index cf961347d..086203142 100644
--- a/apps/boldclk/metadata.json
+++ b/apps/boldclk/metadata.json
@@ -1,7 +1,7 @@
{
"id": "boldclk",
"name": "Bold Clock",
- "version": "0.06",
+ "version": "0.07",
"description": "Simple, readable and practical clock",
"icon": "bold_clock.png",
"screenshots": [{"url":"screenshot_bold.png"}],
diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog
index e3f492d3b..a43ecf86e 100644
--- a/apps/boot/ChangeLog
+++ b/apps/boot/ChangeLog
@@ -51,3 +51,4 @@
0.45: Fix 0.44 regression (auto-add semi-colon between each boot code chunk)
0.46: Fix no clock found error on Bangle.js 2
0.47: Add polyfill for setUI with an object as an argument (fix regression for 2v12 devices after Layout module changed)
+0.48: Workaround for BTHRM issues on Bangle.js 1 (write .boot files in chunks)
diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js
index 119cd2c2c..4cb3c52e4 100644
--- a/apps/boot/bootupdate.js
+++ b/apps/boot/bootupdate.js
@@ -197,8 +197,18 @@ bootFiles.forEach(bootFile=>{
require('Storage').write('.boot0',"//"+bootFile+"\n",fileOffset);
fileOffset+=2+bootFile.length+1;
var bf = require('Storage').read(bootFile);
- require('Storage').write('.boot0',bf,fileOffset);
- fileOffset+=bf.length;
+ // we can't just write 'bf' in one go because at least in 2v13 and earlier
+ // Espruino wants to read the whole file into RAM first, and on Bangle.js 1
+ // it can be too big (especially BTHRM).
+ var bflen = bf.length;
+ var bfoffset = 0;
+ while (bflen) {
+ var bfchunk = Math.min(bflen, 2048);
+ require('Storage').write('.boot0',bf.substr(bfoffset, bfchunk),fileOffset);
+ fileOffset+=bfchunk;
+ bfoffset+=bfchunk;
+ bflen-=bfchunk;
+ }
require('Storage').write('.boot0',";\n",fileOffset);
fileOffset+=2;
});
diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json
index d1bf2edde..62adc4db1 100644
--- a/apps/boot/metadata.json
+++ b/apps/boot/metadata.json
@@ -1,7 +1,7 @@
{
"id": "boot",
"name": "Bootloader",
- "version": "0.47",
+ "version": "0.48",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png",
"type": "bootloader",
diff --git a/apps/bordle/ChangeLog b/apps/bordle/ChangeLog
index f45509a34..ddbd6239c 100644
--- a/apps/bordle/ChangeLog
+++ b/apps/bordle/ChangeLog
@@ -1,2 +1,3 @@
0.01: New App
0.02: app keeps track of statistics now
+0.03: Fix bug in valid word detection
diff --git a/apps/bordle/bordle.app.js b/apps/bordle/bordle.app.js
index 20aa02bc2..07e954a6d 100644
--- a/apps/bordle/bordle.app.js
+++ b/apps/bordle/bordle.app.js
@@ -110,7 +110,12 @@ class Wordle {
}
}
addGuess(w) {
- if ((this.words.indexOf(w.toLowerCase())%5)!=0) {
+ let idx = -1;
+ do{
+ idx = this.words.indexOf(w.toLowerCase(), idx+1);
+ }
+ while(idx !== -1 && idx%5 !== 0);
+ if(idx%5 !== 0) {
E.showAlert(w+"\nis not a word", "Invalid word").then(function() {
layout = getKeyLayout("");
wordle.render(true);
diff --git a/apps/bordle/metadata.json b/apps/bordle/metadata.json
index 37ef5c855..f6011f798 100644
--- a/apps/bordle/metadata.json
+++ b/apps/bordle/metadata.json
@@ -2,7 +2,7 @@
"name": "Bordle",
"shortName":"Bordle",
"icon": "app.png",
- "version":"0.02",
+ "version":"0.03",
"description": "Bangle version of a popular word search game",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
diff --git a/apps/bowserWF/ChangeLog b/apps/bowserWF/ChangeLog
new file mode 100644
index 000000000..dd2b05fb3
--- /dev/null
+++ b/apps/bowserWF/ChangeLog
@@ -0,0 +1,3 @@
+...
+0.02: First update with ChangeLog Added
+0.03: updated watch face to use the ClockFace library
diff --git a/apps/bowserWF/app.js b/apps/bowserWF/app.js
index e53d945cc..956c43602 100644
--- a/apps/bowserWF/app.js
+++ b/apps/bowserWF/app.js
@@ -1,102 +1,233 @@
var sprite = {
- width : 47, height : 47, bpp : 3,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("kmSpICFn/+BAwCImV//VICJuT//SogRMpmT/2SCJtSyQDB/4RMymRkmX/gRLygDC3/piVhCJElAYf/pNIkgRIlIDCl/6pVBkIRIGwWJEYPypMJCI9KGwQRBLANIPRI2CGoPkyVCBwmeyVLTYNJom8yImBz4gEqV/6Vf+g2BPwf/IIq8C/+kyVRkgDBp/5CIX/+mkz/+y/9BIOf0v6///5LdCz+kCIOk34RBYQMSp5XBGQVk/pNBAQP/9IyBxGSv4yCk/1OIK8EC4QgEpM/JgJ+EGoIRBTApQCEYvplLOFXIIdBO4SqBeQJABGoeTDQMlk5WCAAPSYQLgEz4aBlM/9IgB/7CCcAvP/QsBiVfUwOJBgUiCIcmpAVCy/+pMAKwMkRgIRCp6VBAwW6qVOgmSgPkwgRDv53E6WSuEkyEPRgmf2VJv5HBl2SgAKBwEJRgnJiVKp/Sr/0y/yBQOQv56DKwVSv2STwO/DgWD/BADmaDByRoBYoQRCgFCCIf/+jgDNwOUAwMg/kSPQbODX4IJBAwUH8B6DsmRl5oBl7OBklMyV+gBoDycSxMpiVLZwS8EAQeYyjaByR6BBIJBDAQnEIgbFCogOFRgQDBr//I4L0EAQsxAYP//5WCGQ6MCAAKbCpKYEAQiMB//kIQOUyf+CJF/CIIEBTYOfcgQRHBQv/CJKnBpP8GRTCDJIPkGRQCB5I3C/n/EZUgA"))
+ width: 47,
+ height: 47,
+ bpp: 3,
+ transparent: 1,
+ buffer: require("heatshrink").decompress(
+ atob(
+ "kmSpICFn/+BAwCImV//VICJuT//SogRMpmT/2SCJtSyQDB/4RMymRkmX/gRLygDC3/piVhCJElAYf/pNIkgRIlIDCl/6pVBkIRIGwWJEYPypMJCI9KGwQRBLANIPRI2CGoPkyVCBwmeyVLTYNJom8yImBz4gEqV/6Vf+g2BPwf/IIq8C/+kyVRkgDBp/5CIX/+mkz/+y/9BIOf0v6///5LdCz+kCIOk34RBYQMSp5XBGQVk/pNBAQP/9IyBxGSv4yCk/1OIK8EC4QgEpM/JgJ+EGoIRBTApQCEYvplLOFXIIdBO4SqBeQJABGoeTDQMlk5WCAAPSYQLgEz4aBlM/9IgB/7CCcAvP/QsBiVfUwOJBgUiCIcmpAVCy/+pMAKwMkRgIRCp6VBAwW6qVOgmSgPkwgRDv53E6WSuEkyEPRgmf2VJv5HBl2SgAKBwEJRgnJiVKp/Sr/0y/yBQOQv56DKwVSv2STwO/DgWD/BADmaDByRoBYoQRCgFCCIf/+jgDNwOUAwMg/kSPQbODX4IJBAwUH8B6DsmRl5oBl7OBklMyV+gBoDycSxMpiVLZwS8EAQeYyjaByR6BBIJBDAQnEIgbFCogOFRgQDBr//I4L0EAQsxAYP//5WCGQ6MCAAKbCpKYEAQiMB//kIQOUyf+CJF/CIIEBTYOfcgQRHBQv/CJKnBpP8GRTCDJIPkGRQCB5I3C/n/EZUgA"
+ )
+ ),
};
const boxes = {
- width : 122, height : 56, bpp : 3,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("kmZkmSpICPwgDBmQUQAQMJAYNkFiOSiQDB5JESAYQsSpADByYsSyBZBydt23bAR+wgFJkwUQAQNggGSposR23AgMkzZESwECpM2IiUAgmSFiW2gDlBFiVsgDlBFiXYgDNBL4MDWZy2FgEGWZy2FgENWZy2EL4MbWZpTBWwZfBXJpTCWwZiCWZpTBWwZiCWZsbWwhiCWZpWCWwTORWwgXRWwgXRWwZESWwZESWwZESWwYXRWwgXRW362/W362/W362/W362/W362/W362/W362/W362/W362/W362/WwuAgazOWwsAgyzOWwsAhqzOWwhfBjazNKYK2DL4K5NKYS2DMQSzNKYK2DMQSzNja2EMQSzNKwS2CZyK2EC6K2EC6K2DIiS2DIiS2DIiUAFoMAAFTkBFtckyAtrLgWSpICnLIIsqyVAgAsqpIA="))
+ width: 122,
+ height: 56,
+ bpp: 3,
+ transparent: 1,
+ buffer: require("heatshrink").decompress(
+ atob(
+ "kmZkmSpICPwgDBmQUQAQMJAYNkFiOSiQDB5JESAYQsSpADByYsSyBZBydt23bAR+wgFJkwUQAQNggGSposR23AgMkzZESwECpM2IiUAgmSFiW2gDlBFiVsgDlBFiXYgDNBL4MDWZy2FgEGWZy2FgENWZy2EL4MbWZpTBWwZfBXJpTCWwZiCWZpTBWwZiCWZsbWwhiCWZpWCWwTORWwgXRWwgXRWwZESWwZESWwZESWwYXRWwgXRW362/W362/W362/W362/W362/W362/W362/W362/W362/W362/WwuAgazOWwsAgyzOWwsAhqzOWwhfBjazNKYK2DL4K5NKYS2DMQSzNKYK2DMQSzNja2EMQSzNKwS2CZyK2EC6K2EC6K2DIiS2DIiS2DIiUAFoMAAFTkBFtckyAtrLgWSpICnLIIsqyVAgAsqpIA="
+ )
+ ),
};
const background = {
- width : 176, height : 176, bpp : 3,
- transparent : 5,
- buffer : require("heatshrink").decompress(atob("kmSpIC/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/ATWAgEAIP1///8iRB8gf/AAOCIPdIIARBBoJB/+E4IP4ABghB9v4CB8BB5g/92//9pB7wP/97FEIO9IgDACAAn8iVBIOlHH4xBDnA+wyY9IAAmB/BB//5B/IOQ/OAARBup5B/yV/IP5B/IP5BRt5B7/wDC7aD8/w+B+3bBgP7IP5B7HYNt23/AQPfIPX/9oCC24IDINwCBIRAAHIOACBHI3+g4EC/l/4BByAQkA//wpED//4gGAhJB3pMAgQFBgEBH3AC/AX4C/AX4C/AX4C/AX4C/AUOAgBB/v//ghB9gf///gH3UgiVIIAJBBwRB5j+CIIf8uBB5//wIIXb//+hJB6o/92/7v5B7/0/97GCIPYAG4MgIP/BjkSIP34/hB//5B/AAQ+0IP5B/IP5BN7ZB97///wCBIPX93yAB2wCB+5B5tv//dt24CB35B5v/+n/t+P/I4PH8ESIO38gFA/+CgH/+EIgiD3gACCPoMAgQ+2AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/ASVIgAACgRB/IPY8GkAHBiRB/IPBLKgJB/IP5B/AQUAkmQghB/IP2AgEAyVAiRB/IP5BBpMAIP5B/IIUkgBB/IP5BpoAsBgJBOgEEIIoIBIP5BlyE27dt2EEIJ4CBBAlIgRBgpEAhu2IIO24ESQwxB/IJQhGkEJIL8GHwQCDgOweQpB/IKMkwAKJILVgAofYeQhBzsEAIKICLoESILmBQARBBtuwgZB3kA4B4ENIgJBcpMAIMYCDIOcAgEbHYgCGsEJkhEBE6cBIP5BZfYQ+JIIkDsEBIP5BVyEAIKtAHxgCDwBEBINk2IKCGCIKmSpECIP5BUkEBHyACD2BBUFoMJIP5BSpEbHyQCDIP5BXkmAIP5B/AQcAbKJB/ILH/AAP8hM/AgWSv4KCAAP+gmfAoXJk4ME//gpIEC8mTBgvwkgEC+QRDAAX4gVPAgP5kgsCLwWQh/kMIUf5LuFg4jBAoMBKAJ5EwF/AoUA/yFFoE/CI6RDgY+BCIQsDIP5B/IP5B/IP5B/IJ/AIJfghJBKv0EIJcAIJfwIP5BMhMAAAMEz5BGgmABoVJII9IBgUkII8kBgUSII8CoAMBhJB/IIsQoMAYoP/AAP4YpAMC/+BII9/BgXAYpAMC8DFIBgXwIIcCIP6DCgkQh/kCIRBIbQcBIJAFCgBBICI5BE/IRDFgQA="))
+ width: 176,
+ height: 176,
+ bpp: 3,
+ transparent: 5,
+ buffer: require("heatshrink").decompress(
+ atob(
+ "kmSpIC/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/ATWAgEAIP1///8iRB8gf/AAOCIPdIIARBBoJB/+E4IP4ABghB9v4CB8BB5g/92//9pB7wP/97FEIO9IgDACAAn8iVBIOlHH4xBDnA+wyY9IAAmB/BB//5B/IOQ/OAARBup5B/yV/IP5B/IP5BRt5B7/wDC7aD8/w+B+3bBgP7IP5B7HYNt23/AQPfIPX/9oCC24IDINwCBIRAAHIOACBHI3+g4EC/l/4BByAQkA//wpED//4gGAhJB3pMAgQFBgEBH3AC/AX4C/AX4C/AX4C/AX4C/AUOAgBB/v//ghB9gf///gH3UgiVIIAJBBwRB5j+CIIf8uBB5//wIIXb//+hJB6o/92/7v5B7/0/97GCIPYAG4MgIP/BjkSIP34/hB//5B/AAQ+0IP5B/IP5BN7ZB97///wCBIPX93yAB2wCB+5B5tv//dt24CB35B5v/+n/t+P/I4PH8ESIO38gFA/+CgH/+EIgiD3gACCPoMAgQ+2AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/ASVIgAACgRB/IPY8GkAHBiRB/IPBLKgJB/IP5B/AQUAkmQghB/IP2AgEAyVAiRB/IP5BBpMAIP5B/IIUkgBB/IP5BpoAsBgJBOgEEIIoIBIP5BlyE27dt2EEIJ4CBBAlIgRBgpEAhu2IIO24ESQwxB/IJQhGkEJIL8GHwQCDgOweQpB/IKMkwAKJILVgAofYeQhBzsEAIKICLoESILmBQARBBtuwgZB3kA4B4ENIgJBcpMAIMYCDIOcAgEbHYgCGsEJkhEBE6cBIP5BZfYQ+JIIkDsEBIP5BVyEAIKtAHxgCDwBEBINk2IKCGCIKmSpECIP5BUkEBHyACD2BBUFoMJIP5BSpEbHyQCDIP5BXkmAIP5B/AQcAbKJB/ILH/AAP8hM/AgWSv4KCAAP+gmfAoXJk4ME//gpIEC8mTBgvwkgEC+QRDAAX4gVPAgP5kgsCLwWQh/kMIUf5LuFg4jBAoMBKAJ5EwF/AoUA/yFFoE/CI6RDgY+BCIQsDIP5B/IP5B/IP5B/IJ/AIJfghJBKv0EIJcAIJfwIP5BMhMAAAMEz5BGgmABoVJII9IBgUkII8kBgUSII8CoAMBhJB/IIsQoMAYoP/AAP4YpAMC/+BII9/BgXAYpAMC8DFIBgXwIIcCIP6DCgkQh/kCIRBIbQcBIJAFCgBBICI5BE/IRDFgQA="
+ )
+ ),
};
numbersDims = {
- width: 20,
- height: 44
+ width: 20,
+ height: 44,
};
-const numbers = [
- require("heatshrink").decompress(atob("ikswcBkmSpIC/ARGQKYQIDAwUEBxMAAQNAgECpMgAQMkB4IOIAQQLCgEQBwQaBgEBB1oCBBwYCCiRWDCIRWEO5wOHAX4CnA=")),
- require("heatshrink").decompress(atob("ikswcBkmSpIC/ARNIKYIIEwEAggOKNIQODyAHCBxQsWB3TUFgMgA4sSBwzU/AVA=")),
- require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKggIBAwkCBw+QCIQLCgIRCDQcQBwwyDDwUSCgVAAwIOBEwI7EpI7FBw4FDghZGHwgOEF4Y+CEYQ+DBxQADNAIAFNAIOFa/4CoA=")),
- require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKosSAwsBBw4aCoEAgQjEBoIpEBwtIBoIUEwEAggUDBwwyDDoWQA4ZWHhIIEJQoOCgI+EBwMQEAYOJO4oLBO4oRDJQrX/AU4")),
- require("heatshrink").decompress(atob("ikswcBkmSpIC/ARNIKgQIDwAGBgQOJNQYOCyAHDBxEggB6BBwYDBiVABxIjBCIIODF4YOEAAkBV40QBwxiDNAosEB0IC/AUg")),
- require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ5UFkmQAwkCBxIdGCIIIDBxAsTgAaEkEASooOBiQOVJQgOBiBKDBxMSJQwRBLIgRCBwjX/AVA=")),
- require("heatshrink").decompress(atob("ikswcBkmSpIC/ARGQKgYICAwcCBxADBiQdDkEANYoOGEAYyEHYoOIHYqfFBxIdDBAMQFgZHCBysSFgwRBO46GFa/4CnA")),
- require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ5VGiAGFgIOIDQUgBwUCEYQOJGQYNBHAlADQgOHwEAggUDpANBCgYpBBwmQAwJiGhIjDB1gC/AU4A=")),
- require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKYYICAwcEBxGQgAaDgVJgACBDQQOJgB6CBwcAiQODHa4AEhIRBpAHDiARBwAGCgIgCFIYOCFIYOHiQrEJQxlCBwzX/AVAA=")),
- require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKggIBAwkCBw+QCIQLCgIRCDQcQBzkSTAsBHYoOIL4gOCMooOENAYOCoA4EBwoqDgiGGF4gOEa/4CoA=")),
+const numbers = [
+ require("heatshrink").decompress(
+ atob(
+ "ikswcBkmSpIC/ARGQKYQIDAwUEBxMAAQNAgECpMgAQMkB4IOIAQQLCgEQBwQaBgEBB1oCBBwYCCiRWDCIRWEO5wOHAX4CnA="
+ )
+ ),
+ require("heatshrink").decompress(
+ atob("ikswcBkmSpIC/ARNIKYIIEwEAggOKNIQODyAHCBxQsWB3TUFgMgA4sSBwzU/AVA=")
+ ),
+ require("heatshrink").decompress(
+ atob(
+ "ikswcBkmSpIC/AQ8gKggIBAwkCBw+QCIQLCgIRCDQcQBwwyDDwUSCgVAAwIOBEwI7EpI7FBw4FDghZGHwgOEF4Y+CEYQ+DBxQADNAIAFNAIOFa/4CoA="
+ )
+ ),
+ require("heatshrink").decompress(
+ atob(
+ "ikswcBkmSpIC/AQ8gKosSAwsBBw4aCoEAgQjEBoIpEBwtIBoIUEwEAggUDBwwyDDoWQA4ZWHhIIEJQoOCgI+EBwMQEAYOJO4oLBO4oRDJQrX/AU4"
+ )
+ ),
+ require("heatshrink").decompress(
+ atob(
+ "ikswcBkmSpIC/ARNIKgQIDwAGBgQOJNQYOCyAHDBxEggB6BBwYDBiVABxIjBCIIODF4YOEAAkBV40QBwxiDNAosEB0IC/AUg"
+ )
+ ),
+ require("heatshrink").decompress(
+ atob(
+ "ikswcBkmSpIC/AQ5UFkmQAwkCBxIdGCIIIDBxAsTgAaEkEASooOBiQOVJQgOBiBKDBxMSJQwRBLIgRCBwjX/AVA="
+ )
+ ),
+ require("heatshrink").decompress(
+ atob(
+ "ikswcBkmSpIC/ARGQKgYICAwcCBxADBiQdDkEANYoOGEAYyEHYoOIHYqfFBxIdDBAMQFgZHCBysSFgwRBO46GFa/4CnA"
+ )
+ ),
+ require("heatshrink").decompress(
+ atob(
+ "ikswcBkmSpIC/AQ5VGiAGFgIOIDQUgBwUCEYQOJGQYNBHAlADQgOHwEAggUDpANBCgYpBBwmQAwJiGhIjDB1gC/AU4A="
+ )
+ ),
+ require("heatshrink").decompress(
+ atob(
+ "ikswcBkmSpIC/AQ8gKYYICAwcEBxGQgAaDgVJgACBDQQOJgB6CBwcAiQODHa4AEhIRBpAHDiARBwAGCgIgCFIYOCFIYOHiQrEJQxlCBwzX/AVAA="
+ )
+ ),
+ require("heatshrink").decompress(
+ atob(
+ "ikswcBkmSpIC/AQ8gKggIBAwkCBw+QCIQLCgIRCDQcQBzkSTAsBHYoOIL4gOCMooOENAYOCoA4EBwoqDgiGGF4gOEa/4CoA="
+ )
+ ),
];
-digitPositions = [ // relative to the box
- {x:13, y:6}, {x:32, y:6},
- {x:74, y:6}, {x:93, y:6},
+digitPositions = [
+ // relative to the box
+ { x: 13, y: 6 },
+ { x: 32, y: 6 },
+ { x: 74, y: 6 },
+ { x: 93, y: 6 },
];
-var drawTimeout;
const animation_duration = 1; // seconds
-const animation_steps = 20;
+const animation_steps = 20;
const jump_height = 45; // top coordinate of the jump
const seconds_per_minute = 60;
-function draw() {
- const now = new Date();
- g.drawImage(background, 0, 0);
- var boxTL_x = 27; var boxTL_y = 29;
- var sprite_TL_x = 72; var sprite_TL_y = 161 - sprite.height;
- const seconds = now.getSeconds()%seconds_per_minute + now.getMilliseconds()/1000;
- const hours = now.getHours();
- const minutes = now.getMinutes();
-
- var time_advance = seconds / animation_duration;
-
- if (time_advance < 0.5) {
- sprite_TL_y += (jump_height - sprite_TL_y) * time_advance * 2;
- } else if (time_advance < 1) {
- sprite_TL_y = jump_height + (sprite_TL_y-jump_height) * (time_advance-0.5) * 2;
- }
- const box_penetration = boxTL_y + boxes.height - sprite_TL_y;
- if (box_penetration > 0) {
- boxTL_y -= box_penetration;
- }
- g.drawImage(boxes, boxTL_x, boxTL_y);
- g.drawImage(numbers[(hours / 10) >> 0], boxTL_x+digitPositions[0].x, boxTL_y+digitPositions[0].y);
- g.drawImage(numbers[(hours % 10) >> 0], boxTL_x+digitPositions[1].x, boxTL_y+digitPositions[1].y);
- g.drawImage(numbers[(minutes / 10) >> 0], boxTL_x+digitPositions[2].x, boxTL_y+digitPositions[2].y);
- g.drawImage(numbers[(minutes % 10) >> 0], boxTL_x+digitPositions[3].x, boxTL_y+digitPositions[3].y);
- g.drawImage(sprite, sprite_TL_x, sprite_TL_y);
- Bangle.drawWidgets();
-
- const timeout = time_advance <= 1?
- animation_duration / animation_steps
- : (seconds_per_minute - seconds);
- setTimeout( _=>{
- drawTimeout = undefined;
- draw();
- }, timeout * 1000);
-}
+const ClockFace = require("ClockFace");
+const clock = new ClockFace({
+ precision: 60, // just once a minute
-// Clear the screen once, at startup
-g.setTheme({bg:"#00f",fg:"#fff",dark:true}).clear();
+ init: function() {
+ // Clear the screen once, at startup
+ g.setTheme({ bg: "#00f", fg: "#fff", dark: true }).clear();
-Bangle.on('lcdPower',on=>{
- if (on) {
- draw(); // draw immediately, queue redraw
- } else { // stop draw timer
- if (drawTimeout) {
- clearTimeout(drawTimeout);
- }
- drawTimeout = undefined;
- }
+ this.drawing = true;
+
+ this.simpleDraw = function(now) {
+ var boxTL_x = 27;
+ var boxTL_y = 29;
+ var sprite_TL_x = 72;
+ var sprite_TL_y = 161 - sprite.height;
+ const seconds =
+ (now.getSeconds() % seconds_per_minute) + now.getMilliseconds() / 1000;
+ const hours =
+ this.is12Hour && now.getHours() > 12
+ ? now.getHours() - 12
+ : now.getHours();
+
+ const minutes = now.getMinutes();
+
+ g.drawImage(boxes, boxTL_x, boxTL_y);
+ g.drawImage(
+ numbers[(hours / 10) >> 0],
+ boxTL_x + digitPositions[0].x,
+ boxTL_y + digitPositions[0].y
+ );
+ g.drawImage(
+ numbers[hours % 10 >> 0],
+ boxTL_x + digitPositions[1].x,
+ boxTL_y + digitPositions[1].y
+ );
+ g.drawImage(
+ numbers[(minutes / 10) >> 0],
+ boxTL_x + digitPositions[2].x,
+ boxTL_y + digitPositions[2].y
+ );
+ g.drawImage(
+ numbers[minutes % 10 >> 0],
+ boxTL_x + digitPositions[3].x,
+ boxTL_y + digitPositions[3].y
+ );
+ };
+ },
+
+ pause: function() {
+ this.drawing = false;
+ },
+
+ resume: function() {
+ this.drawing = true;
+ },
+
+ draw: function(now) {
+ if (!this.drawing) {
+ this.simpleDraw(now);
+ return;
+ }
+ g.drawImage(background, 0, 0);
+ var boxTL_x = 27;
+ var boxTL_y = 29;
+ var sprite_TL_x = 72;
+ var sprite_TL_y = 161 - sprite.height;
+ const seconds =
+ (now.getSeconds() % seconds_per_minute) + now.getMilliseconds() / 1000;
+ const hours =
+ this.is12Hour && now.getHours() > 12
+ ? now.getHours() - 12
+ : now.getHours();
+
+ const minutes = now.getMinutes();
+
+ var time_advance = seconds / animation_duration;
+
+ if (time_advance < 0.5) {
+ sprite_TL_y += (jump_height - sprite_TL_y) * time_advance * 2;
+ } else if (time_advance < 1) {
+ sprite_TL_y =
+ jump_height + (sprite_TL_y - jump_height) * (time_advance - 0.5) * 2;
+ }
+ const box_penetration = boxTL_y + boxes.height - sprite_TL_y;
+ if (box_penetration > 0) {
+ boxTL_y -= box_penetration;
+ }
+ g.drawImage(boxes, boxTL_x, boxTL_y);
+ g.drawImage(
+ numbers[(hours / 10) >> 0],
+ boxTL_x + digitPositions[0].x,
+ boxTL_y + digitPositions[0].y
+ );
+ g.drawImage(
+ numbers[hours % 10 >> 0],
+ boxTL_x + digitPositions[1].x,
+ boxTL_y + digitPositions[1].y
+ );
+ g.drawImage(
+ numbers[(minutes / 10) >> 0],
+ boxTL_x + digitPositions[2].x,
+ boxTL_y + digitPositions[2].y
+ );
+ g.drawImage(
+ numbers[minutes % 10 >> 0],
+ boxTL_x + digitPositions[3].x,
+ boxTL_y + digitPositions[3].y
+ );
+ g.drawImage(sprite, sprite_TL_x, sprite_TL_y);
+ // Bangle.drawWidgets();
+
+ if (this.drawing) {
+ const timeout =
+ time_advance <= 1 ? animation_duration / animation_steps : -999;
+ if (timeout > 0) {
+ setTimeout((_) => {
+ this.draw(new Date());
+ }, timeout * 1000);
+ }
+ }
+ },
+
+ update: function(date, changed) {
+ if (this.drawing && changed.m) {
+ this.draw(date);
+ }
+ },
});
-// Show launcher when middle button pressed
-Bangle.setUI("clock");
-// Load widgets
-Bangle.loadWidgets();
-
-draw();
+clock.start();
diff --git a/apps/bowserWF/metadata.json b/apps/bowserWF/metadata.json
index 22df2dea4..bba15e5df 100644
--- a/apps/bowserWF/metadata.json
+++ b/apps/bowserWF/metadata.json
@@ -1,14 +1,18 @@
-{ "id": "bowserWF",
+{
+ "id": "bowserWF",
"name": "Bowser Watchface",
- "shortName":"Bowser Watchface",
- "version":"0.01",
+ "shortName": "Bowser Watchface",
+ "version": "0.03",
"description": "Let bowser show you the time",
"icon": "app.png",
- "tags": "",
- "supports" : ["BANGLEJS2"],
+ "type": "clock",
+ "tags": "clock",
+ "supports": ["BANGLEJS2"],
+ "allow_emulator": true,
"readme": "README.md",
"storage": [
- {"name":"bowserWF.app.js","url":"app.js"},
- {"name":"bowserWF.img","url":"app-icon.js","evaluate":true}
- ]
+ { "name": "bowserWF.app.js", "url": "app.js" },
+ { "name": "bowserWF.img", "url": "app-icon.js", "evaluate": true }
+ ],
+ "data": [{ "name": "bowserWF.json" }]
}
diff --git a/apps/bradbury/app-icon.js b/apps/bradbury/app-icon.js
new file mode 100644
index 000000000..07c4f5582
--- /dev/null
+++ b/apps/bradbury/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwcCkGSpEgwQCChICFkgCBgkQoMEyFJAoICByVBkgLBkkSpIaDEwWShEkFgcIBAIdCEYQCBAoQdBAoYsBC4Q7BpICBEYQCDF4Q7CEYYCCEYUSKYYUDyRlCJQQIBNYYvBMoQCBkgjBFgxxCL4REDFgaPEHYgmCIgosCNYZEEDoZ0CNwY7CIIYgDEYtB9+e/dg/4AB2EJkYEB/mC/fn33Ivvz598v4MB/0BgoRCyVHvmW7Mg2EA8uD/EAh/IkGP/8AgVLtkA5El+FJvoRBgmf4Mkh0HkEQo9kyEfkeQofsgf4kmPCIP+h/gwULkkCncEu/ZsmRI4cEv0H8ESpdgEwMjwXI9kTCIOANYkSEYOCncF+UAjuR/ED+FBg/3/f8RgNgiVPkYdBtkT/Egv0Il+AoMfI4PgyX7vkW799F4Nl//4//woH/+0Ztvx7Fs335sk//5EB/IRBhACB77CBpEkgEIgGQoDRBgEggVBgDdBgGAgPv317ku+5cj334t+OSoI+B8gCBtlx7dkuFfgvx4N8yPbvgOB8ACBR4MA9mf4Egz3IgeChEDwDOBx/AjuCoN8y/JgkX4ME2FBjuQn65BgMtwELkGOEYOO4Mh2EJh+Sh/jOIMd+3fskRcwMTEwOWo98gCSBwFJkm2pfgx3II4PBk++/aABhEfwEInpZBvkX7MkJQMl2FHfANBjgCBlmQhHsgwjB33IkeyBAOChMcEwM9+/ZsBHBboMJtv2hd9+FHZANBVoM7kGC/fv2FJ9+GEYOAh//+UIaIMBkkQpEAHwIIBoMgiFJBANJEAMIkGShEkwQIChIIBhIIBhIaCkmQpIFCgmSEwYpDEYwCCpAICBwUEiQdFEwIICyAIDHwQ7CEYYpCEYWSpA7FDocSEwojBCgIaDIgYCBNwR0BNYYjFEwZTDLgQjGOgYvBEYQ7ENYlJFgQCCDohuGTYpBFkhoCSoQICEYIA="))
diff --git a/apps/bradbury/app.js b/apps/bradbury/app.js
new file mode 100644
index 000000000..147242689
--- /dev/null
+++ b/apps/bradbury/app.js
@@ -0,0 +1,115 @@
+require("Font7x11Numeric7Seg").add(Graphics);
+require("Font5x9Numeric7Seg").add(Graphics);
+require("Font8x12").add(Graphics);
+require("FontDylex7x13").add(Graphics);
+const X = 98, Y = 46;
+var wizible = 0;
+
+function getImg() {
+ return require("heatshrink").decompress(atob("2GwwcCAoNBgmQpMkiACCoMkyALBAoMEyQCDkkSAoICCCIIXCCgQaCAQNJDQYUBDQIIBDQkgIwsShEkwUJkGSpACBBAQFCAQOCBAgFCyQXBDQQIDCIUIEYgOCpICBFgYXCII2W7ft237AQPbt++7fvBAIFBAQgRCAoNtCgIaDC4QaD7dtBAgUBDQYXC9+z5cEIIv279t+/fvoFBvoFC+/bBAe3CIQIBBwW2CIgCCCIYgFAQgjEAoN5sGAIAcF33JaIT4DAoTyDcYL1CAQgXBpIUDfYQCCC4T+CDoYgDDQQFBocMyBBDkeyagb4EBArpCpD1DfAoIDfAQLCDQwIGDQklwRBDLIJfEiffjv//H/AAPkgUB//+oEk+Pf/+B5/8+VHn/wh9/+ff/vx4f/+0f/+yFIIsCPoSMCWYSVDoBBCL48A8kQvEn+VA/i2Bn/yoMgh0BkvwhMFx0Bg6nBhkQh8Ej14h4XB9kSU4iSFQwMkGoWWhCDDagRQCiUPgED5Ej+EI/kAhF8+0BkH+LIOyCIOT4EAF4M8gVfgGG5EhRgNggFJGoIpBJQKJDAQQLBRgSDEKYRZCoNnOIXypaJBgPkjzFBn1AkeQv8/FgMcwFBnkArNsv/Bkl+v4vBVQQCBPQKzBRgoCCjEgIILOCKwQFBo8gwf4kf//50Bp/kz/IgEyhEj+UPskPPQOAoE8yE7GQInBx/4R4R6DfwQCGBYQCBIITOCagJNBo/gyEZg/wAoMCv5GB/MnR4KDBvEEi0IhkCQYMQp8Aj0BFgPBgECOgT+BQwJ9EAoQCEwEAJobOBQwM/kFx5E/+UH8mCv8gyf5kHygHlwVLklz5EjSQM8wP/BwPJkGX7IsBVQL7CO4SJFAoK5BBAUAI4SDDwfP9+eoH//0P/+f//gj//8OOvcsgV5/6JBgm+nNkgPH8kev8ETQMANALOBO4QFBPoa2CR4qDByBKCagTPBwAOBgEIVQJTBTAIIDDQIRBgMggQIBiQaGfAwCLRgWQoDUEpCeDgCSDAQLyCoEAAQIdCHIIjEAoS/BDQbsCyQCNX4dIJQaGCDRwCnRIbUDyTRBIOy8BQYTLDBYL7BAGUEIgI+CQYeCpMgIGYABiSDByUIkkSI4JKBgEB/4AW4/j+PHILECXgKDDpCDCgF+QehBBgDCBkkQI4VIgeAY2q/ByCDBySDCIPKDCgkQoKDCjg4tgcMmHDgACCQYuChMkQYJBujFhwwCBwQICpEEySDDAoKD1oaDDiSDDAQNIDI1z588+YCiQYoCBkCDCwSDCI4KDHgeevPnAUeAQYkQgaDDyFBkjIBkiDHjiAjAQXwQYwxBHAI+CiFBZYKDGg+f/4Ak+CDEAQSDCHwMgZAKDKIMyDEwCDDgg+BIgUkQYJBFQd0DQY9IAQSD1sCDDAQKDCySDvgKDEAQKDCySDChKDygKDCAQMgQYcIgkSI4MSQd8DQYkDQYcSYQJEBAQKDwkCDHgA+CiDLCQeCADmFDQYsgQAICCQd8hw0AsOCgFgQZESQeEMQZbCBQeR6BjFhw0YQYlIgmQoKDFgOwQdnBQYPDQYY+BkmChICBIIcP+yDqQAQCBgEgQYMEYQKDDAoKDxQAKDFiFBkCDBAQJBDAASDpgOCQwdgQYMAwUIkhEBkkSIIccuHAQd1DQY5EBQYnjx04QdMAgUAsOCgCDDyUIIgUEQYtxQdSABAQiDGhICBQeEhw0YsOAhCDCgmSAQOQoMkQeMMmHDQYICBQYQ+BQZUYQdAuCAAo4BHYMkZAKDGmKDriCDHiQ+ByUIQYvggU4QdEYsOGAQdgQYhEBI4SDEgKDrQASDFYQWCQY8AQejCBkmQoMEySD8kBEBQY0GjCD4kkSIIcEuKD0ySDJ8OOQdkIQYw7BZAUEQYkcgKDsoaDGYQKABQY3jwSDqgEGQwOAQYcEQYTIBAQKDyoKDGYQKABpKDF8EOhCDpsOGgACCQYkIkkQpMkiSDwgiACQYuQoMkyUIQY0AjCDnwCDCAQOAhCDCgCDEgiDFuPAgaDl/kAgcMmFDQY0QoKABhKDFsOOAgQAmQYsAQYUEyQCBIgKDFAFcDQAKDC4CDDyCDCpKDFaAIHBAT+Dx048YCDhCDBQAICCQYQ7BQYaJBIAIHDAQM/AokEj3x/mf/IIDz3JgEQv8/+VxtmX+MEj/BnkAuPAglx4cMQYYxBmAFBQYQkBkmSLoSDIn+DxBrDHwP48R0E+0OAoP6k+D/N/33YlAUCQYMIgEOgHgh0YsEGjCGBAoMArA2BQYMSI4KDJnnz4IIDjlx4mwqIID//P4EQp8Hifx4/y56DB6N8QYQaBAQNwQYUBAQPDQY8JkiDKwSDEyV5tGX5AIEh9gwX+QYPJ8+f/CYB5MgQYM48ICB8eOgEBw0AQYMIQYR9BhBBBQZYCRgAOLQYPHQYICBQYMMmFDQY0SoJpBpACCQYQAsQAMYsACCQYMAQYWQI4VJIN4AGgQ7ByCDFIPHIgA+BgmSoMkiFJkBB1iVAgkQQYTIByVJkmAIGcEy1IHYKDBIgKGDIgQCxkuSQYMSQYOShCGBJQQ1m5cs2QCKwUBkjCCiFJQwYC1gBBBAoJWBQYQCBf9gAIgVIYQRECJoKeBJQQFCyALBAQMkiSVBboICDNAgUDDQQOCBAQXFyQRDBAICBDQdBQAMJZYICCQwOSpCPEpICBLIIFBCIIFCDRwCFDQp6BCI5HEOgiJDBAJ0ETAaPFCIgaGSo4IDGpKDCOIZTDBAJWCOgRxCboQCBCgQCCBwICDOgqPCBAqeDCgoODdhDdBBYqPIBwSPDDQgCDfAQCCSQgyDEASJDQYLODOgZfBOIRWFL4TjERIYdDTAYOEBAICEC4o4FBwLaGAXNAgBuFAXMAAH4A/AAcB23btoC84ENIP/Yj/+7Ml/4AG9u27//+3/CgIJB/3bt4EBEAe3DAn2BAO/DoQJCGof/HYgXD/3JlpBDpdsz5CHAF/yrdk//4hvy/9kwf7v5lCTA9vQAJxB/YLFPoRxETYTCSt+WTAOeQYOX7cki1/QWv833bsmRQYOW7Mg31f9rvB/pWCCoR6HNBF9NYQXGRJAOCCQXbtm+5MkgX4jgFBgvy5//wEAAB8PQcPl2VIgG2vEM21Il+W5/8ICAABNYKYEcIf+SoKABBYI4Gtu/CgaMCsmSgNv+VYjmyoN83xBTgKDh8mArf8y14huShfl21bthBS/ZlCt59Bt7vBtowFBYIRCtoFBv4FBBYSGC9kW/8n2XYj+Qr8t+1/Qev83/Jtuz/EN+X5v+z/d8IKqGCAQO///9PQW274FE//tAoYCEDoNvyV/vueQYP+pdsz5NBQen/+Vbsn/QYO27MlcAWAIKEGdgP2dgSGCBAIABPQoOBAoO3SoftAQKbE5Mt2yDBNUQAc/BBBMoXfBALXCAQLyF27sIEIYRCAgJ3CEwQLDv6wBRIIADEAYFDQf6DChpuGAQ++BZP/C5VvOggFCCgV/Rgi5GQf6DDIIP7gAAUgz+C//t2APIn/tO4WABxEbPoKPDtu/IIX4IKsBMImDNQ/+o4EC/ixI/0HQZENZAJBVgBfBcwNsgVbfwQCD2VAAoVgiQLEBwcAAoX9BYfYQbv8CBQOC8AONQYxBXQYRiBthHB8FwBQNwg4CBgF/IJtvBwPtQccB4EcIIcA8eAQbEf+3YILXsIIkDx0AjgQBOIVgD5R9B//9AQKDE/5BVh6DFYoZBFQa4oCd4TjCKAPf9u3AoTaC74ZD+3bYorCCMoKJBGQP9DoJBLGQQjCtrFCJY4AUIId//EcZYSDZhrLDOgP+PQSJDBAtt34IBAoX/7dsGRZxBsAOKEwaVBAoPYQbx0NQakfZYRNBt4LDAon+R4qDDBwPbvkSpMkyQCEOgSYBIJanDHYZBBA4IWKABUPQYk/RhKDYAQJBVgHbv6DBtiDHkB0EsCDLUgNtH4IFB7BBYgJ6GOhaDW7BBXMoVsGRe275BLQYgCBQf6DDh6DXgHf/qDNtu3IJiDCt/27f//yD/QYUNZAKDW7d9MoJBLQaP/2xBDQf5BCZAJBW/Z0C9gQK/p0BsAOKt49C+3bRIPYQf4+BhpEBIKoiBOgVsB5ZxBQZYdCUgTFDQf/4jqDYMQP+QZjyBQZlt34+C/aGBQYX/ICsPQakJkmSpICEoCDIhrOCJQQFB/4ICAQ9/DAIFFIKATCAAnyQYIjC+3fGoPYQYQAaQbGQBwaDFIIKADt//AQQIDboYODIKQdB75BBBxW/F4iDwBxiDHO4T7EKAYCEQwgICQaH/sAFByUAYIMBkgOFSoRBE/4lKABUPQauAoEggEIAoKDM/BBVgCGCQZpBGkmAIIJQDUgSqDILMBQarFBQYYOFQYsNIgJBYMQNsQZaSBYpd//6AB/wCBYrSDWYoWQgMkQZcf/3YIKsAcYSDM/oOBsBQL+3bv6DC23YQb0CrZHCAQeyoCDDXoSSKQYsN3//IKsGHAd/wYoH/1HQYVsiVJkmSAQsDto4BLgiDCADnwKJE/BwaDJBwiDFAYJcCvoCBa4PfbQRrBO4IFBL4P7XgwdBt6ADBAQLDCgwCB9oFDF4KDjAEKDCKwwCFCIIFDSoQOD2//NYJoBAoYRHQwaeB3//96PGDoP/Qf6DCh50DMoOAgAAPgz7E356Dt4oCSQynE+wLCRIP//YXDQYfZkoGB/hAQgEBP8X+5MvQYMf/1Ltme7dsIKhxENAJuBPoKMFAQe/RIdv/wIDtvyrdk+yDB+X/t+zQe+X/ckj/4huXLIVbQaUAfAv//r+ER4r7BO4O2SQJ9B94IDXIO+7Mg2f4juSpMkyVfQevs23Jgvy/EcwAtChZBSQYR3FQAT1CBY6YE7f9BYll+1Il+WrEcQYdP/5HDABsPQcPl2VBvm2vEcBQfLMRO/cwZrFdgPbt/2CgXfOIttE4Pt/4dBSQv/74NBQYOShfl+1YjqDDr5vhACfsyFfluy/EfyxQB+1/bQRiCO4Vt3x9DMoVvBwW2eQRrBOIZ6BOIIXCBwYpBv4aBSoIIDtmC/Nv2XYgfy/d/2aC1AAOX5f9z/AgO+pdszzmEAQT1BeQZrDO4qGCDQ4CJCoILI+Vbsn/4EAv/ZkqC3cAPJl/+gEAh5oH350DeobjD76GCBYgFB/4FCDQIdCBYNvTAQFBv4RDAQ3/+BBBgE/QXAAC/g/BA="));
+}
+
+function draw() {
+ var d = new Date();
+ var h = d.getHours() % 12 || 12, m = d.getMinutes(), yyyy = d.getFullYear(), mm = d.getMonth(), dd = d.getDate();
+ var time = (""+h).substr(-2) + ":" + ("0"+m).substr(-2);
+ g.reset(); // Reset the state of the graphics library
+ g.clear();
+ g.drawImage(getImg()); //load bg image
+ //TIME
+ g.setFont("7x11Numeric7Seg",2);
+ g.setFontAlign(1,1);
+ g.setColor(0,0,1);
+ g.drawString(time, 97, 53, false /*clear background*/);
+ g.setColor(0,0,0);
+ g.drawString(time, 96, 52, false /*clear background*/);
+ //SECONDS
+ g.setFont("7x11Numeric7Seg",1);
+ //g.setFont("5x9Numeric7Seg");
+ g.setFontAlign(-1,1); // align right bottom
+ g.setColor(0,0,1);
+ g.drawString(("0"+d.getSeconds()).substr(-2), 100, 42, 0);
+ g.setColor(0,0,0);
+ g.drawString(("0"+d.getSeconds()).substr(-2), 99, 41, 0);
+ //DATE
+ g.setFont("5x9Numeric7Seg",1);
+ g.setFontAlign(1,1);
+ g.setColor(0,0,1);
+ g.drawString(yyyy+" "+("0"+mm)+" "+dd, 100, 65, 0);
+ g.setColor(0,0,0);
+ g.drawString(yyyy+" "+("0"+mm)+" "+dd, 99, 64, 0);
+ //BATTERY
+ g.setColor(0,0,1);
+ g.drawString(E.getBattery(), 137, 53, 0);
+ g.setColor(0,0,0);
+ g.drawString(E.getBattery(), 136, 52, 0);
+ //STEPS
+ g.setColor(0,0,1);
+ g.drawString(Bangle.getHealthStatus("day").steps, 137, 65, 0);
+ g.setColor(0,0,0);
+ g.drawString(Bangle.getHealthStatus("day").steps, 136, 64, 0);
+ //WEEK DAY
+ g.setFont("8x12");
+ g.setColor(0,0,1);
+ if (d.getDay()==0) {
+ g.drawString("SU", 137, 43, 0);
+ g.setColor(0,0,0);
+ g.drawString("SU", 136, 42, 0);
+ } else if (d.getDay()==1) {
+ g.drawString("MO", 137, 43, 0);
+ g.setColor(0,0,0);
+ g.drawString("MO", 136, 42, 0);
+ } else if (d.getDay()==2) {
+ g.drawString("TU", 137, 43, 0);
+ g.setColor(0,0,0);
+ g.drawString("TU", 136, 42, 0);
+ } else if (d.getDay()==3) {
+ g.drawString("WE", 137, 43, 0);
+ g.setColor(0,0,0);
+ g.drawString("WE", 136, 42, 0);
+ } else if (d.getDay()==4) {
+ g.setFont("Dylex7x13");
+ g.drawString("TH", 137, 43, 0);
+ g.setColor(0,0,0);
+ g.drawString("TH", 136, 42, 0);
+ } else if (d.getDay()==5) {
+ g.drawString("FR", 137, 43, 0);
+ g.setColor(0,0,0);
+ g.drawString("FR", 136, 42, 0);
+ } else {
+ g.drawString("SA", 137, 43, 0);
+ g.setColor(0,0,0);
+ g.drawString("SA", 136, 42, 0);
+ }
+ if(wizible==1){
+ Bangle.drawWidgets();
+ }
+}
+
+// Clear the screen once, at startup
+g.clear();
+// draw immediately at first
+draw();
+var secondInterval = setInterval(draw, 1000);
+// Stop updates when LCD is off, restart when on
+Bangle.on('lcdPower',on=>{
+ if (secondInterval) clearInterval(secondInterval);
+ secondInterval = undefined;
+ if (on) {
+ secondInterval = setInterval(draw, 1000);
+ draw(); // draw immediately
+ }
+});
+// Show launcher when middle button pressed
+Bangle.setUI("clock");
+
+//Toggle Widgets
+Bangle.loadWidgets();
+Bangle.on('touch', function(button) {
+ if(wizible==0){
+ wizible=1;
+ }
+ else if(wizible==1){
+ wizible=0;
+ }
+});
diff --git a/apps/bradbury/app.png b/apps/bradbury/app.png
new file mode 100644
index 000000000..f7141d15e
Binary files /dev/null and b/apps/bradbury/app.png differ
diff --git a/apps/bradbury/metadata.json b/apps/bradbury/metadata.json
new file mode 100644
index 000000000..456daa381
--- /dev/null
+++ b/apps/bradbury/metadata.json
@@ -0,0 +1,14 @@
+{ "id": "bradbury",
+ "name": "Bradbury Watch",
+ "shortName":"Bradbury",
+ "icon": "app.png",
+ "screenshots": [{"url":"screenshot.png"}],
+ "version":"0.01",
+ "description": "A watch face based on the classic Seiko model worn by one of my favorite authors. I didn't follow the original lcd layout exactly, opting for larger font for more easily readable time, and adding date, battery level, and step count; read from the device. Tapping the screen toggles visibility of widgets.",
+ "type": "clock",
+ "supports":["BANGLEJS2"],
+ "storage": [
+ {"name":"bradbury.app.js","url":"app.js"},
+ {"name":"bradbury.img","url":"app-icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/bradbury/screenshot.png b/apps/bradbury/screenshot.png
new file mode 100644
index 000000000..914266668
Binary files /dev/null and b/apps/bradbury/screenshot.png differ
diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog
index 41eec666a..57f0ecf3d 100644
--- a/apps/bthrm/ChangeLog
+++ b/apps/bthrm/ChangeLog
@@ -21,3 +21,11 @@
Adds some preset modes and a custom one
Restructure the settings menu
0.08: Allow scanning for devices in settings
+0.09: Misc Fixes and improvements (https://github.com/espruino/BangleApps/pull/1655)
+0.10: Use default Bangle formatter for booleans
+0.11: App now shows status info while connecting
+ Fixes to allow cached BluetoothRemoteGATTCharacteristic to work with 2v14.14 onwards (>1 central)
+0.12: Fix HRM fallback handling
+ Use default boolean formatter in custom menu and directly apply config if useful
+ Allow recording unmodified internal HR
+ Better connection retry handling
diff --git a/apps/bthrm/README.md b/apps/bthrm/README.md
index 42ad619bd..f4eaf43af 100644
--- a/apps/bthrm/README.md
+++ b/apps/bthrm/README.md
@@ -2,7 +2,7 @@
When this app is installed it overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.
-HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM'` event as if it came from the on board monitor.
+HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM')` event as if it came from the on board monitor.
This means it's compatible with many Bangle.js apps including:
@@ -16,21 +16,36 @@ as that requires live sensor data (rather than just BPM readings).
Just install the app, then install an app that uses the heart rate monitor.
-Once installed it'll automatically try and connect to the first bluetooth
-heart rate monitor it finds.
+Once installed you will have to go into this app's settings while your heart rate monitor
+ is available for bluetooth pairing and scan for devices.
-**To disable this and return to normal HRM, uninstall the app**
+**To disable this and return to normal HRM, uninstall the app or change the settings**
+
+### Modes
+
+* Off - Internal HRM is used, no attempt on connecting to BT HRM.
+* Default - Replaces internal HRM with BT HRM and falls back to internal HRM if no valid measurements received.
+* Both - The BT HRM needs to be started explicitly by an app that wants to use it. BT HRM has its own event and is completely separated from the internal HRM. Apps not supporting the BT HRM will not see the BT HRM measurements.
+* Custom - Combine low level settings as you see fit.
## Compatible Heart Rate Monitors
This works with any heart rate monitor providing the standard Bluetooth
-Heart Rate Service (`180D`) and characteristic (`2A37`).
+Heart Rate Service (`180D`) and characteristic (`2A37`). It additionally supports
+the location (`2A38`) characteristic and the Battery Service (`180F`), reporting
+that information in the `BTHRM` event when they are available.
So far it has been tested on:
* CooSpo Bluetooth Heart Rate Monitor
+* Polar H10
+* Polar OH1
* Wahoo TICKR X 2
+## Recorder plugin
+
+The recorder plugin can record the BT HRM event (blue) and the original unchanged HRM event (green). This is mainly useful for debugging purposes or comparing the BT with the internal HRM, as the resulting "merged" HRM can be recordet using the default HRM recorder.
+
## Internals
This replaces `Bangle.setHRMPower` with its own implementation.
@@ -38,7 +53,6 @@ This replaces `Bangle.setHRMPower` with its own implementation.
## TODO
* A widget to show connection state?
-* Specify a specific device by address?
## Creator
diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js
index 3a1f1cc4c..aa97d83b7 100644
--- a/apps/bthrm/boot.js
+++ b/apps/bthrm/boot.js
@@ -3,49 +3,48 @@
require('Storage').readJSON("bthrm.default.json", true) || {},
require('Storage').readJSON("bthrm.json", true) || {}
);
-
+
var log = function(text, param){
+ if (global.showStatusInfo)
+ showStatusInfo(text);
if (settings.debuglog){
var logline = new Date().toISOString() + " - " + text;
- if (param){
- logline += " " + JSON.stringify(param);
- }
+ if (param) logline += ": " + JSON.stringify(param);
print(logline);
}
};
-
+
log("Settings: ", settings);
-
+
if (settings.enabled){
- function clearCache(){
+ var clearCache = function() {
return require('Storage').erase("bthrm.cache.json");
- }
+ };
- function getCache(){
+ var getCache = function() {
var cache = require('Storage').readJSON("bthrm.cache.json", true) || {};
- if (settings.btname && settings.btname == cache.name) return cache;
+ if (settings.btid && settings.btid === cache.id) return cache;
clearCache();
return {};
- }
-
- function addNotificationHandler(characteristic){
- log("Setting notification handler: " + supportedCharacteristics[characteristic.uuid].handler);
- characteristic.on('characteristicvaluechanged', supportedCharacteristics[characteristic.uuid].handler);
- }
-
- function writeCache(cache){
+ };
+
+ var addNotificationHandler = function(characteristic) {
+ log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/);
+ characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value));
+ };
+
+ var writeCache = function(cache) {
var oldCache = getCache();
- if (oldCache != cache) {
+ if (oldCache !== cache) {
log("Writing cache");
- require('Storage').writeJSON("bthrm.cache.json", cache)
+ require('Storage').writeJSON("bthrm.cache.json", cache);
} else {
log("No changes, don't write cache");
}
-
- }
+ };
- function characteristicsToCache(characteristics){
+ var characteristicsToCache = function(characteristics) {
log("Cache characteristics");
var cache = getCache();
if (!cache.characteristics) cache.characteristics = {};
@@ -60,9 +59,10 @@
};
}
writeCache(cache);
- }
+ };
- function characteristicsFromCache(){
+ var characteristicsFromCache = function(device) {
+ var service = { device : device }; // fake a BluetoothRemoteGATTService
log("Read cached characteristics");
var cache = getCache();
if (!cache.characteristics) return [];
@@ -76,43 +76,51 @@
r.properties = {};
r.properties.notify = cached.notify;
r.properties.read = cached.read;
+ r.service = service;
addNotificationHandler(r);
log("Restored characteristic: ", r);
restored.push(r);
}
return restored;
- }
+ };
log("Start");
var lastReceivedData={
};
- var serviceFilters = [{
- services: [ "180d" ]
- }];
-
- supportedServices = [
- "0x180d", "0x180f"
+ var supportedServices = [
+ "0x180d", // Heart Rate
+ "0x180f", // Battery
];
+ var bpmTimeout;
+
var supportedCharacteristics = {
"0x2a37": {
//Heart rate measurement
- handler: function (event){
- var dv = event.target.value;
+ active: false,
+ handler: function (dv){
var flags = dv.getUint8(0);
-
+
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
-
+ supportedCharacteristics["0x2a37"].active = bpm > 0;
+ log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active);
+ if (supportedCharacteristics["0x2a37"].active) stopFallback();
+ if (bpmTimeout) clearTimeout(bpmTimeout);
+ bpmTimeout = setTimeout(()=>{
+ supportedCharacteristics["0x2a37"].active = false;
+ startFallback();
+ }, 3000);
+
var sensorContact;
-
+
if (flags & 2){
- sensorContact = (flags & 4) ? true : false;
+ sensorContact = !!(flags & 4);
}
-
+
var idx = 2 + (flags&1);
-
+
var energyExpended;
if (flags & 8){
energyExpended = dv.getUint16(idx,1);
@@ -121,11 +129,11 @@
var interval;
if (flags & 16) {
interval = [];
- maxIntervalBytes = (dv.byteLength - idx);
+ var maxIntervalBytes = (dv.byteLength - idx);
log("Found " + (maxIntervalBytes / 2) + " rr data fields");
for(var i = 0 ; i < maxIntervalBytes / 2; i++){
interval[i] = dv.getUint16(idx,1); // in milliseconds
- idx += 2
+ idx += 2;
}
}
@@ -140,45 +148,44 @@
}
if (settings.replace){
- var newEvent = {
+ var repEvent = {
bpm: bpm,
confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
src: "bthrm"
};
-
- log("Emitting HRM: ", newEvent);
- Bangle.emit("HRM", newEvent);
+
+ log("Emitting HRM", repEvent);
+ Bangle.emit("HRM_int", repEvent);
}
var newEvent = {
bpm: bpm
};
-
+
if (location) newEvent.location = location;
if (interval) newEvent.rr = interval;
if (energyExpended) newEvent.energy = energyExpended;
if (battery) newEvent.battery = battery;
if (sensorContact) newEvent.contact = sensorContact;
-
- log("Emitting BTHRM: ", newEvent);
+
+ log("Emitting BTHRM", newEvent);
Bangle.emit("BTHRM", newEvent);
}
},
"0x2a38": {
//Body sensor location
- handler: function(data){
+ handler: function(dv){
if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {};
- if (!lastReceivedData["0x180d"]["0x2a38"]) lastReceivedData["0x180d"]["0x2a38"] = data.target.value;
+ lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10);
}
},
"0x2a19": {
//Battery
- handler: function (event){
+ handler: function (dv){
if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {};
- if (!lastReceivedData["0x180f"]["0x2a19"]) lastReceivedData["0x180f"]["0x2a19"] = event.target.value.getUint8(0);
+ lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0);
}
}
-
};
var device;
@@ -195,7 +202,7 @@
maxInterval: 1500
};
- function waitingPromise(timeout) {
+ var waitingPromise = function(timeout) {
return new Promise(function(resolve){
log("Start waiting for " + timeout);
setTimeout(()=>{
@@ -203,9 +210,13 @@
resolve();
}, timeout);
});
- }
+ };
if (settings.enabled){
+ Bangle.isBTHRMActive = function (){
+ return supportedCharacteristics["0x2a37"].active;
+ };
+
Bangle.isBTHRMOn = function(){
return (Bangle._PWR && Bangle._PWR.BTHRM && Bangle._PWR.BTHRM.length > 0);
};
@@ -215,36 +226,39 @@
};
}
-
if (settings.replace){
- var origIsHRMOn = Bangle.isHRMOn;
+ Bangle.origIsHRMOn = Bangle.isHRMOn;
Bangle.isHRMOn = function() {
if (settings.enabled && !settings.replace){
- return origIsHRMOn();
+ return Bangle.origIsHRMOn();
} else if (settings.enabled && settings.replace){
return Bangle.isBTHRMOn();
}
- return origIsHRMOn() || Bangle.isBTHRMOn();
+ return Bangle.origIsHRMOn() || Bangle.isBTHRMOn();
};
}
- function clearRetryTimeout(){
+ var clearRetryTimeout = function(resetTime) {
if (currentRetryTimeout){
log("Clearing timeout " + currentRetryTimeout);
clearTimeout(currentRetryTimeout);
currentRetryTimeout = undefined;
}
- }
+ if (resetTime) {
+ log("Resetting retry time");
+ retryTime = initialRetryTime;
+ }
+ };
- function retry(){
+ var retry = function() {
log("Retry");
if (!currentRetryTimeout){
var clampedTime = retryTime < 100 ? 100 : retryTime;
-
- log("Set timeout for retry as " + clampedTime);
+
+ log("Set timeout for retry as " + clampedTime);
clearRetryTimeout();
currentRetryTimeout = setTimeout(() => {
log("Retrying");
@@ -252,23 +266,23 @@
initBt();
}, clampedTime);
- retryTime = Math.pow(retryTime, 1.1);
+ retryTime = Math.pow(clampedTime, 1.1);
if (retryTime > maxRetryTime){
retryTime = maxRetryTime;
}
} else {
log("Already in retry...");
}
- }
+ };
var buzzing = false;
- function onDisconnect(reason) {
+ var onDisconnect = function(reason) {
log("Disconnect: " + reason);
- log("GATT: ", gatt);
- log("Characteristics: ", characteristics);
- retryTime = initialRetryTime;
- clearRetryTimeout();
- switchInternalHrm();
+ log("GATT", gatt);
+ log("Characteristics", characteristics);
+ clearRetryTimeout(reason != "Connection Timeout");
+ supportedCharacteristics["0x2a37"].active = false;
+ startFallback();
blockInit = false;
if (settings.warnDisconnect && !buzzing){
buzzing = true;
@@ -277,76 +291,78 @@
if (Bangle.isBTHRMOn()){
retry();
}
- }
+ };
- function createCharacteristicPromise(newCharacteristic){
- log("Create characteristic promise: ", newCharacteristic);
+ var createCharacteristicPromise = function(newCharacteristic) {
+ log("Create characteristic promise", newCharacteristic);
var result = Promise.resolve();
+ // For values that can be read, go ahead and read them, even if we might be notified in the future
+ // Allows for getting initial state of infrequently updating characteristics, like battery
+ if (newCharacteristic.readValue){
+ result = result.then(()=>{
+ log("Reading data", newCharacteristic);
+ return newCharacteristic.readValue().then((data)=>{
+ if (supportedCharacteristics[newCharacteristic.uuid] && supportedCharacteristics[newCharacteristic.uuid].handler) {
+ supportedCharacteristics[newCharacteristic.uuid].handler(data);
+ }
+ });
+ });
+ }
if (newCharacteristic.properties.notify){
result = result.then(()=>{
- log("Starting notifications for: ", newCharacteristic);
- var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started for ", newCharacteristic));
+ log("Starting notifications", newCharacteristic);
+ var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic));
if (settings.gracePeriodNotification > 0){
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
startPromise = startPromise.then(()=>{
log("Wait after connect");
- waitingPromise(settings.gracePeriodNotification)
+ return waitingPromise(settings.gracePeriodNotification);
});
}
return startPromise;
});
- } else if (newCharacteristic.read){
- result = result.then(()=>{
- readData(newCharacteristic);
- log("Reading data for " + newCharacteristic);
- return newCharacteristic.read().then((data)=>{
- supportedCharacteristics[newCharacteristic.uuid].handler(data);
- });
- });
}
- return result.then(()=>log("Handled characteristic: ", newCharacteristic));
- }
-
- function attachCharacteristicPromise(promise, characteristic){
+ return result.then(()=>log("Handled characteristic", newCharacteristic));
+ };
+
+ var attachCharacteristicPromise = function(promise, characteristic) {
return promise.then(()=>{
log("Handling characteristic:", characteristic);
return createCharacteristicPromise(characteristic);
});
- }
-
- function createCharacteristicsPromise(newCharacteristics){
- log("Create characteristics promise: ", newCharacteristics);
+ };
+
+ var createCharacteristicsPromise = function(newCharacteristics) {
+ log("Create characteristics promis ", newCharacteristics);
var result = Promise.resolve();
for (var c of newCharacteristics){
if (!supportedCharacteristics[c.uuid]) continue;
- log("Supporting characteristic: ", c);
+ log("Supporting characteristic", c);
characteristics.push(c);
if (c.properties.notify){
addNotificationHandler(c);
}
-
+
result = attachCharacteristicPromise(result, c);
}
return result.then(()=>log("Handled characteristics"));
- }
-
- function createServicePromise(service){
- log("Create service promise: ", service);
+ };
+
+ var createServicePromise = function(service) {
+ log("Create service promise", service);
var result = Promise.resolve();
result = result.then(()=>{
- log("Handling service: " + service.uuid);
+ log("Handling service" + service.uuid);
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
});
return result.then(()=>log("Handled service" + service.uuid));
- }
-
- function attachServicePromise(promise, service){
- return promise.then(()=>createServicePromise(service));
- }
-
- var reUseCounter = 0;
+ };
- function initBt() {
+ var attachServicePromise = function(promise, service) {
+ return promise.then(()=>createServicePromise(service));
+ };
+
+ var initBt = function () {
log("initBt with blockInit: " + blockInit);
if (blockInit){
retry();
@@ -355,66 +371,64 @@
blockInit = true;
- if (reUseCounter > 10){
- log("Reuse counter to high");
- gatt=undefined;
- reUseCounter = 0;
- }
-
var promise;
-
+ var filters;
+
if (!device){
- var filters = serviceFilters;
- if (settings.btname){
- log("Configured device name", settings.btname);
- filters = [{name: settings.btname}];
+ if (settings.btid){
+ log("Configured device id", settings.btid);
+ filters = [{ id: settings.btid }];
+ } else {
+ return;
}
log("Requesting device with filters", filters);
- promise = NRF.requestDevice({ filters: filters });
-
+ promise = NRF.requestDevice({ filters: filters, active: true });
+
if (settings.gracePeriodRequest){
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
}
-
+
promise = promise.then((d)=>{
- log("Got device: ", d);
+ log("Got device", d);
d.on('gattserverdisconnected', onDisconnect);
device = d;
});
-
+
promise = promise.then(()=>{
log("Wait after request");
return waitingPromise(settings.gracePeriodRequest);
});
-
} else {
promise = Promise.resolve();
- log("Reuse device: ", device);
+ log("Reuse device", device);
}
-
+
promise = promise.then(()=>{
if (gatt){
- log("Reuse GATT: ", gatt);
+ log("Reuse GATT", gatt);
} else {
- log("GATT is new: ", gatt);
+ log("GATT is new", gatt);
characteristics = [];
- var cachedName = getCache().name;
- if (device.name != cachedName){
- log("Device name changed from " + cachedName + " to " + device.name + ", clearing cache");
+ var cachedId = getCache().id;
+ if (device.id !== cachedId){
+ log("Device ID changed from " + cachedId + " to " + device.id + ", clearing cache");
clearCache();
}
var newCache = getCache();
- newCache.name = device.name;
+ newCache.id = device.id;
writeCache(newCache);
gatt = device.gatt;
}
-
+
return Promise.resolve(gatt);
});
-
+
promise = promise.then((gatt)=>{
if (!gatt.connected){
- var connectPromise = gatt.connect(connectSettings);
+ log("Connecting...");
+ var connectPromise = gatt.connect(connectSettings).then(function() {
+ log("Connected.");
+ });
if (settings.gracePeriodConnect > 0){
log("Add " + settings.gracePeriodConnect + "ms grace period after connecting");
connectPromise = connectPromise.then(()=>{
@@ -427,59 +441,69 @@
return Promise.resolve();
}
});
-
+
+/* promise = promise.then(() => {
+ log(JSON.stringify(gatt.getSecurityStatus()));
+ if (gatt.getSecurityStatus()['bonded']) {
+ log("Already bonded");
+ return Promise.resolve();
+ } else {
+ log("Start bonding");
+ return gatt.startBonding()
+ .then(() => console.log(gatt.getSecurityStatus()));
+ }
+ });*/
+
promise = promise.then(()=>{
- if (!characteristics || characteristics.length == 0){
- characteristics = characteristicsFromCache();
+ if (!characteristics || characteristics.length === 0){
+ characteristics = characteristicsFromCache(device);
}
});
promise = promise.then(()=>{
var characteristicsPromise = Promise.resolve();
- if (characteristics.length == 0){
+ if (characteristics.length === 0){
characteristicsPromise = characteristicsPromise.then(()=>{
log("Getting services");
return gatt.getPrimaryServices();
});
characteristicsPromise = characteristicsPromise.then((services)=>{
- log("Got services:", services);
+ log("Got services", services);
var result = Promise.resolve();
for (var service of services){
if (!(supportedServices.includes(service.uuid))) continue;
- log("Supporting service: ", service.uuid);
+ log("Supporting service", service.uuid);
result = attachServicePromise(result, service);
}
if (settings.gracePeriodService > 0) {
log("Add " + settings.gracePeriodService + "ms grace period after services");
result = result.then(()=>{
log("Wait after services");
- return waitingPromise(settings.gracePeriodService)
+ return waitingPromise(settings.gracePeriodService);
});
}
return result;
});
-
} else {
for (var characteristic of characteristics){
characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true);
}
}
-
+
return characteristicsPromise;
});
-
- promise = promise.then(()=>{
+
+ return promise.then(()=>{
log("Connection established, waiting for notifications");
- reUseCounter = 0;
characteristicsToCache(characteristics);
- clearRetryTimeout();
+ clearRetryTimeout(true);
}).catch((e) => {
characteristics = [];
log("Error:", e);
onDisconnect(e);
});
- }
+ };
Bangle.setBTHRMPower = function(isOn, app) {
// Do app power handling
@@ -487,16 +511,18 @@
if (Bangle._PWR===undefined) Bangle._PWR={};
if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[];
if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app);
- if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!=app);
+ if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!==app);
isOn = Bangle._PWR.BTHRM.length;
// so now we know if we're really on
if (isOn) {
+ switchFallback();
if (!Bangle.isBTHRMConnected()) initBt();
} else { // not on
log("Power off for " + app);
+ clearRetryTimeout(true);
if (gatt) {
if (gatt.connected){
- log("Disconnect with gatt: ", gatt);
+ log("Disconnect with gatt", gatt);
try{
gatt.disconnect().then(()=>{
log("Successful disconnect");
@@ -510,8 +536,34 @@
}
}
};
-
- var origSetHRMPower = Bangle.setHRMPower;
+
+ if (settings.replace){
+ Bangle.on("HRM", (e) => {
+ e.modified = true;
+ Bangle.emit("HRM_int", e);
+ });
+
+ Bangle.origOn = Bangle.on;
+ Bangle.on = function(name, callback) {
+ if (name == "HRM") {
+ Bangle.origOn("HRM_int", callback);
+ } else {
+ Bangle.origOn(name, callback);
+ }
+ };
+
+ Bangle.origRemoveListener = Bangle.removeListener;
+ Bangle.removeListener = function(name, callback) {
+ if (name == "HRM") {
+ Bangle.origRemoveListener("HRM_int", callback);
+ } else {
+ Bangle.origRemoveListener(name, callback);
+ }
+ };
+
+ }
+
+ Bangle.origSetHRMPower = Bangle.setHRMPower;
if (settings.startWithHrm){
@@ -521,28 +573,42 @@
Bangle.setBTHRMPower(isOn, app);
}
if ((settings.enabled && !settings.replace) || !settings.enabled){
- origSetHRMPower(isOn, app);
+ Bangle.origSetHRMPower(isOn, app);
}
};
}
-
-
- var fallbackInterval;
-
- function switchInternalHrm(){
- if (settings.allowFallback && !fallbackInterval){
- log("Fallback to HRM enabled");
- origSetHRMPower(1, "bthrm_fallback");
- fallbackInterval = setInterval(()=>{
- if (Bangle.isBTHRMConnected()){
- origSetHRMPower(0, "bthrm_fallback");
- clearInterval(fallbackInterval);
- fallbackInterval = undefined;
- log("Fallback to HRM disabled");
- }
- }, settings.fallbackTimeout);
+
+ var fallbackActive = false;
+ var inSwitch = false;
+
+ var stopFallback = function(){
+ if (fallbackActive){
+ Bangle.origSetHRMPower(0, "bthrm_fallback");
+ fallbackActive = false;
+ log("Fallback to HRM disabled");
}
- }
+ };
+
+ var startFallback = function(){
+ if (!fallbackActive && settings.allowFallback) {
+ fallbackActive = true;
+ Bangle.origSetHRMPower(1, "bthrm_fallback");
+ log("Fallback to HRM enabled");
+ }
+ };
+
+ var switchFallback = function() {
+ log("Check falling back to HRM");
+ if (!inSwitch){
+ inSwitch = true;
+ if (Bangle.isBTHRMActive()){
+ stopFallback();
+ } else {
+ startFallback();
+ }
+ }
+ inSwitch = false;
+ };
if (settings.replace){
log("Replace HRM event");
@@ -550,18 +616,17 @@
for (var i = 0; i < Bangle._PWR.HRM.length; i++){
var app = Bangle._PWR.HRM[i];
log("Moving app " + app);
- origSetHRMPower(0, app);
+ Bangle.origSetHRMPower(0, app);
Bangle.setBTHRMPower(1, app);
if (Bangle._PWR.HRM===undefined) break;
}
}
- switchInternalHrm();
}
-
+
E.on("kill", ()=>{
if (gatt && gatt.connected){
log("Got killed, trying to disconnect");
- var promise = gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e));
+ gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e));
}
});
}
diff --git a/apps/bthrm/bthrm.js b/apps/bthrm/bthrm.js
index cc533eedd..fadf2a5d8 100644
--- a/apps/bthrm/bthrm.js
+++ b/apps/bthrm/bthrm.js
@@ -1,7 +1,16 @@
-var btm = g.getHeight()-1;
var intervalInt;
var intervalBt;
+var BODY_LOCS = {
+ 0: 'Other',
+ 1: 'Chest',
+ 2: 'Wrist',
+ 3: 'Finger',
+ 4: 'Hand',
+ 5: 'Ear Lobe',
+ 6: 'Foot',
+}
+
function clear(y){
g.reset();
g.clearRect(0,y,g.getWidth(),y+75);
@@ -15,17 +24,17 @@ function draw(y, type, event) {
g.setFontAlign(0,0);
g.setFontVector(40).drawString(str,px,y+20);
str = "Event: " + type;
- if (type == "HRM") {
+ if (type === "HRM") {
str += " Confidence: " + event.confidence;
g.setFontVector(12).drawString(str,px,y+40);
str = " Source: " + (event.src ? event.src : "internal");
g.setFontVector(12).drawString(str,px,y+50);
}
- if (type == "BTHRM"){
+ if (type === "BTHRM"){
if (event.battery) str += " Bat: " + (event.battery ? event.battery : "");
g.setFontVector(12).drawString(str,px,y+40);
str= "";
- if (event.location) str += "Loc: " + event.location.toFixed(0) + "ms";
+ if (event.location) str += "Loc: " + BODY_LOCS[event.location];
if (event.rr && event.rr.length > 0) str += " RR: " + event.rr.join(",");
g.setFontVector(12).drawString(str,px,y+50);
str= "";
@@ -33,19 +42,27 @@ function draw(y, type, event) {
if (event.energy) str += " kJoule: " + event.energy.toFixed(0);
g.setFontVector(12).drawString(str,px,y+60);
}
-
}
var firstEventBt = true;
var firstEventInt = true;
+
+// This can get called for the boot code to show what's happening
+function showStatusInfo(txt) {
+ var R = Bangle.appRect;
+ g.reset().clearRect(R.x,R.y2-8,R.x2,R.y2).setFont("6x8");
+ txt = g.wrapString(txt, R.w)[0];
+ g.setFontAlign(0,1).drawString(txt, (R.x+R.x2)/2, R.y2);
+}
+
function onBtHrm(e) {
if (firstEventBt){
clear(24);
firstEventBt = false;
}
draw(100, "BTHRM", e);
- if (e.bpm == 0){
+ if (e.bpm === 0){
Bangle.buzz(100,0.2);
}
if (intervalBt){
diff --git a/apps/bthrm/default.json b/apps/bthrm/default.json
index 64e638b8a..fb284bcd2 100644
--- a/apps/bthrm/default.json
+++ b/apps/bthrm/default.json
@@ -7,10 +7,10 @@
"allowFallback": true,
"warnDisconnect": false,
"fallbackTimeout": 10,
- "custom_replace": false,
+ "custom_replace": true,
"custom_debuglog": false,
- "custom_startWithHrm": false,
- "custom_allowFallback": false,
+ "custom_startWithHrm": true,
+ "custom_allowFallback": true,
"custom_warnDisconnect": false,
"custom_fallbackTimeout": 10,
"gracePeriodNotification": 0,
diff --git a/apps/bthrm/metadata.json b/apps/bthrm/metadata.json
index b35ebd6af..4d2cb811b 100644
--- a/apps/bthrm/metadata.json
+++ b/apps/bthrm/metadata.json
@@ -2,11 +2,11 @@
"id": "bthrm",
"name": "Bluetooth Heart Rate Monitor",
"shortName": "BT HRM",
- "version": "0.08",
+ "version": "0.12",
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
"icon": "app.png",
"type": "app",
- "tags": "health,bluetooth",
+ "tags": "health,bluetooth,hrm,bthrm",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
diff --git a/apps/bthrm/recorder.js b/apps/bthrm/recorder.js
index 21345a907..ed36b5aef 100644
--- a/apps/bthrm/recorder.js
+++ b/apps/bthrm/recorder.js
@@ -32,8 +32,45 @@
Bangle.removeListener('BTHRM', onHRM);
if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(0,"recorder");
},
- draw : (x,y) => g.setColor((bpm != "")?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
+ draw : (x,y) => g.setColor((Bangle.isBTHRMActive && Bangle.isBTHRMActive())?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
};
- }
+ };
+ recorders.hrmint = function() {
+ var active = false;
+ var bpmTimeout;
+ var bpm = "", bpmConfidence = "", src="";
+ function onHRM(h) {
+ bpmConfidence = h.confidence;
+ bpm = h.bpm;
+ srv = h.src;
+ if (h.bpm > 0){
+ active = true;
+ print("active" + h.bpm);
+ if (bpmTimeout) clearTimeout(bpmTimeout);
+ bpmTimeout = setTimeout(()=>{
+ print("inactive");
+ active = false;
+ },3000);
+ }
+ }
+ return {
+ name : "HR int",
+ fields : ["Heartrate", "Confidence"],
+ getValues : () => {
+ var r = [bpm,bpmConfidence,src];
+ bpm = ""; bpmConfidence = ""; src="";
+ return r;
+ },
+ start : () => {
+ Bangle.origOn('HRM', onHRM);
+ if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(1,"recorder");
+ },
+ stop : () => {
+ Bangle.removeListener('HRM', onHRM);
+ if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(0,"recorder");
+ },
+ draw : (x,y) => g.setColor(( Bangle.origIsHRMOn && Bangle.origIsHRMOn() && active)?"#0f0":"#8f8").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
+ };
+ };
})
diff --git a/apps/bthrm/settings.js b/apps/bthrm/settings.js
index 4b564d670..2b19ea46a 100644
--- a/apps/bthrm/settings.js
+++ b/apps/bthrm/settings.js
@@ -5,18 +5,26 @@
require('Storage').writeJSON(FILE, s);
readSettings();
}
-
+
function readSettings(){
settings = Object.assign(
require('Storage').readJSON("bthrm.default.json", true) || {},
require('Storage').readJSON(FILE, true) || {}
);
}
-
+
var FILE="bthrm.json";
var settings;
readSettings();
+ function applyCustomSettings(){
+ writeSettings("enabled",true);
+ writeSettings("replace",settings.custom_replace);
+ writeSettings("startWithHrm",settings.custom_startWithHrm);
+ writeSettings("allowFallback",settings.custom_allowFallback);
+ writeSettings("fallbackTimeout",settings.custom_fallbackTimeout);
+ }
+
function buildMainMenu(){
var mainmenu = {
'': { 'title': 'Bluetooth HRM' },
@@ -35,7 +43,6 @@
case 1:
writeSettings("enabled",true);
writeSettings("replace",true);
- writeSettings("debuglog",false);
writeSettings("startWithHrm",true);
writeSettings("allowFallback",true);
writeSettings("fallbackTimeout",10);
@@ -43,17 +50,11 @@
case 2:
writeSettings("enabled",true);
writeSettings("replace",false);
- writeSettings("debuglog",false);
writeSettings("startWithHrm",false);
writeSettings("allowFallback",false);
break;
case 3:
- writeSettings("enabled",true);
- writeSettings("replace",settings.custom_replace);
- writeSettings("debuglog",settings.custom_debuglog);
- writeSettings("startWithHrm",settings.custom_startWithHrm);
- writeSettings("allowFallback",settings.custom_allowFallback);
- writeSettings("fallbackTimeout",settings.custom_fallbackTimeout);
+ applyCustomSettings();
break;
}
writeSettings("mode",v);
@@ -61,12 +62,13 @@
}
};
- if (settings.btname){
- var name = "Clear " + settings.btname;
+ if (settings.btname || settings.btid){
+ var name = "Clear " + (settings.btname || settings.btid);
mainmenu[name] = function() {
- E.showPrompt("Clear current device name?").then((r)=>{
+ E.showPrompt("Clear current device?").then((r)=>{
if (r) {
writeSettings("btname",undefined);
+ writeSettings("btid",undefined);
}
E.showMenu(buildMainMenu());
});
@@ -78,22 +80,18 @@
mainmenu.Debug = function() { E.showMenu(submenu_debug); };
return mainmenu;
}
-
-
var submenu_debug = {
'' : { title: "Debug"},
'< Back': function() { E.showMenu(buildMainMenu()); },
'Alert on disconnect': {
value: !!settings.warnDisconnect,
- format: v => settings.warnDisconnect ? "On" : "Off",
onchange: v => {
writeSettings("warnDisconnect",v);
}
},
'Debug log': {
value: !!settings.debuglog,
- format: v => settings.debuglog ? "On" : "Off",
onchange: v => {
writeSettings("debuglog",v);
}
@@ -103,57 +101,61 @@
function createMenuFromScan(){
E.showMenu();
- E.showMessage("Scanning");
+ E.showMessage("Scanning for 4 seconds");
var submenu_scan = {
- '' : { title: "Scan"},
'< Back': function() { E.showMenu(buildMainMenu()); }
};
- var packets=10;
- var scanStart=Date.now();
- NRF.setScan(function(d) {
- packets--;
- if (packets<=0 || Date.now() - scanStart > 5000){
- NRF.setScan();
- E.showMenu(submenu_scan);
- } else if (d.name){
- print("Found device", d);
- submenu_scan[d.name] = function(){
- E.showPrompt("Set "+d.name+"?").then((r)=>{
- if (r) {
- writeSettings("btname",d.name);
- }
- E.showMenu(buildMainMenu());
+ NRF.findDevices(function(devices) {
+ submenu_scan[''] = { title: `Scan (${devices.length} found)`};
+ if (devices.length === 0) {
+ E.showAlert("No devices found")
+ .then(() => E.showMenu(buildMainMenu()));
+ return;
+ } else {
+ devices.forEach((d) => {
+ print("Found device", d);
+ var shown = (d.name || d.id.substr(0, 17));
+ submenu_scan[shown] = function () {
+ E.showPrompt("Set " + shown + "?").then((r) => {
+ if (r) {
+ writeSettings("btid", d.id);
+ // Store the name for displaying later. Will connect by ID
+ if (d.name) {
+ writeSettings("btname", d.name);
+ }
+ }
+ E.showMenu(buildMainMenu());
+ });
+ };
});
- };
}
- }, { filters: [{services: [ "180d" ]}]});
+ E.showMenu(submenu_scan);
+ }, { timeout: 4000, active: true, filters: [{services: [ "180d" ]}]});
}
-
-
var submenu_custom = {
'' : { title: "Custom mode"},
'< Back': function() { E.showMenu(buildMainMenu()); },
'Replace HRM': {
value: !!settings.custom_replace,
- format: v => settings.custom_replace ? "On" : "Off",
onchange: v => {
writeSettings("custom_replace",v);
+ if (settings.mode == 3) applyCustomSettings();
}
},
'Start w. HRM': {
value: !!settings.custom_startWithHrm,
- format: v => settings.custom_startWithHrm ? "On" : "Off",
onchange: v => {
writeSettings("custom_startWithHrm",v);
+ if (settings.mode == 3) applyCustomSettings();
}
},
'HRM Fallback': {
value: !!settings.custom_allowFallback,
- format: v => settings.custom_allowFallback ? "On" : "Off",
onchange: v => {
writeSettings("custom_allowFallback",v);
+ if (settings.mode == 3) applyCustomSettings();
}
},
'Fallback Timeout': {
@@ -164,10 +166,11 @@
format: v=>v+"s",
onchange: v => {
writeSettings("custom_fallbackTimout",v*1000);
+ if (settings.mode == 3) applyCustomSettings();
}
},
};
-
+
var submenu_grace = {
'' : { title: "Grace periods"},
'< Back': function() { E.showMenu(submenu_debug); },
@@ -212,51 +215,6 @@
}
}
};
-
- var submenu = {
- '' : { title: "Grace periods"},
- '< Back': function() { E.showMenu(buildMainMenu()); },
- 'Request': {
- value: settings.gracePeriodRequest,
- min: 0,
- max: 3000,
- step: 100,
- format: v=>v+"ms",
- onchange: v => {
- writeSettings("gracePeriodRequest",v);
- }
- },
- 'Connect': {
- value: settings.gracePeriodConnect,
- min: 0,
- max: 3000,
- step: 100,
- format: v=>v+"ms",
- onchange: v => {
- writeSettings("gracePeriodConnect",v);
- }
- },
- 'Notification': {
- value: settings.gracePeriodNotification,
- min: 0,
- max: 3000,
- step: 100,
- format: v=>v+"ms",
- onchange: v => {
- writeSettings("gracePeriodNotification",v);
- }
- },
- 'Service': {
- value: settings.gracePeriodService,
- min: 0,
- max: 3000,
- step: 100,
- format: v=>v+"ms",
- onchange: v => {
- writeSettings("gracePeriodService",v);
- }
- }
- };
-
+
E.showMenu(buildMainMenu());
-})
+});
diff --git a/apps/bwclk/ChangeLog b/apps/bwclk/ChangeLog
index 11569af0c..59bf9eb96 100644
--- a/apps/bwclk/ChangeLog
+++ b/apps/bwclk/ChangeLog
@@ -3,4 +3,19 @@
0.03: Adapt colors based on the theme of the user.
0.04: Steps can be hidden now such that the time is even larger.
0.05: Included icons for information.
-0.06: Design and usability improvements.
\ No newline at end of file
+0.06: Design and usability improvements.
+0.07: Improved positioning.
+0.08: Select the color of widgets correctly. Additional settings to hide colon.
+0.09: Larger font size if colon is hidden to improve readability further.
+0.10: HomeAssistant integration if HomeAssistant is installed.
+0.11: Performance improvements.
+0.12: Implements a 2D menu.
+0.13: Clicks < 24px are for widgets, if fullscreen mode is disabled.
+0.14: Adds humidity to weather data.
+0.15: Added option for a dynamic mode to show widgets only if unlocked.
+0.16: You can now show your agenda if your calendar is synced with Gadgetbridge.
+0.17: Fix - Step count was no more shown in the menu.
+0.18: Set timer for an agenda entry by simply clicking in the middle of the screen. Only one timer can be set.
+0.19: Fix - Compatibility with "Digital clock widget"
+0.20: Better handling of async data such as getPressure.
+0.21: On the default menu the week of year can be shown.
\ No newline at end of file
diff --git a/apps/bwclk/README.md b/apps/bwclk/README.md
index f282bd187..dfb9bf515 100644
--- a/apps/bwclk/README.md
+++ b/apps/bwclk/README.md
@@ -1,16 +1,46 @@
# BW Clock
+A very minimalistic clock to mainly show date and time.

## Features
-- Fullscreen on/off
-- Tab left/right of screen to show steps, temperature etc.
-- Enable / disable lock icon in the settings.
-- If the "sched" app is installed tab top / bottom of the screen to set the timer.
-- The design is adapted to the theme of your bangle.
+The BW clock provides many features and also 3rd party integrations:
+- Bangle data such as steps, heart rate, battery or charging state.
+- A timer can be set directly. *Requirement: Scheduler library*
+- Show agenda entries. A timer for an agenda entry can also be set by simply clicking in the middle of the screen. This can be used to not forget a meeting etc. Note that only one agenda-timer can be set at a time. *Requirement: Gadgetbridge calendar sync enabled*
+- Weather temperature as well as the wind speed can be shown. *Requirement: Weather app*
+- HomeAssistant triggers can be executed directly. *Requirement: HomeAssistant app*
+
+Note: If some apps are not installed (e.gt. weather app), then this menu item is hidden.
+
+## Settings
+- Screen: Normal (widgets shown), Dynamic (widgets shown if unlocked) or Full (widgets are hidden).
+- Enable/disable lock icon in the settings. Useful if fullscreen mode is on.
+- The colon (e.g. 7:35 = 735) can be hidden in the settings for an even larger time font to improve readability further.
+- Your bangle uses the sys color settings so you can change the color too.
+
+## Menu structure
+2D menu allows you to display lots of different data including data from 3rd party apps and it's also possible to control things e.g. to set a timer or send a HomeAssistant trigger.
+
+Simply click left / right to go through the menu entries such as Bangle, Timer etc.
+and click up/down to move into this sub-menu. You can then click in the middle of the screen
+to e.g. send a trigger via HomeAssistant once you selected it.
+
+```
+ +5min
+ |
+ Bangle -- Timer[Optional] -- Agenda 1[Optional] -- Weather[Optional] -- HomeAssistant [Optional]
+ | | | | |
+ Bpm -5min Agenda 2 Temperature Trigger1
+ | | | |
+ Steps ... ... ...
+ |
+ Battery
+```
+
## Thanks to
Icons created by Flaticon
## Creator
-- [David Peer](https://github.com/peerdavid)
+[David Peer](https://github.com/peerdavid)
diff --git a/apps/bwclk/app.js b/apps/bwclk/app.js
index 5240e69ec..cd1979e98 100644
--- a/apps/bwclk/app.js
+++ b/apps/bwclk/app.js
@@ -1,24 +1,27 @@
-/*
+/************
* Includes
*/
const locale = require('locale');
const storage = require('Storage');
-/*
+/************
* Statics
*/
const SETTINGS_FILE = "bwclk.setting.json";
-const TIMER_IDX = "bwclk";
+const TIMER_IDX = "bwclk_timer";
+const TIMER_AGENDA_IDX = "bwclk_agenda";
const W = g.getWidth();
const H = g.getHeight();
-/*
+/************
* Settings
*/
let settings = {
- fullscreen: false,
+ screen: "Normal",
showLock: true,
- showInfo: 0,
+ hideColon: false,
+ menuPosX: 0,
+ menuPosY: 0,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
@@ -27,14 +30,18 @@ for (const key in saved_settings) {
}
-/*
+/************
* Assets
*/
-
// Manrope font
Graphics.prototype.setLargeFont = function(scale) {
- // Actual height 49 (50 - 2)
- this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAfwAAAAAAAAf/AAAAAAAAf/8AAAAAAAf//wAAAAAAP///AAAAAAP///8AAAAAP////wAAAAP////4AAAAP////8AAAAH////8AAAAH////8AAAAB////8AAAAAH///+AAAAAAf//+AAAAAAB//+AAAAAAAH/+AAAAAAAAf+AAAAAAAAB/AAAAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///gAAAAAD////4AAAAA/////4AAAAH/////4AAAA//////wAAAH//////gAAA///////AAAH//////+AAA///////4AAD/4AAAH/wAAP+AAAAP/AAB/wAAAAf8AAH/AAAAA/4AAf4AAAAB/gAB/gAAAAH+AAP8AAAAAf4AA/wAAAAB/gAD/AAAAAH+AAP8AAAAAf4AAf4AAAAB/gAB/gAAAAH+AAH+AAAAA/4AAf8AAAAH/AAB/4AAAA/8AAD/4AAAH/wAAP/8AAH/+AAAf//////4AAA///////AAAB//////4AAAD//////AAAAH/////4AAAAP////+AAAAAP////gAAAAAD///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAAAAAB/wAAAAAAAAH/AAAAAAAAA/4AAAAAAAAH/gAAAAAAAAf8AAAAAAAAD/gAAAAAAAAP+AAAAAAAAB///////8AAH///////wAAf///////AAB///////8AAH///////wAAf///////AAB///////8AAH///////wAAP///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAfwAAAH8AAAD/AAAB/wAAAf8AAAP/AAAD/wAAB/8AAAf/AAAP/wAAD/8AAB//AAAf/wAAH/8AAD//AAA//gAAf/8AAD/wAAB//wAAf+AAAP//AAB/wAAB//8AAH+AAAP//wAAf4AAB///AAD/AAAP/v8AAP8AAB/8/wAA/wAAP/j/AAD/AAB/8P8AAH+AAH/g/wAAf4AA/8D/AAB/wAH/gP8AAH/AA/+A/wAAf/AP/wD/AAA//D/+AP8AAD////wA/wAAH///+AD/AAAP///wAP8AAAf//+AA/wAAA///wAD/AAAB//+AAP8AAAB//gAA/wAAAB/4AAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAD+AAAAHwAAAf4AAAAfwAAB/gAAAB/gAAH+AAAAP/AAAf4AAAA/8AAB/gAAAD/4AAH+ADAAH/wAAf4AeAAP/AAB/gD+AAP8AAH+Af+AA/4AAf4D/4AB/gAB/gP/AAH+AAH+B/8AAf4AAf4P/wAB/gAB/h//AAH+AAH+P/8AAf4AAf5//wAB/gAB/v//gAP+AAH+//+AA/4AAf//f8AH/AAB//5/8B/8AAH//D////gAAf/4P///+AAB//Af///wAAH/4A///+AAAf/AB///wAAB/4AD//+AAAH/AAH//gAAAP4AAD/4AAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAA/+AAAAAAAAP/4AAAAAAAH//gAAAAAAB//+AAAAAAAf//4AAAAAAH///gAAAAAB///+AAAAAAf///4AAAAAH//9/gAAAAD///H+AAAAA///wf4AAAAP//8B/gAAAD///AH+AAAA///wAf4AAAH//8AB/gAAAf//AAH+AAAB//gAAf4AAAH/4AAB/gAAAf+AAAH+AAAB/gAf///8AAH4AB////wAAeAAH////AABgAAf///8AAAAAB////wAAAAAH////AAAAAAf///8AAAAAB////wAAAAAH////AAAAAAAAf4AAAAAAAAB/gAAAAAAAAH+AAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAeAAAAAf//AB+AAAH///+AP8AAAf///4A/4AAB////gD/wAAH////Af/gAAf///8B/+AAB////wB/8AAH///+AB/wAAf4Af4AD/gAB/gB/AAP+AAH+AP8AAf4AAf4A/wAB/gAB/gD+AAH+AAH+AP4AAf4AAf4A/gAB/gAB/gD/AAH+AAH+AP8AAf4AAf4A/wAD/gAB/gD/gAf8AAH+AH/AD/wAAf4Af/Af+AAB/gB////4AAH+AD////AAAf4AH///8AAB/gAP///gAAH+AA///8AAAAAAA///AAAAAAAB//4AAAAAAAB/+AAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///gAAAAAB////4AAAAAf////4AAAAH/////4AAAA//////wAAAH//////gAAA///////AAAH//////+AAAf//////4AAD/4D/wH/wAAP+AP8AP/AAB/wB/gAf8AAH/AH8AA/4AAf4A/wAB/gAB/gD/AAH+AAH8AP4AAf4AA/wA/gAB/gAD/AD+AAH+AAH8AP8AAf4AAf4A/wAB/gAB/gD/AAP+AAH+AP+AB/wAAf8Af8AP/AAA/4B/8B/8AAD/gH////gAAP8AP///8AAAfgAf///wAAA8AB///+AAADgAD///wAAAAAAD//+AAAAAAAH//gAAAAAAAH/4AAAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAAAH+AAAAAAAAAf4AAAAAAAAB/gAAAAAAAAH+AAAAAAAAAf4AAAAADgAB/gAAAAA+AAH+AAAAAf4AAf4AAAAH/gAB/gAAAD/+AAH+AAAA//4AAf4AAAf//gAB/gAAH//+AAH+AAD///wAAf4AA///8AAB/gAf//+AAAH+AH///gAAAf4D///wAAAB/g///8AAAAH+f//+AAAAAf////gAAAAB////wAAAAAH///8AAAAAAf//+AAAAAAB///gAAAAAAH//wAAAAAAAf/8AAAAAAAB/+AAAAAAAAH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAB/wB//wAAAAf/wf//gAAAD//z///AAAAf/////+AAAD//////8AAAf//////4AAD///////gAAP////w//AAB/+f/8Af8AAH/Af/gA/4AAf4A/8AD/gAB/gB/wAH+AAP8AH+AAf4AA/wAf4AB/gAD/AB/gAH+AAP8AH+AAf4AA/wAf4AB/gAB/gB/wAH+AAH+AP/AAf4AAf8A/+AD/gAB/8f/8Af8AAD////4H/wAAP//////+AAAf//////4AAA///////AAAD//////8AAAH//z///gAAAH/+H//4AAAAH/gH//AAAAAAAAH/wAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAP/wAAAAAAAD//wAAAAAAAf//wAAAAAAH///gABgAAA////AAPAAAD///+AB+AAAf///4AP4AAD////wB/wAAP/AP/AH/AAB/4Af+AP+AAH/AA/4A/4AAf4AB/gB/gAB/gAH+AH+AAP8AAP4Af4AA/wAA/gB/gAD/AAD+AH+AAP8AAP4Af4AA/4AB/gB/gAB/gAH+AH+AAH+AAfwA/4AAf8AD/AH/AAB/4Af4A/8AAD/4H/gP/wAAP//////+AAAf//////wAAA///////AAAB//////4AAAD//////AAAAH/////wAAAAH////+AAAAAH////AAAAAAAf/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf4AP8AAAAAB/gA/wAAAAAH+AD/AAAAAAf4AP8AAAAAB/gA/wAAAAAH+AD/AAAAAAf4AP8AAAAAB/gA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("ExwqHCYlJyYoIicoFg=="), 64+(scale<<8)+(1<<16));
+ // Actual height 47 (48 - 2)
+ this.setFontCustom(
+ atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAD/AAAAAAAAA/wAAAAAAAAP8AAAAAAAAD/AAAAAAAAA/wAAAAAAAAP8AAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAD/AAAAAAAAP/wAAAAAAAf/8AAAAAAB///AAAAAAH///wAAAAAf///8AAAAB/////AAAAH////8AAAAP////wAAAA/////AAAAB////+AAAAA////4AAAAAP///gAAAAAD//+AAAAAAA//4AAAAAAAP/gAAAAAAAD/AAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///+AAAAAB////8AAAAB/////wAAAA/////+AAAA//////wAAAf/////+AAAH//////wAAD//////+AAB/+AAAf/gAAf+AAAA/8AAH/AAAAH/AAD/gAAAA/4AA/wAAAAH+AAP8AAAAB/gAD+AAAAAf4AA/gAAAAH+AAP4AAAAA/gAD+AAAAAf4AA/wAAAAH+AAP8AAAAB/gAD/AAAAA/4AA/4AAAAP+AAH/AAAAH/AAB/4AAAH/wAAP/wAAP/4AAD//////+AAAf//////AAAD//////gAAAf/////wAAAD/////4AAAAf////4AAAAB////4AAAAAB///gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAH/AAAAAAAAD/gAAAAAAAA/4AAAAAAAAf8AAAAAAAAH+AAAAAAAAD/gAAAAAAAB/wAAAAAAAAf8AAAAAAAAP///////AAD///////wAA///////8AAP///////AAD///////wAA///////8AAP///////AAD///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAB/AAAA/gAAA/wAAA/4AAAf8AAAf+AAAP/AAAP/gAAH/wAAH/4AAD/8AAD/+AAB//AAA//gAA//wAAf/AAAP/8AAH/AAAH//AAD/gAAD//wAA/wAAB//8AAP8AAA///AAD/AAAf+fwAA/gAAP/n8AAP4AAH/x/AAD+AAD/4fwAA/gAB/8H8AAP8AAf+B/AAD/AAP/AfwAA/4AH/gH8AAH/AH/wB/AAB/8H/4AfwAAP///8AH8AAD////AB/AAAf///gAfwAAD///wAH8AAAf//4AB/AAAD//4AAfwAAAP/8AAH8AAAAf4AAB/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAADgAAAfwAAAB+AAAH8AAAAfwAAB/AAAAH+AAAfwAAAB/wAAH8AAAA/+AAB/AAAAP/gAAfwA4AA/8AAH8AfgAH/AAB/AP8AA/4AAfwD/gAH+AAH8B/4AB/gAB/A/8AAf4AAfwf/AAD+AAH8P/wAA/gAB/H/8AAf4AAfz//gAH+AAH8//4AB/gAB/f//AA/4AAf/+/4Af8AAH//P/AP/AAB//j////gAAf/wf///4AAH/4H///8AAB/8A///+AAAf+AH///AAAH/AA///gAAB/gAD//wAAAfwAAP/wAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAH/wAAAAAAAH/8AAAAAAAH//AAAAAAAH//wAAAAAAH//8AAAAAAH///AAAAAAH///wAAAAAH///8AAAAAP//9/AAAAAP//8fwAAAAP//4H8AAAAP//4B/AAAAP//4AfwAAAP//4AH8AAAD//4AB/AAAA//4AAfwAAAP/4AAH8AAAD/wAAB/AAAA/wAAAfwAAAPwAH////AADwAB////wAAwAAf///8AAAAAH////AAAAAB////wAAAAAf///8AAAAAH////AAAAAA////wAAAAAAAfwAAAAAAAAH8AAAAAAAAB/AAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAGAHwAAAB///gB+AAAH///8AfwAAB////AP+AAAf///wD/wAAH///+A/+AAB////gP/gAAf///4A/8AAH/8P8AH/AAB/AD+AA/4AAfwA/gAH+AAH8AfwAB/gAB/AH8AAf4AAfwB/AAH+AAH8AfwAB/gAB/AH8AAf4AAfwB/gAH+AAH8Af4AB/gAB/AH/AA/wAAfwB/4Af8AAH8AP/AP/AAB/AD////gAAfwAf///wAAH8AD///8AAB/AA///+AAAfwAH///AAAAAAA///gAAAAAAD//gAAAAAAAP/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///4AAAAAH////wAAAAH/////AAAAD/////4AAAB//////AAAA//////4AAAf//////AAAP//////4AAD/8D/w/+AAB/4B/wD/wAAf8A/wAf8AAP+AP4AD/gAD/AD+AAf4AA/wB/AAH+AAP4AfwAB/gAD+AH8AAf4AA/gB/AAH+AAP4AfwAB/gAD+AH+AAf4AA/wB/gAH+AAP8Af8AD/gAD/gH/gB/wAAf8A/8A/8AAH/AP///+AAB/gB////gAAPwAP///wAAB4AD///4AAAMAAf//8AAAAAAD//+AAAAAAAP/+AAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAAH8AAAAAAAAB/AAAAAAAAAfwAAAAAAAAH8AAAAAAAAB/AAAAABwAAfwAAAAB8AAH8AAAAD/AAB/AAAAD/wAAfwAAAH/8AAH8AAAH//AAB/AAAP//wAAfwAAP//8AAH8AAf//+AAB/AAf//8AAAfwA///8AAAH8A///4AAAB/A///4AAAAfx///wAAAAH9///wAAAAB////gAAAAAf///gAAAAAH///AAAAAAB///AAAAAAAf/+AAAAAAAH/+AAAAAAAB/8AAAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAf/AAAAAP+Af/8AAAAP/4P//wAAAP//P//+AAAH//////wAAB//////8AAA///////gAAf//////8AAH////gP/AAD/wf/wA/wAA/4D/4AP+AAP8Af8AB/gAD/AH/AAf4AA/gA/wAH+AAP4AP4AA/gAD+AD/AAP4AA/gA/wAH+AAP8Af8AB/gAD/AH/AAf4AA/4D/4AP+AAP/B//AH/AAB////4D/wAAf//////8AAD//////+AAAf//////AAAH//////wAAA//8///4AAAD/+D//8AAAAP+Af/8AAAAAAAB/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAAAAAAAB//AAAAAAAB//8AAAAAAB///gAAgAAA///8AAcAAAf///gAPAAAH///8AH4AAD////AD/AAB/+H/4B/wAAf+Af+Af8AAP+AB/wD/gAD/gAf8Af4AA/wAD/AH+AAP8AA/wB/gAD+AAH8AP4AA/gAB/AD+AAP4AAfwB/gAD+AAH8Af4AA/wAD/AH+AAP8AA/gD/gAD/gAf4A/wAAf8AP8A/8AAH/gH/Af/AAA///////gAAP//////wAAB//////8AAAP/////+AAAB//////AAAAP/////AAAAA/////gAAAAD////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wA/wAAAAAP8AP8AAAAAD/AD/AAAAAA/wA/wAAAAAP8AP8AAAAAD/AD/AAAAAA/wA/wAAAAAP8AP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='),
+ 46,
+ atob("ExspGyUkJiQnISYnFQ=="),
+ 62+(scale<<8)+(1<<16)
+ );
return this;
};
@@ -45,96 +52,327 @@ Graphics.prototype.setMediumFont = function(scale) {
return this;
};
+
Graphics.prototype.setSmallFont = function(scale) {
// Actual height 28 (27 - 0)
- this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//84D//zgP/+GAAAAAAAAAAAAAAAAAAAD4AAAPgAAA+AAAAAAAAAAAAA+AAAD4AAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAg4AAHDgAAcOCABw54AHD/gAf/8AD/8AB//gAP8OAA9w4YCHD/gAcf+AB//gAf/gAP/uAA/w4ADnDgAAcOAABw4AAHAAAAcAAAAAAAAAAAAAAAIAA+A4AH8HwA/4PgHjgOAcHAcBwcBw/BwH78DgfvwOB8HA4HAOBw8A+HngB4P8ADgfgAAAYAAAAAAAAAAB4AAAf4AQB/gDgOHAeA4cDwDhweAOHDwA88eAB/nwAD88AAAHgAAA8AAAHn4AA8/wAHnvgA8cOAHhg4A8GDgHgcOA8B74BgD/AAAH4AAAAAAAAAAAAAAAAAMAAAH8AD8/4Af/3wB/8HgODwOA4HA4DgODgOAcOA4A44DwDzgHAH8AMAPwAQP+AAA/8AAAB4AAADAAAAAA+AAAD4AAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/8AD//+A/+/+H4AD98AAB3gAADIAAAAAAAAAAAAAIAAABwAAAXwAAHPwAB8P8D/gP//4AH/8AAAAAAAAAAAAAAAAAAAAAAAAGAAAA4gAAB/AAAH8AAD/AAAP8AAAH4AAAfwAADiAAAOAAAAAAAAAAAAAAGAAAAYAAABgAAAGAAAAYAAABgAAD/+AAP/4AABgAAAGAAAAYAAABgAAAGAAAAYAAAAAAAAAAAAAADkAAAPwAAA/AAAAAAAAAAAAAAAAAAAAAAAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAAAAAAAAAAAAAAAAAAAAAADgAAAOAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAA4AAA/gAA/+AA//AA//AAP/AAA/AAADAAAAAAAAAAAAAAAAAAA//gAP//gB///AHgA8A8AB4DgADgOAAOA4AA4DgADgPAAeAeADwB///AD//4AD/+AAAAAAAAAAAAAAAA4AAAHgAAAcAAADwAAAP//+A///4D///gAAAAAAAAAAAAAAAAAAAAAAYAeADgD4AeAfAD4DwAfgOAD+A4Ae4DgDzgOAeOA4Dw4DweDgH/wOAP+A4AfwDgAAAAAAAAAAAAIAOAA4A4ADwDggHAOHgOA48A4DnwDgO/AOA7uA4D84HgPh/8A8H/gDgH8AAACAAAAAAAAAAAAAHgAAB+AAA/4AAP7gAD+OAA/g4AP4DgA+AOADAA4AAB/+AAH/4AAf/gAADgAAAOAAAAAAAAAAAAAAAAD4cAP/h4A/+HwDw4HgOHAOA4cA4DhwDgOHAOA4cA4Dh4HAOD58A4H/gAAP8AAAGAAAAAAAAAAAAAAAAD/+AAf/8AD//4AePDwDw4HgOHAOA4cA4DhwDgOHAOA4cB4Bw8PAHD/8AIH/gAAH4AAAAAAAAAADgAAAOAAAA4AAYDgAHgOAD+A4B/wDgf4AOP+AA7/AAD/gAAP4AAA8AAAAAAAAAAAAAAAAAAeH8AD+/4Af//wDz8HgOHgOA4OA4Dg4DgODgOA4eA4Dz8HgH//8AP7/gAeH8AAAAAAAAAAAAAAAA+AAAH+AgB/8HAHh4cA8Dg4DgODgOAcOA4Bw4DgODgPA4eAeHDwB///AD//4AD/+AAAAAAAAAAAAAAAAAAAAAAAAAAODgAA4OAADg4AAAAAAAAAAAAAAAAAAAAAAAAAAAAABwA5AHAD8AcAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAB8AAAP4AAB5wAAPDgAB4HAAHAOAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGMAAAYwAABjAAAGMAAAYwAABjAAAGMAAAYwAABjAAAGMAAAYwAABjAAAGMAAAYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAEAAcA4AB4HAADw4AADnAAAH4AAAPAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAB8AAAHgAAA4AAADgDzgOA/OA4D84DgeAAPHwAAf+AAA/wAAB8AAAAAAAAAAAAAAAAAAD+AAB/+AAP/8AB4B4AOABwBwADgHB8OA4P4cDhxxwMGDDAwYMMDBgwwOHHHA4f4cDh/xwHAHCAcAMAA8AwAB8PAAD/4AAD/AAAAAAAAAAAAAACAAAB4AAB/gAA/8AAf+AAP/wAH/nAA/gcADwBwAPwHAA/4cAA/9wAAf/AAAP/AAAD/gAAB+AAAA4AAAAAAAAAAAAAAD///gP//+A///4DgcDgOBwOA4HA4DgcDgOBwOA4HA4Dg8DgPHwOAf/h4A///AB8f4AAAfAAAAAAAP+AAD/+AAf/8AD4D4AeADwBwAHAOAAOA4AA4DgADgOAAOA4AA4DgADgOAAOAcABwB4APAD4D4AHgPAAOA4AAAAAAAAAAAAAAAP//+A///4D///gOAAOA4AA4DgADgOAAOA4AA4DgADgOAAOA8AB4BwAHAHwB8AP//gAP/4AAP+AAAAAAAAAAAAAAAA///4D///gP//+A4HA4DgcDgOBwOA4HA4DgcDgOBwOA4HA4DgcDgOBgOA4AA4AAAAAAAAAAAAAAD///gP//+A///4DgcAAOBwAA4HAADgcAAOBwAA4HAADgcAAOAwAA4AAAAAAAAAf+AAD/+AA//+ADwB4AeADwDwAHgOAAOA4AA4DgADgOAAOA4AA4DgMDgPAweAcDBwB8MfADw/4AHD/AAAPwAAAAAAAAAAAAAAAP//+A///4D///gABwAAAHAAAAcAAABwAAAHAAAAcAAABwAAAHAAAAcAAABwAA///4D///gP//+AAAAAAAAAAAAAAAAAAAD///gP//+A///4AAAAAAAAAAAADgAAAPAAAA+AAAA4AAADgAAAOAAAA4AAAHgP//8A///wD//8AAAAAAAAAAAAAAAAAAAA///4D///gP//+AAHAAAA+AAAP8AAB54AAPDwAB4HgAPAPAB4AfAPAA+A4AA4DAABgAAACAAAAAAAAAAP//+A///4D///gAAAOAAAA4AAADgAAAOAAAA4AAADgAAAOAAAA4AAADgAAAAAAAAAAAAAAP//+A///4D///gD+AAAD+AAAB+AAAB/AAAB/AAAB/AAAB+AAAH4AAB+AAA/gAAP4AAD+AAA/AAAfwAAD///gP//+A///4AAAAAAAAAAAAAAAAAAAP//+A///4D///gHwAAAPwAAAPgAAAfgAAAfAAAAfAAAA/AAAA+AAAB+AAAB8A///4D///gP//+AAAAAAAAAAAP+AAD/+AAf/8AD4D4AeADwBwAHAOAAOA4AA4DgADgOAAOA4AA4DgADgOAAOAcABwB4APAD4D4AH//AAP/4AAP+AAAAAAAAAAAP//+A///4D///gOAcAA4BwADgHAAOAcAA4BwADgHAAOAcAA4DgAD4eAAH/wAAP+AAAPgAAAAAAAA/4AAP/4AB//wAPgPgB4APAHAAcA4AA4DgADgOAAOA4AA4DgADgOAAOA4AO4BwA/AHgB8APgPwAf//gA//uAA/4QAAAAAAAAAA///4D///gP//+A4BwADgHAAOAcAA4BwADgHAAOAcAA4B8ADgP8APh/8Af/H4A/4HgA+AGAAAAAAAAAAAABgAHwHAA/g+AH/A8A8cBwDg4DgODgOA4OA4DgcDgOBwOA4HA4DwODgHg4cAPh/wAcH+AAwPwAAAAADgAAAOAAAA4AAADgAAAOAAAA4AAAD///gP//+A///4DgAAAOAAAA4AAADgAAAOAAAA4AAADgAAAAAAAAAAAAAAAAAP//AA///AD//+AAAB8AAABwAAADgAAAOAAAA4AAADgAAAOAAAA4AAAHgAAA8A///gD//8AP//gAAAAAAAAAAIAAAA8AAAD+AAAH/AAAD/wAAB/4AAA/8AAAf4AAAPgAAB+AAA/4AAf+AAP/AAH/gAD/wAAP4AAA4AAAAAAAAPAAAA/gAAD/4AAA/+AAAf/AAAH/gAAB+AAAf4AAf/AAf/AAP/gAD/gAAPwAAA/4AAA/+AAAf/AAAH/wAAB/gAAB+AAB/4AA/+AA/+AA/+AAD/AAAPAAAAgAAAAAAAAMAAGA4AA4D4APgHwB8APwfAAPn4AAf+AAAfwAAB/AAAf+AAD4+AA/B8AHwB8A+AD4DgADgMAAGAwAAADwAAAPwAAAPwAAAfgAAAfgAAAf/4AAf/gAH/+AB+AAAPwAAD8AAA/AAADwAAAMAAAAgAAAAAAAAMAACA4AA4DgAPgOAD+A4Af4DgH7gOB+OA4Pw4Dj8DgO/AOA/4A4D+ADgPgAOA4AA4DAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////////gAAAOAAAA4AAADAAAAAAAAAAAAAAAAAAAAAAA4AAAD+AAAP/gAAH/4AAB/+AAAf+AAAH4AAABgAAAAAAAAADAAAAOAAAA4AAADgAAAP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAADgAAAcAAADgAAAcAAADgAAAcAAAB4AAADwAAADgAAAHAAAAOAAAAYAAAAAAAAAAAAAAAAAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAHH8AA8/4AHzjgAcMOABxwYAHHBgAccOABxwwAHGHAAP/4AA//4AA//gAAAAAAAAAAAAAAAAAAA///4D///gP//+AA4BwAHADgAcAOABwA4AHADgAcAOAB4B4ADwPAAP/8AAf/AAAf4AAAAAAAAAAAAPwAAD/wAAf/gADwPAAeAeABwA4AHADgAcAOABwA4AHADgAeAeAA8DwABwOAADAwAAAAAAAAAAAA/AAAP/AAD//AAPA8AB4B4AHADgAcAOABwA4AHADgAcAOAA4BwD///gP//+A///4AAAAAAAAAAAAAAAAPwAAD/wAAf/gAD2PAAeYeABxg4AHGDgAcYOABxg4AHGDgAeYeAA/jwAB+OAAD4wAABgAAAAAAAAAAABgAAAGAAAB//+Af//4D///gPcAAA5gAADGAAAMYAAAAAAAAAPwAAD/wMA//w4DwPHgeAePBwA4cHADhwcAOHBwA4cHADhwOAcPB///4H///Af//wAAAAAAAAAAAAAAAAAAD///gP//+AA//4ADgAAAcAAABwAAAHAAAAcAAABwAAAHgAAAP/+AAf/4AA//gAAAAAAAAAAAAAAMf/+A5//4Dn//gAAAAAAAAAAAAAAAAAAHAAAAfn///+f//+5///wAAAAAAAAAAAAAAAAAAP//+A///4D///gAAcAAAD8AAAf4AADzwAAeHgAHwPAAeAeABgA4AEABgAAAAAAAAAD///gP//+A///4AAAAAAAAAAAAAAAAAAAAf/+AB//4AH//gAOAAABwAAAHAAAAcAAABwAAAHgAAAP/+AA//4AB//gAOAAABwAAAHAAAAcAAABwAAAHgAAAf/+AA//4AA//gAAAAAAAAAAAAAAAf/+AB//4AD//gAOAAABwAAAHAAAAcAAABwAAAHAAAAeAAAA//4AB//gAD/+AAAAAAAAAAAAAAAAD8AAA/8AAH/4AA8DwAHgHgAcAOABwA4AHADgAcAOABwA4AHgHgAPh8AAf/gAA/8AAA/AAAAAAAAAAAAAAAAB///8H///wf///A4BwAHADgAcAOABwA4AHADgAcAOAB4B4ADwPAAP/8AAf/AAAf4AAAAAAAAAAAAPwAAD/wAA//wADwPAAeAeABwA4AHADgAcAOABwA4AHADgAOAcAB///8H///wf///AAAAAAAAAAAAAAAAAAAH//gAf/+AB//4ADwAAAcAAABwAAAHAAAAcAAAAAAAAAAMAAHw4AA/jwAH+HgAcYOABxw4AHHDgAcMOABw44AHjjgAPH+AA8fwAAw+AAAAAABgAAAGAAAAcAAAf//wB///AH//+ABgA4AGADgAYAOABgA4AAAAAAAAAAAAAAAH/AAAf/wAB//wAAB/AAAAeAAAA4AAADgAAAOAAAA4AAADgAAAcAB//4AH//gAf/+AAAAAAAAAAAAAAABwAAAH4AAAf8AAAP8AAAH+AAAD+AAAD4AAA/gAAf8AAP+AAH/AAAfgAABwAAAAAAAAAAAABwAAAH8AAAf+AAAP/gAAD/gAAB+AAAf4AAP8AAP+AAB/AAAH4AAAf8AAAP+AAAD/gAAB+AAAf4AAf/AAP/AAB/gAAHgAAAQAAABAAIAHADgAeAeAA8HwAB8+AAD/gAAD8AAAPwAAD/gAAfPgADwfAAeAeABwA4AEAAgAAAAABAAAAHgAAAfwAAA/wAAAf4BwAP4/AAP/8AAP+AAD/AAB/wAA/4AAP8AAB+AAAHAAAAQAAAAAAIAHADgAcAeABwD4AHA/gAcHuABx84AHPDgAf4OAB/A4AHwDgAeAOABgA4AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAH4Af//////n//AAAA4AAADgAAAAAAAAAAAAAAAAAP//+A///4D///gAAAAAAAAAAAAAAAAAAA4AAADgAAAOAAAA//5/9////wAH4AAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAeAAAD4AAAOAAAA4AAADgAAAHAAAAcAAAA4AAADgAAAOAAAD4AAAPAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), 32, atob("BgkMGhEZEgYMDAwQCAwICxILEBAREBEOEREJCREVEQ8ZEhEUExAOFBQHDREPGBMUERQSEhEUERsREBIMCwwTEg4QERAREQoREQcHDgcYEREREQoPDBEPFg8PDwwIDBMc"), 28+(scale<<8)+(1<<16));
+ this.setFontCustom(
+ atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/+cB//5wH//nAAAAAAAAAAAAAAAAAAAB8AAAHwAAAfAAAAAAAAAAAAAfAAAB8AAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAQcAADhwAAOHBAA4c8ADh/wAP/+AB/+AA//wAH+HAAe4cMBDh/wAOP/AA//wAP/wAH/3AAf4cABzhwAAOHAAA4cAADgAAAOAAAAAAAAAAAAAAAAAAAAwAH8HwA/4PgD/geAePA8BwcBw/BwH78DgfvwOB+HA4HAeBwcA8HDgB4f+ADg/wAGB+AAAAAAAAAAAAAAAH4AAA/wBwHngPAcOB4Bw4PAHDh4AcOPAA/x4AD/PAADx4AAAPAAAB5wAAPPwAB5/gAPOPAB4wcAPDBwB4MHAPA4cA4B/gBAH8AAAHAAAAAAAAAAAAAPAAHD/AB/f+AP/x4B4+DwHB4HAcDwcBwHhwHAPHAcAccB4A5wDgB+AGA/4AAH/AAAf+AAAA8AAABgAAAAAfAAAB8AAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/8AD//+A/+/+H4AD98AAB3gAADIAAAAAAAAAAAAAIAAABwAAAXwAAHPwAB8P8D/gP//4AH/8AAAAAAAAAAAAAAAAAAAAAAAAHAAAAcwAAA/gAAb8AAB/gAAH+AAAD+AAAOwAABxAAADAAAAAAAAAAAAAADAAAAMAAAAwAAADAAAAMAAAAwAAB//AAH/8AAAwAAADAAAAMAAAAwAAADAAAAMAAAAAAAAAAAAAABwAAAHIAAAfgAAB8AAAAAAAAAAAAAAAAAAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAAAAAAAAAAAAAAAAAAAABwAAAHAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAA/wAA//AA//AA//AAH/AAAfAAABAAAAAAAAAAAAAAAAAAAf/wAH//wA///gDgAOAcAAcBwABwHAAHAcAAcBwABwHgAPAPAB4Af//AA//4AA/+AAAAAAAAAAAAAAAAMAAABwAAAOAAAB4AAAH///Af//8B///wAAAAAAAAAAAAAAAAAAAAwAcAPADwB8AfAPAB8B4APwHAB/AcAPcBwB5wHAPHAcB4cA8PBwD/4HAH/AcAHwBwAAAAAAAAAAAAGAHAAcAcAB4BwYDwHDwHAceAcBz4BwHfgHAf3AcB+eDwHw/+AeB/wBwD+AAAAAAAAAAAAAAAAABwAAAfAAAP8AAD/wAA/nAAP4cAD+BwAfgHAB4AcAEA//AAD/8AAP/wAABwAAAHAAAAMAAAAAAAAAAAAAEAH/w4Af/D4B/8HgHDgPAcOAcBw4BwHDgHAcOAcBw8DwHB4eAcH/wBgP+AAAPwAAAAAAAAAAAAAAAB//AAf//AD//+AOHB4Bw4BwHDgHAcOAcBw4BwHDgHAcPA8A4eHgDh/8AEB/gAAD4AAAAAAAAAABwAAAHAAAAcAAMBwADwHAB/AcA/4BwP8AHH/AAd/gAB/wAAH8AAAeAAAAAAAAAAAAAAAEAAPD+AB/f8AP//4B4+DwHDwHAcHAcBwcBwHBwHAcPAcB/+DgD//+AH5/wACB8AAAAAAAAAAAAAAAAEAAAD+AAAf+DAD74OAODw8BwHBwHAOHAcA4cBwDBwHAcHAeBw8A+ePgB//8AD//gAB/wAAAAAAAAAAAAAAAAAAAAAHBwAAcHAABwcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AcgDgB+AOAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAeAAAD8AAAf4AADzwAAeHgADwPAAGAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADGAAAMYAAAxgAADGAAAMYAAAxgAADGAAAMYAAAxgAADGAAAMYAAAxgAADGAAAMYAAAxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABACAAOAcAA8DgAB4cAABzgAAD8AAAHgAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAHgAAA+AAAHgAAAcAAABwD5wHAfnAcD8cBweAAHzwAAP+AAAfwAAAcAAAAAAAAAAAAAAAAAAB/AAA//AAH/+AA8A8AHAA4A4ABwDg+HAcH8OBw444GDBhgYMGGBgwYYHDjjgcP8OBw/44DgDhAOAGAAeAYAA+HgAB/8AAB/gAAAAAAAAAAAAABAAAA8AAAfwAAP/AAH/gAD/4AB/zgAf4OAB8A4AHwDgAf4OAA/84AAP/gAAH/AAAD/gAAB/AAAA8AAAAQAAAAAAAAAB///wH///Af//8BwOBwHA4HAcDgcBwOBwHA4HAcDgcBweBwHj4HAP/58Afz/gAcH8AAAPAAAAAAAH/AAB//AAf//AB4A8APAB4B4ADwHAAHAcAAcBwABwHAAHAcAAcBwABwHAAHAOAA4A8AHgB8B8ADwHgADAYAAAAAAAAAAAAAAAH///Af//8B///wHAAHAcAAcBwABwHAAHAcAAcBwABwHAAHAeAA8A8AHgB8B+AD//gAH/8AAD/AAAAAAAAAAAAAAAAf//8B///wH///AcDgcBwOBwHA4HAcDgcBwOBwHA4HAcDgcBwOBwHAAHAcAAcAAAAAAAAAAAAAAB///wH///Af//8BwOAAHA4AAcDgABwOAAHA4AAcDgABwOAAHAAAAcAAAAAAAAAP/AAB//AAf//AB4A8APAB4B4ADwHAAHAcAAcBwABwHAAHAcAAcBwGBwHgYPAOBg4A+GPgB4f8ADh/gAAH4AAAAAAAAAAAAAAAH///Af//8B///wAA4AAADgAAAOAAAA4AAADgAAAOAAAA4AAADgAAAOAAAA4AAf//8B///wH///AAAAAAAAAAAAAAAAAAAB///wH///Af//8AAAAAAAAAAAABgAAAHgAAAeAAAA8AAABwAAAHAAAAcAAABwH///Af//4B///AAAAAAAAAAAAAAAAAAAAf//8B///wH///AAHgAAA/AAAH+AAA88AAHh8AA8D4AHgDwA8AHgHgAPAYAAcBAAAwAAABAAAAAAAAAAH///Af//8B///wAAAHAAAAcAAABwAAAHAAAAcAAABwAAAHAAAAcAAABwAAAAAAAAAAAAAAH///Af//8B///wB/AAAB/AAAA/AAAA/gAAA/gAAA/gAAA/AAAD8AAA/AAAfwAAH8AAB/AAAfgAAP4AAB///wH///Af//8AAAAAAAAAAAAAAAAAAAH///Af//8B///wD8AAAD4AAAH4AAAHwAAAPwAAAPgAAAPgAAAfAAAAfAAAA/Af//8B///wH///AAAAAAAAAAAH/AAB//AAf//AB4A8APAB4B4ADwHAAHAcAAcBwABwHAAHAcAAcBwABwHAAHAOAA4A8AHgB+D8AD//gAH/8AAD+AAAAAAAAAAAH///Af//8B///wHAOAAcA4ABwDgAHAOAAcA4ABwDgAHAeAAeBwAA+fAAD/4AAD/AAADgAAAAAAAAf8AAH/8AB//8AHgDwA8AHgHgAPAcAAcBwABwHAAHAcAAcBwABwHAAnAcAHcA4AfgDwA+AH4P4AP//wAf/3AAP4AAAAAAAAAAAf//8B///wH///AcA4ABwDgAHAOAAcA4ABwDgAHAOAAcB+AB4H+AD59/AP/h8AP8BwAOABAAAAAAAAAAAAAwAD4HwA/4fAD/geAePA8BwcBwHBwHAcDgcBwOBwHA4HAcDgcA4HDwD4eeAHw/4AOD/AAIDwAAAAABwAAAHAAAAcAAABwAAAHAAAAcAAABwAAAH///Af//8B///wHAAAAcAAABwAAAHAAAAcAAABwAAAGAAAAAAAAAAAAAH//wAf//gB///AAAAeAAAA8AAABwAAAHAAAAcAAABwAAAHAAAAcAAADgAAAeAf//wB//+AH//gAAAAAAAAAAGAAAAfAAAB/gAAB/wAAA/4AAAf8AAAP/AAAH8AAADwAAA/AAAf8AAP+AAP/AAH/gAB/wAAH4AAAcAAABAAAAHwAAAf4AAA/+AAAP/gAAH/wAAB/wAAA/AAAf8AAf/AAP/gAP/gAB/gAAH4AAAf+AAAf/AAAH/wAAB/8AAAfwAAB/AAB/8AA/+AA/+AAf+AAB/AAAHAAAAAAAAAAAAQGAADAeAA8B8AHwD8B+AD4PgAH74AAH/AAAPwAAA/gAAP/gAD8fAAfA/AH4A+AeAA8BwABwEAABAQAAABwAAAHwAAAPwAAAfwAAAfgAAAfgAAAf/wAB//AAf/8AH8AAA/AAAPwAAB8AAAHAAAAQAAAAAAAAAAABAcAAcBwADwHAA/AcAP8BwD/wHAfnAcH4cBx+BwHPwHAf8AcB/ABwH4AHAeAAcBgABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////////gAAAOAAAA4AAADAAAAAAAAAAAAAAAAAAAAAAAeAAAB/gAAH/4AAB/+AAAf/gAAH/AAAB8AAAAQAAAAAAAAAAAAAAOAAAA4AAADgAAAP/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAADgAAAcAAADgAAAcAAADgAAAcAAAB4AAADwAAADgAAAHAAAAOAAAAYAAAAAAAAAAAAAAAAAAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAADj+AAef8AD5xwAOGHAA44MADjgwAOOHAA44YADjDgAH/8AAf/8AAf/wAAAAAAAAAAAAAAAAAAAf//8B///wH//+AAcA4ADgBwAOAHAA4AcADgBwAOAHAA8A8AB8PgAD/8AAH/gAAH4AAAAAAAAAAAAH4AAB/4AAP/wAB4HgAPAPAA4AcADgBwAOAHAA4AcADgBwAPAPAAeB4AA4HAABgYAAAAAAAAAAAAfgAAH/gAB//gAHgeAA8A8ADgBwAOAHAA4AcADgBwAOAHAAcA4B///wH///Af//8AAAAAAAAAAAAAAAAH4AAB/4AAP/wAB7HgAPMPAA4wcADjBwAOMHAA4wcADjBwAPMPAAfx4AA/HAAB8YAAAwAAAAAAAAAAAAwAAADAAAB///AP//8B///wHMAAAYwAABjAAAGMAAAAAAAAAPwAAD/wMA//w4DwPHgeAePBwA4cHADhwcAOHBwA4cHADhwOAcPB///4H///Af//wAAAAAAAAAAAAAAAAAAB///wH///AAf/8ABwAAAOAAAA4AAADgAAAOAAAA4AAADwAAAH//AAP/8AAf/wAAAAAAAAAAAAAAAAAAAc//8Bz//wHP//AAAAAAAAAAAAAAHAAAAcAAAH+f///5///7H//8AAAAAAAAAAAAAAH///Af//8B///wAAPAAAB+AAAP8AAB54AAfDwAD4HgAOAPAAwAcACAAwAAAAAAAAAB///wH///Af//8AAAAAAAAAAAAAAAAAAAAP//AA//8AB//wAHAAAA4AAADgAAAOAAAA4AAAD4AAAH//AAP/8AB//wAHAAAA4AAADgAAAOAAAA4AAADwAAAH//AAP/8AAf/wAAAAAAAAAAAAAAAP//AA//8AB//wAHAAAA4AAADgAAAOAAAA4AAADgAAAPAAAAf/8AA//wAB//AAAAAAAAAAAAAAAAB+AAAf+AAD/8AAeB4ADwDwAOAHAA4AcADgBwAOAHAA4AcADwDwAHw+AAP/wAAf+AAAfgAAAAAAAAAAAAAAAB///8H///wP///A4BwAHADgAcAOABwA4AHADgAcAOAB4B4AD4fAAH/4AAP/AAAPwAAAAAAAAAAAAPwAAD/wAA//wADwPAAeAeABwA4AHADgAcAOABwA4AHADgAOAcAB///8H///wf///AAAAAAAAAAAAAAAAAAAD//wAP//AAf/8ABwAAAOAAAA4AAADgAAAOAAAAAAAAAYGAAD4cAAfx4AD3DwAOOHAA44cADjhwAOGHAA4ccADxzwAHj+AAOP4AAYOAAAAAAAwAAADAAAAMAAAP//wA///gD///AAwAcADABwAMAHAAwAcADAAwAAAAAAAAAAD/gAAP/4AA//4AAA/gAAAPAAAAcAAABwAAAHAAAAcAAABwAAAOAA//8AD//wAP//AAAAAAAAAAAIAAAA4AAAD8AAAH+AAAH/AAAD/gAAB/AAAB8AAA/wAAf8AAP+AAD/AAAPgAAAwAAAAAAAAIAAAA8AAAD/AAAH/gAAD/wAAA/wAAA/AAAf8AAP+AAP+AAA/AAAD+AAAH/AAAD/gAAA/wAAA/AAAf8AAP/AAP/AAA/gAADgAAAAAAAAAAEADAAwAOAHAA+B8AB8PgAB74AAD/AAAH4AAA/wAAHvgAB8PgAPgfAA4AcADAAwAAABABAAAAHAAAAfgAAA/wAAA/wAwAf4fAAP/8AAP/AAB/gAA/wAAf4AAP+AAB/AAAHgAAAQAAAAAAEADAAwAOAPAA4B8ADgPwAOD/AA4ecADnxwAO8HAA/gcAD8BwAPAHAA4AcACAAwAAAAAAAAAAAAAAAAAAAAAAAAAA8AB////f//////n/+AAAA4AAADgAAAAAAAAAAAAAAAAAH///Af//8B///wAAAAAAAAAAAAAAAAAAA4AAADgAAAOAAAA//5/9////z////AAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAB8AAAHwAAAcAAABwAAAHgAAAOAAAA8AAABwAAAHAAAB8AAAHwAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAGAAABwAAAOAAABwAAAHAAAAcAAAA4AAABwAAABgAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAOAB4B4ADwPAAHh4AAPPAAAf4AAA/AAAB4AAAPwAAB/gAAPPAAB4eAAPA8AB4B4AHADgAIAEAAAAAAADAAAAMAAAAwAAADAAAAMAAAAwAAHDDgA8MPADww8AGDBgAAMAAAAwAAADAAAAMAAAAwAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADn//gOf/+A5//4AAAAAAAAAAAAAAAAAAAD/AAA//AAH/+AA+B8ADgBwAOAHAHwAPgfAA+B8AD4A4AcADwDwAHgeAAOBwAAQCAAAAAAAAAAAADgcAAOBwAA4HAD//8A///wD///AeDgcBwOBwHA4HAcDgcB4GBwD4AHAHgAcAOAAAAAAAAAAAAAMAGAB7+8AD//gAHx8AAcBwADgDgAOAOAA4A4ADgDgAOAOAA4A4ABwHAAHg8AA//4AH//wAMOGAAAAAAQAAABwAAAHwMYAPwxgAfjGAAfsYAAf7gAAf/wAB//AAf/8AH7GAA/MYAPwxgB8DGAHAAAAQAAAAAAAAAAAAAf/D/5/8P/n/w/+AAAAAAAAAAAAAAAAAAAABwAAffhwD//Hgf+cfBzwwcGHDhwYcOHBxw4cHDhxwfOPvA8//4Bx//AADwwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAP/wAB//gAPAPAB4AOAPDw8A4/xwHH/jgc4HOBzgc4HMAzgcwDOBzgc4HPDjgOcOeA4whwBwAOAHwD4APw/AAf/4AAf+AAAPAAAAAAAATgAAD/AAANsAAA2wAADTAAAP8AAAfwAAAAAAAAAAAAAAAAAAgAAAPAAAB+AAAOeAADw8AAOIwAADxAAAfgAADngAA8PAADgMAAEAQAAAAAAAAAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAAB+AAAH4AAAAAAAAAAAAAAAAAAAAD8AAA/8AAHh4AAYDgAD/3AAN/MAA0QwADRjAAN/MAA7hwABwOAADhwAAH+AAAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAD/AAAeeAABw4AAGDgAAYOAABw4AAH/AAAP8AAAfAAAAAAAAAAAAAAAAAAAwYAADBgAAMGAAAwYAADBgAAMGAAP+YAA/5gAD/mAAAwYAADBgAAMGAAAwYAADBgAAAAAAAAAAAAAAAMDAABwcAAPDwAAwPAADB8AAMOwAA5zAAB+MAADwwAAAAAAAAAAAIBAAAwGAADMcAANwwAA/DAAD8MAAO/wAAx+AAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf///B///8H//AAAAeAAAA4AAADgAAAOAAAA4AAADgAAAcAB//gAH//gAf/+AAAAAAAAAAAAAAAAAAAAP4AAB/4AAP/gAB//AAH/8AAf/wAB//AAH///8f///x////AAAAAAAAAB////H///8f///wAAAAAAAAAAAAAAABAAAAOAAAB4AAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAzAAAPMAAA/wAAAeAAAAAAAAAAAAAAAAAAAIAAABgAAAMAAAA//AAD/8AAAAAAAAAAAAAAAAAAAAAA8AAAP4AAAwwAADDAAAMMAAA5wAAB+AAADwAAAAAAAAAAAAAAAAAMAwAA8HAAB44AAD/AAAD4AADGMAAOBwAAeOAAA/wAAA+AAABgAAAAAAAAAAAAAAABAAAAMAAABwAAAH/8CAf/wcAAAHgAAA8AAAHgAAB4AAAPAAAB4AAAeAAADwAAA+AAAHgCAA8A8APAfwB4H7AHB+MAAHAwAAQ/wAAD/AAAAwAAADAAAAAAAAAAAAAAAAAAAAEAAAAwAAAHAAAAf/wIB//BwAAAeAAADwAAAeAAAHgAAA8AAAHgAAB4AAAPAAAD4AAAeAAADwAAA8GAwHg4HAcHA8AAYHwABg7AAGHMAAf4wAA/DAAA4MAAAAAAAAAAYBgABgHAAGMOAAZwYABvBgAH8OCAe/wcBx+HgABg8AAAHgAAB4AAAPAAAB4AAAeAAADwAAA+AAAHgHAA8B8APAfwB4HzADB8MAAHAwAAQ/wAAD/AAAAwAAAAAAAAAAAAAAAAAA4AAAP4AAB/wAAPHgABwOA4/A4Dn4DgOfAOAAAA4AAAHgAAB8AAAHgAAAYAAAAAAAAEAAADwAAB/AAA/8AAf+AAP/gAH/OAB/g4AHwDgAfAOAB/g4AD/zgAA/+AAAf8AAAP+AAAH8AAADwAAABAAAAEAAADwAAB/AAA/8AAf+AAP/gAH/OAB/g4AHwDgAfAOAB/g4AD/zgAA/+AAAf8AAAP+AAAH8AAADwAAABAAAAEAAADwAAB/AAA/8AAf+AAP/gAH/OAB/g4AHwDgAfAOAB/g4AD/zgAA/+AAAf8AAAP+AAAH8AAADwAAABAAAAEAAADwAAB/AAA/8AAf+AAP/gAH/OAB/g4AHwDgAfAOAB/g4AD/zgAA/+AAAf8AAAP+AAAH8AAADwAAABAAAAEAAADwAAB/AAA/8AAf+AAP/gAH/OAB/g4AHwDgAfAOAB/g4AD/zgAA/+AAAf8AAAP+AAAH8AAADwAAABAAAAAAAAABwAAA/AAAf8AAP/AAH/wfD/nD+/wcMb4BwxvgHD+/wcHx/5wEAf/AAAP+AAAH/AAAD8AAABwAAAAAAAEAAADwAAB/AAA/8AAf+AAP/gAH/OAB/g4AHwDgAcAOABwA4AHADgAf//8B///wHA4HAcDgcBwOBwHA4HAcDgcBwOBwHA4HAcDgcBwOBwHAAHAYAAMAAAAAAAAAAA/4AAP/4AD//4APAHgB4APAPAAeA4AA4DgADg+AAPz4AA//gAD/+AAOe4AA4BwAHAHgA8APgPgAeA8AAYDAAAAAAAAAAAAAAAAf//8B///wH///AcDgcBwOBwHA4HAcDgcBwOBwHA4HAcDgcBwOBwHAAHAcAAcAAAAAAAAAAAAAAB///wH///Af//8BwOBwHA4HAcDgcBwOBwHA4HAcDgcBwOBwHA4HAcAAcBwABwAAAAAAAAAAAAAAH///Af//8B///wHA4HAcDgcBwOBwHA4HAcDgcBwOBwHA4HAcDgcBwABwHAAHAAAAAAAAAAAAAAAf//8B///wH///AcDgcBwOBwHA4HAcDgcBwOBwHA4HAcDgcBwOBwHAAHAcAAcAAAAAAAAAAAAAAB///wH///Af//8AAAAAAAAAAAAAAAAAAAH///Af//8B///wAAAAAAAAAAAAAAAAAAAf//8B///wH///AAAAAAAAAAAAAAAAAAAB///wH///Af//8AAAAAAAAAAABgAAAGAAH///Af//8B///wHAYHAcBgcBwGBwHAYHAcBgcBwABwHAAHAeAA8A8AHgB+D8AD//gAH/8AAD+AAAAAAAAAAAAAAAAf//8B///wH///APwAAAPgAAAfgAAAfAAAA/AAAA+AAAA+AAAB8AAAB8AAAD8B///wH///Af//8AAAAAAAAAAAf8AAH/8AB//8AHgDwA8AHgHgAPAcAAcBwABwHAAHAcAAcBwABwHAAHAcAAcA4ADgDwAeAH4PwAP/+AAf/wAAP4AAAAAAAH/AAB//AAf//AB4A8APAB4B4ADwHAAHAcAAcBwABwHAAHAcAAcBwABwHAAHAOAA4A8AHgB+D8AD//gAH/8AAD+AAAAAAAB/wAAf/wAH//wAeAPADwAeAeAA8BwABwHAAHAcAAcBwABwHAAHAcAAcBwABwDgAOAPAB4Afg/AA//4AB//AAA/gAAAAAAAf8AAH/8AB//8AHgDwA8AHgHgAPAcAAcBwABwHAAHAcAAcBwABwHAAHAcAAcA4ADgDwAeAH4PwAP/+AAf/wAAP4AAAAAAAH/AAB//AAf//AB4A8APAB4B4ADwHAAHAcAAcBwABwHAAHAcAAcBwABwHAAHAOAA4A8AHgB+D8AD//gAH/8AAD+AAAAAAAAAAAAGDgAA8eAAB7wAAD+AAAHwAAAfAAAD+AAAe8AADw4AAGBAAAAAAAAAAAAAAAAAf8MAH//4B///AHgD4A8AfgHgD/AcAecBwDxwHAeHAcDwcBw+BwHHgHAc8AcA/gDgD8AeAH4PwA//+AH//wAMP4AAAAAAAAAAAf//AB//+AH//8AAAB4AAADwAAAHAAAAcAAABwAAAHAAAAcAAABwAAAOAAAB4B///AH//4Af/+AAAAAAAAAAAAAAAAAAAAH//wAf//gB///AAAAeAAAA8AAABwAAAHAAAAcAAABwAAAHAAAAcAAADgAAAeAf//wB//+AH//gAAAAAAAAAAAAAAAAAAAB//8AH//4Af//wAAAHgAAAPAAAAcAAABwAAAHAAAAcAAABwAAAHAAAA4AAAHgH//8Af//gB//4AAAAAAAAAAAAAAAAAAAAf//AB//+AH//8AAAB4AAADwAAAHAAAAcAAABwAAAHAAAAcAAABwAAAOAAAB4B///AH//4Af/+AAAAAAAAAAAQAAABwAAAHwAAAPwAAAfwAAAfgAAAfgAAAf/wAB//AAf/8AH8AAA/AAAPwAAB8AAAHAAAAQAAAAAAAAAAAAAf//8B///wH///ABwHAAHAcAAcBwABwHAAHAcAAcBwABwHAAHg8AAP/gAAf8AAA/gAAAAAAAAAAAAAAAA///AP//8A///wHgAAAcAAcBwABwHBwHAcHAcB4+BwD/4PAH954APn/gAAP8AAAOAAAAAAAAAAAAAD4AAcfwADz/gAfOOCBww4PHHBg+ccGAZxw4AHHDAAcYcAA//gAD//gAD/+AAAAAAAAAAAAAAAAAPgABx/AAPP+AB844AHDDgAccGAZxwYPnHDg8ccMDBxhwAD/+AAP/+AAP/4AAAAAAAAAAAAAAAAA+AAHH8AA8/4BnzjgOcMOBxxwYOHHBg4ccOBxxwwDnGHAGP/4AA//4AA//gAAAAAAAAAAAAAAAAHwAA4/gAHn/A8+ccDzhhwMOODA444MBjjhwHOOGAM4w4Dx//AOH//AAH/8AAAAAAAAAAAAAAAAAfAADj+AAef8Bz5xwHOGHAc44MADjgwAOOHAY44YBzjDgHH/8AAf/8AAf/wAAAAAAAAAAAAAAAAAfAADj+AAef8AD5xweOGHD844MMzjgwzOOHD844YHjjDgAH/8AAf/8AAf/wAAAAAAAAAAAAAAAAHwAAx/gAHn/AAc4cADjhwAOMDAA4wcADjBwAOMHAA4w4AB//AAH/4AAP/wAB/fgAPMPAA4wcADjBwAOMHAA4wcADjBwAPMPAAfx4AA/HAAB8YAAAAAAAAAAAA/AAAP/AAB/+AAPA8AB4B4AHADgwcAPzBwA/8HADngcAOMB4B4ADwPAAHA4AAMDAAAAAAAAAAAAA/AAAP/AAB/+AAPY8AB5h4OHGDg+cYOB5xg4AnGDgAcYOAB5h4AD+PAAH44AAPjAAAGAAAAAAAAAAAAAPwAAD/wAAf/gAD2PAAeYeABxg4AHGDgOcYOD5xg4OHGDggeYeAA/jwAB+OAAD4wAABgAAAAAAAAAAAAD8AAA/8AAH/4AY9jwDnmHgecYODhxg4OHGDg8cYOB5xg4BnmHgCP48AAfjgAA+MAAAYAAAAAAAAAAAAB+AAAf+AAD/8Acex4BzzDwHOMHAA4wcADjBwAOMHAc4wcBzzDwGH8eAAPxwAAfGAAAMAAAAAAAAAAAOAAAA+f/+A5//4An//gAAAAAAAAAAAAAAAAAAAJ//4Dn//g+f/+DgAAAAAAAAMAAABwAAAOP//Aw//8Dj//wHAAAAMAAABwAAAHAAAAA//8AD//wAP//AcAAABwAAAAAAAAAA/gAAP/AAB//AAPA8AA4A4DDgDgPMAOA/wA4D7ADgPOAOB+8B4C/+/AA//4AB//AAAHAAAAAAAAAAAAP//AA//8Bx//wPHAAAw4AADjgAAGOAAAc4AAAzgAAPPAAA4f/8AA//wAB//AAAAAAAAAAAAAAAAA/AAAP/AAB/+AAPA8CB4B4OHADg+cAOA5wA4AHADgAcAOAB4B4AD4fAAH/4AAP/AAAPwAAAAAAAAAAAAPwAAD/wAAf/gADwPAAeAeABwA4AnADgecAOD5wA4OHADgAeAeAA+HwAB/+AAD/wAAD8AAAAAAAAAAAAD8AAA/8AAH/4AY8DwDngHgecAODhwA4OHADg8cAOB5wA4BngHgCPh8AAf/gAA/8AAA/AAAAAAAAAAAAB+AAAf+AAD/8AceB4DzwDwMOAHA44AcBjgBwHOAHAM4AcDzwDwOHw+AAP/wAAf+AAAfgAAAAAAAAAAAAfgAAH/gAA//AHHgeAc8A8BzgBwAOAHAA4AcADgBwHOAHAc8A8Bh8PgAD/8AAH/gAAH4AAAAAAAAAAAAMAAAAwAAADAAAAMAAAAwAAADAAADtwAAO3AAA7cAAAMAAAAwAAADAAAAMAAAAAAAAAAAAAH5gAB//AAP/4AB4PgAPB/AA4PcADh5wAOPHAA54cADvBwAP4PAAfD4AB//AAP/4AAZ+AAAAAAAAAAAAf8AAB//AAH//AAAH8AAAB4OAADg+AAOB4AA4AgADgAAAOAAABwAH//gAf/+AB//4AAAAAAAAAAAAAAAH/AAAf/wAB//wAAB/AAAAeAAAA4BgADgeAAOD4AA4MAADgAAAcAB//4AH//gAf/+AAAAAAAAAAAAAAAB/wAAH/8AAf/8AYAfwDgAHgcAAODgAA4OAADg8AAOB4AA4BgAHACf/+AB//4AH//gAAAAAAAAAAAAAAA/4AAD/+AAP/+AcAP4BwADwHAAHAAAAcAAABwAAAHAcAAcBwADgGP//AA//8AD//wAAAAAAAAAABAAAAHAAAAfgAAA/wAAA/wAAAf4cAAP/zgAP/+AB/jgA/wAAf4AAP+AAB/AAAHgAAAQAAAAAAAAAAAA//////////////A4BwAHADgAcAOABwA4AHADgAcAOAB4B4AD4fAAH/4AAP/AAAPwAAAAAABAAAAHAAAAfgAAw/wADg/wA+Af4fAAP/8AAP/AAB/g4A/wDgf4AOP+AAB/AAAHgAAAQAAA=='),
+ 32,
+ atob("BgkMGhEZEgYMDAwQCAwICxILEBAREBEOEREJCREVEQ8ZEhEUExAOFBQHDREPGBMUERQSEhEUERsREBIMCwwTEg4QERAREQoREQcHDgcYEREREQoPDBEPFg8PDwwIDBMcCgoAAAAAAAAAAAAAACERESEAAAAAAAAAAAAAAAAhIQAGCRAQEhAIDw8XCQ8RABIODRELCw4REwcLCQoPHBscDxISEhISEhoUEBAQEAcHBwcTExQUFBQUDhQUFBQUEBEREBAQEBAQGhARERERBwcHBxAREREREREPEREREREPEQ8="),
+ 28+(scale<<8)+(1<<16)
+ );
return this;
};
-var imgLock = {
- width : 16, height : 16, bpp : 1,
- transparent : 0,
- buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w="))
+
+Graphics.prototype.setMiniFont = function(scale) {
+ // Actual height 16 (15 - 0)
+ this.setFontCustom(
+ atob('AAAAAAAAAAAAAP+w/5AAAAAA4ADgAOAA4AAAAAAAAAABgBmAGbAb8D+A+YDZ8B/wf4D5gJmAGQAQAAAAAAAeOD8cMwzxj/GPMYwc/Az4AAAAAHAA+DDIYMjA+YBzAAYADeA7MHMw4zDD4ADAAAAz4H/wzjDHMMMwwbBj4APgADAAAAAA4ADgAAAAAAAAAAfwH/54B+ABAAAAAOABeAcf/gfwAAAAACAAaAD4APgAOABgAAAAAAACAAIAAgA/wAMAAgACAAAAAAAAPAA4AAAAAAIAAgACAAIAAgAAAAAAADAAMAAAAAAAcAfwf4D4AIAAAAA/wH/gwDDAMMAwwDB/4D/AAAAAAGAAwAD/8P/wAAAAAHAw8HDA8MHww7DnMH4wGBAAAMBgyHDcMPww/DDv4MfAAAAAAAHgD+A+YPhgwGAH8AfwAEAAAAAA/GD8cMwwzDDMMM5wx+ABgAAAP8B/4MwwzDDMMMwwx+ADwAAAgADAAMBwwfDPgP4A8ADAAAAAe+D/8M4wxjDGMP5wf+ABwAAAfAB+cMYwwjDCMMYwf+A/wAAAAAAAAAxgBCAAAAAAAAAYPBA4AAAAAAAAAgAHAA+AHMAYYAAAAAAAAAAAAAAJAAkACQAJAAkACQAJAAkAAAAAAAAAAAAAABhgHMAPgAcAAgAAAAAAAABgAOAAwbDDsMYA/AA4AAAAAAAD4A/wGBgxzDPsMyQjJDPkM+wYIBxgD+AAAAAAABAA8A/gf8DwwODA/sAfwAHwADAAAP/w//DGMMYwxjDOMP9we+ABwA8AP8Bw4MAwwDDAMMAwwDDgcHDgMMAAAAAA//D/8MAwwDDAMMAw4HB/4D/AAAAAAP/w//DGMMYwxjDGMMQwgBAAAP/w//DDAMMAwwDDAMAADwA/wHDgwDDAMMAwwDDCMOJwc+ADwAAA//D/8AMAAwADAAMAAwD/8P/wAAAAAP/w//AAAABgAHAAMAAwAHD/4P+AAAAAAP/w//AOAB+AOcBw4MBwgDAAEAAA//D/8AAwADAAMAAwADAAAP/w//A8AA8AA+AA8AHwB8AeAHgA//D/8AAAAAD/8P/wcAAcAA8AA4AB4P/w//AAAA8AP8Bw4MAwwDDAMMAwwDDgcH/gP8AAAAAA//D/8MMAwwDDAMYA7gB8ABgADwA/wHDgwDDAMMAwwDDA8ODwf/A/8AAAAAD/8P/wwwDDAMMAx4Dv4HxwEBAAAHjg/HDMMMYwxjDGMONwc+ABwMAAwADAAMAA//D/8MAAwADAAIAAAAD/wP/gAHAAMAAwADAAMAHg/8AAAMAA+AA/AAfgAPAA8AfgPwD4AMAAwAD4AD+AA/AA8A/g/gDwAP4AH8AB8APwH8D8AMAAgBDAMPDgO8APAB8AOcDw8MAwgBCAAOAAeAAeAAfwH/B4AOAAwAAAAMAwwPDB8Mew3jD4MPAwwDAAAAAAAAB//3//QABAAAAAAADgAP4AH+AB8AAQAABAAEAAf/9//wAAAAAAAAAAGAAwAGAAwABgADAAGAAAAAAAAAQABAAEAAQABAAEAAQABAAEAAQABAAAAAAAAAAAAAAAAAAAAAAAQA3wHbAZMBswGzAf4A/wAAAAAP/w//AYMBgwGDAYMA/gB8AAAAEAD+Ae8BgwGDAYMBgwDGAAAAMAD+Ae8BgwGDAYMBhw//D/8AAAAYAP4B/wGTAZMBkwGTAP4AcAEAAYAP/w//CQAJAAAwAP4hz3GDMQMxAzGHcf/h/8AAAAAP/w//AYABgAGAAYAA/wB/AAAAAA3/Df8AAAAAOf/9//AAAAAP/w//ADgAfADGAYMBAQAAD/8P/wAAAAAB/wH/AYABgAGAAf8A/wGAAYABgAH/AP8AAAAAAf8B/wGAAYABgAGAAP8AfwAAADAA/gHvAYMBgwGDAYMA/gB8AAAAAAH/8f/xgwGDAYMBgwD+AHwAAAAwAP4B7wGDAYMBgwGHAf/x//AAAAAB/wH/AYABgAEAAAAA5gHzAbMBkwGbAd8AzgEAAYAP/wf/AQMBAwAAAAAB/gH/AAMAAwADAAcB/wH/AAABAAHgAPwAHwAPAH4B8AGAAQAB8AB+AA8APwHwAeAA/AAPAD8B+AHAAQEBgwHOAHwAOAD+AccBAwAAAQAB4AD4EB/wB8A/APgBwAAAAAEBgwGPAZ8B8wHjAcMBAQAAAAAABgf/9/n2AAAAAAAP/w//AAAEAAYAB/nz//AGAAAAAAAAAAAAcABgAGAAcAAwAHAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'),
+ 32,
+ atob("AwUHDwoOCwQHBwcJBAcEBgoGCQkKCQoICQoFBQoMCgkPCgoMCwkICwsECAoIDgsMCgwKCgoLCg8KCQoHBgcLCwgJCgkKCQYKCgQECAQOCgoKCgYIBwoIDAkJCAcEBwsQ"),
+ 16+(scale<<8)+(1<<16)
+ );
+ return this;
};
-var imgSteps = {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("/H///wv4CBn4CD8ACCj4IBj8f+Eeh/wjgCBngCCg/4nEH//4h/+jEP/gRBAQX+jkf/wgB//8GwP4FoICDHgICCBwIA=="))
-};
-var imgBattery = {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("/4AN4EAg4TBgd///9oEAAQv8ARQRDDQQgCEwQ4OA"))
-};
-var imgBpm = {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("/4AOn4CD/wCCjgCCv/8jF/wGYgOA5MB//BC4PDAQnjAQPnAQgANA"))
-};
+function imgLock(){
+ return {
+ width : 16, height : 16, bpp : 1,
+ transparent : 0,
+ buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w="))
+ }
+}
-var imgTemperature = {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("//D///wICBjACBngCNkgCP/0kv/+s1//nDn/8wICEBAIOC/08v//IYJECA=="))
-};
+function imgSteps(){
+ return {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("/H///wv4CBn4CD8ACCj4IBj8f+Eeh/wjgCBngCCg/4nEH//4h/+jEP/gRBAQX+jkf/wgB//8GwP4FoICDHgICCBwIA=="))
+ }
+}
-var imgWind = {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("/0f//8h///Pn//zAQXzwf/88B//mvGAh18gEevn/DIICB/PwgEBAQMHBAIADFwM/wEAGAP/54CD84CE+eP//wIQU/A=="))
-};
+function imgBattery(){
+ return {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("/4AN4EAg4TBgd///9oEAAQv8ARQRDDQQgCEwQ4OA"))
+ }
+}
-var imgTimer = {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("/+B/4CD84CEBAPygFP+F+h/x/+P+fz5/n+HnAQNn5/wuYCBmYCC5kAAQfOgFz80As/ngHn+fD54mC/F+j/+gF/HAQA=="))
-};
+function imgCharging() {
+ return {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("//+v///k///4AQPwBANgBoMxBoMb/P+h/w/kH8H4gfB+EBwfggHH4EAt4CBn4CBj4CBh4FCCIO/8EB//Agf/wEH/8Gh//x////fAQIA="))
+ }
+}
-var imgCharging = {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("//+v///k///4AQPwBANgBoMxBoMb/P+h/w/kH8H4gfB+EBwfggHH4EAt4CBn4CBj4CBh4FCCIO/8EB//Agf/wEH/8Gh//x////fAQIA="))
-};
+function imgBpm() {
+ return {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("/4AOn4CD/wCCjgCCv/8jF/wGYgOA5MB//BC4PDAQnjAQPnAQgANA"))
+ }
+}
-var imgWatch = {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("/8B//+ARANB/l4//5/1/+f/n/n5+fAQnf9/P44CC8/n7/n+YOB/+fDQQgCEwQsCHBBEC"))
-};
+function imgTemperature() {
+ return {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("//D///wICBjACBngCNkgCP/0kv/+s1//nDn/8wICEBAIOC/08v//IYJECA=="))
+ }
+}
+function imgWeather(){
+ return {
+ width : 24, height : 24, bpp : 1,
+ transparent : 0,
+ buffer : require("heatshrink").decompress(atob("AAcYAQ0MgEwAQUAngLB/8AgP/wACCgf/4Fz//OAQQICCIoaCEAQpGHA4ACA="))
+ }
+}
+
+function imgWind () {
+ return {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("/0f//8h///Pn//zAQXzwf/88B//mvGAh18gEevn/DIICB/PwgEBAQMHBAIADFwM/wEAGAP/54CD84CE+eP//wIQU/A=="))
+ }
+}
+
+function imgHumidity () {
+ return {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("//7///+YCB+ICB8ACE4F/AQX9AQP54H//AOB+F/34CBj/gn8f4E+h/Aj0H4Ecg+AjED4ACE8E4gfwvEDEgICB/kHGwMP"))
+ }
+}
+
+function imgTimer() {
+ return {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("/+B/4CD84CEBAPygFP+F+h/x/+P+fz5/n+HnAQNn5/wuYCBmYCC5kAAQfOgFz80As/ngHn+fD54mC/F+j/+gF/HAQA=="))
+ }
+}
+
+function imgWatch() {
+ return {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("/8B//+ARANB/l4//5/1/+f/n/n5+fAQnf9/P44CC8/n7/n+YOB/+fDQQgCEwQsCHBBEC"))
+ }
+}
+
+function imgHomeAssistant() {
+ return {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("/4AF84CB4YCBwICBCAP+jFH/k8g/4kkH+AFB8ACB4cY4eHzPhgmZkHnzPn8fb4/gvwUD8EYARhAC"))
+ }
+}
+
+function imgAgenda() {
+ return {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("/4AFnPP+ALBAQX4CIgLFAQvggEBAQvAgEDAQMCwEAgwTBhgiB/AlCGQ8BGQQ"))
+ }
+}
+
+function imgMountain() {
+ return {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : atob("//////////////////////3///n///D//uZ//E8//A+/+Z+f8//P5//n7//3z//zn//5AAAAAAAA////////////////////")
+ }
+}
+
+/************
+ * 2D MENU with entries of:
+ * [name, icon, opt[customDownFun], opt[customUpFun], opt[customCenterFun]]
+ *
+ */
+var menu = [
+ [
+ function(){ return [ null, null ] },
+ function(){ return [ "Week " + weekOfYear(), null ] },
+ ],
+ [
+ function(){ return [ "Bangle", imgWatch() ] },
+ function(){ return [ E.getBattery() + "%", Bangle.isCharging() ? imgCharging() : imgBattery() ] },
+ function(){ return [ getSteps(), imgSteps() ] },
+ function(){ return [ Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", imgBpm()] },
+ function(){ return [ measureAltitude, imgMountain() ]},
+ ]
+]
/*
- * INFO ENTRIES
+ * Timer Menu
*/
-var infoArray = [
- function(){ return [ null, null, "left" ] },
- function(){ return [ "Bangle", imgWatch, "right" ] },
- function(){ return [ E.getBattery() + "%", imgBattery, "left" ] },
- function(){ return [ getSteps(), imgSteps, "left" ] },
- function(){ return [ Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", imgBpm, "left"] },
- function(){ return [ getWeather().temp, imgTemperature, "left" ] },
- function(){ return [ getWeather().wind, imgWind, "left" ] },
-];
-const NUM_INFO=infoArray.length;
-
-
-function getInfoEntry(){
- if(isAlarmEnabled()){
- return [getAlarmMinutes() + " min.", imgTimer, "left"]
- } else if(Bangle.isCharging()){
- return [E.getBattery() + "%", imgCharging, "left"]
- } else{
- return infoArray[settings.showInfo]();
- }
+try{
+ require('sched');
+ menu.push([
+ function(){
+ var text = isAlarmEnabled(TIMER_IDX) ? getAlarmMinutes(TIMER_IDX) + " min." : "Timer";
+ return [text, imgTimer(), () => decreaseAlarm(TIMER_IDX), () => increaseAlarm(TIMER_IDX), null ]
+ },
+ ]);
+} catch(ex) {
+ // If sched is not installed, we hide this menu item
}
/*
+ * AGENDA MENU
+ * Note that we handle the agenda differently in order to hide old entries...
+ */
+var agendaIdx = 0;
+var agendaTimerIdx = 0;
+if(storage.readJSON("android.calendar.json") !== undefined){
+ function nextAgendaEntry(){
+ agendaIdx += 1;
+ }
+
+ function previousAgendaEntry(){
+ agendaIdx -= 1;
+ }
+
+ menu.push([
+ function(){
+ var now = new Date();
+ var agenda = storage.readJSON("android.calendar.json")
+ .filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000)
+ .sort((a,b)=>a.timestamp - b.timestamp);
+
+ if(agenda.length <= 0){
+ return ["All done", imgAgenda()]
+ }
+
+ agendaIdx = agendaIdx < 0 ? 0 : agendaIdx;
+ agendaIdx = agendaIdx >= agenda.length ? agendaIdx -1 : agendaIdx;
+
+ var entry = agenda[agendaIdx];
+ var title = entry.title.slice(0,14);
+ var date = new Date(entry.timestamp*1000);
+ var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
+ dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
+
+ function dynImgAgenda(){
+ if(isAlarmEnabled(TIMER_AGENDA_IDX) && agendaTimerIdx == agendaIdx){
+ return imgTimer();
+ } else {
+ return imgAgenda();
+ }
+ }
+
+ return [title + "\n" + dateStr, dynImgAgenda(), () => nextAgendaEntry(), () => previousAgendaEntry(), function(){
+ try{
+ var alarm = require('sched')
+
+ // If other time, we disable the old one and enable this one.
+ if(agendaIdx != agendaTimerIdx){
+ agendaTimerIdx = -1;
+ alarm.setAlarm(TIMER_AGENDA_IDX, undefined);
+ }
+
+ // Disable alarm if enabled
+ if(isAlarmEnabled(TIMER_AGENDA_IDX)){
+ agendaTimerIdx = -1;
+ alarm.setAlarm(TIMER_AGENDA_IDX, undefined);
+ alarm.reload();
+ return
+ }
+
+ // Otherwise, set alarm for given event
+ agendaTimerIdx = agendaIdx;
+ alarm.setAlarm(TIMER_AGENDA_IDX, {
+ msg: title,
+ timer : parseInt((date - now)),
+ });
+ alarm.reload();
+ } catch(ex){ }
+ }]
+ },
+ ]);
+}
+
+
+/*
+ * WEATHER MENU
+ */
+if(storage.readJSON('weather.json') !== undefined){
+ menu.push([
+ function(){ return [ "Weather", imgWeather() ] },
+ function(){ return [ getWeather().temp, imgTemperature() ] },
+ function(){ return [ getWeather().hum, imgHumidity() ] },
+ function(){ return [ getWeather().wind, imgWind() ] },
+ ]);
+}
+
+
+/*
+ * HOME ASSISTANT MENU
+ */
+try{
+ var triggers = require("ha.lib.js").getTriggers();
+ var haMenu = [
+ function(){ return [ "Home", imgHomeAssistant() ] },
+ ];
+
+ triggers.forEach(trigger => {
+ haMenu.push(function(){
+ return [trigger.display, trigger.getIcon(), () => {}, () => {}, function(){
+ var ha = require("ha.lib.js");
+ ha.sendTrigger("TRIGGER_BW");
+ ha.sendTrigger(trigger.trigger);
+ }]
+ });
+ })
+ menu.push(haMenu);
+} catch(ex){
+ // If HomeAssistant is not installed, we hide this item
+}
+
+
+function getMenuEntry(){
+ // In case the user removes HomeAssistant entries, showInfo
+ // could be larger than infoArray.length...
+ settings.menuPosX = settings.menuPosX % menu.length;
+ settings.menuPosY = settings.menuPosY % menu[settings.menuPosX].length;
+ var menuEntry = menu[settings.menuPosX][settings.menuPosY]();
+
+ if(menuEntry[0] == null){
+ return menuEntry;
+ }
+
+ // For the first entry we always convert it into a callback function
+ // such that the menu is compatible with async functions such as
+ // measuring the pressure, altitude or sending http requests...
+ if(typeof menuEntry[0] !== 'function'){
+ var value = menuEntry[0];
+ menuEntry[0] = function(callbackFun){
+ callbackFun(String(value), settings.menuPosX, settings.menuPosY);
+ }
+ }
+ return menuEntry;
+}
+
+
+/************
* Helper
*/
+function isFullscreen(){
+ var s = settings.screen.toLowerCase();
+ if(s == "dynamic"){
+ return Bangle.isLocked()
+ } else {
+ return s == "full"
+ }
+}
+
function getSteps() {
var steps = 0;
try{
@@ -149,8 +387,7 @@ function getSteps() {
// In case we failed, we can only show 0 steps.
}
- steps = Math.round(steps/100) / 10; // This ensures that we do not show e.g. 15.0k and 15k instead
- return steps + "k";
+ return steps;
}
@@ -169,7 +406,7 @@ function getWeather(){
// Wind
const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
- weather.wind = Math.round(wind[1]) + " km/h";
+ weather.wind = Math.round(wind[1]) + "kph";
return weather
@@ -178,19 +415,33 @@ function getWeather(){
}
return {
- temp: "? °C",
- hum: "-",
- txt: "-",
- wind: "? km/h",
- wdir: "-",
- wrose: "-"
+ temp: " ? ",
+ hum: " ? ",
+ txt: " ? ",
+ wind: " ? ",
+ wdir: " ? ",
+ wrose: " ? "
};
}
-function isAlarmEnabled(){
+// From https://weeknumber.com/how-to/javascript
+function weekOfYear() {
+ var date = new Date();
+ date.setHours(0, 0, 0, 0);
+ // Thursday in current week decides the year.
+ date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
+ // January 4 is always in week 1.
+ var week1 = new Date(date.getFullYear(), 0, 4);
+ // Adjust to Thursday in week 1 and count number of weeks from date to week1.
+ return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000
+ - 3 + (week1.getDay() + 6) % 7) / 7);
+}
+
+
+function isAlarmEnabled(idx){
try{
var alarm = require('sched');
- var alarmObj = alarm.getAlarm(TIMER_IDX);
+ var alarmObj = alarm.getAlarm(idx);
if(alarmObj===undefined || !alarmObj.on){
return false;
}
@@ -201,37 +452,40 @@ function isAlarmEnabled(){
return false;
}
-function getAlarmMinutes(){
- if(!isAlarmEnabled()){
+
+function getAlarmMinutes(idx){
+ if(!isAlarmEnabled(idx)){
return -1;
}
var alarm = require('sched');
- var alarmObj = alarm.getAlarm(TIMER_IDX);
+ var alarmObj = alarm.getAlarm(idx);
return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
}
-function increaseAlarm(){
+
+function increaseAlarm(idx){
try{
- var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0;
- var alarm = require('sched')
- alarm.setAlarm(TIMER_IDX, {
+ var minutes = isAlarmEnabled(idx) ? getAlarmMinutes(idx) : 0;
+ var alarm = require('sched');
+ alarm.setAlarm(idx, {
timer : (minutes+5)*60*1000,
});
alarm.reload();
} catch(ex){ }
}
-function decreaseAlarm(){
+
+function decreaseAlarm(idx){
try{
- var minutes = getAlarmMinutes();
+ var minutes = getAlarmMinutes(idx);
minutes -= 5;
var alarm = require('sched')
- alarm.setAlarm(TIMER_IDX, undefined);
+ alarm.setAlarm(idx, undefined);
if(minutes > 0){
- alarm.setAlarm(TIMER_IDX, {
+ alarm.setAlarm(idx, {
timer : minutes*60*1000,
});
}
@@ -241,10 +495,26 @@ function decreaseAlarm(){
}
-/*
- * DRAW functions
- */
+function measureAltitude(callbackFun){
+ var oldX = settings.menuPosX;
+ var oldY = settings.menuPosY;
+ try{
+ Bangle.getPressure().then(data=>{
+ if(data && data.altitude && data.altitude > -100){
+ callbackFun(Math.round(data.altitude) + "m", oldX, oldY);
+ } else {
+ callbackFun("???", oldX, oldY);
+ }
+ });
+ }catch(ex){
+ callbackFun("err", oldX, oldY);
+ }
+}
+
+/************
+ * DRAW
+ */
function draw() {
// Queue draw again
queueDraw();
@@ -259,11 +529,12 @@ function draw() {
function drawDate(){
// Draw background
- var y = H/5*2 + (settings.fullscreen ? 0 : 8);
+ var y = H/5*2;
g.reset().clearRect(0,0,W,W);
// Draw date
- y -= settings.fullscreen ? 8 : 0;
+ y = parseInt(y/2)+4;
+ y += isFullscreen() ? 0 : 13;
var date = new Date();
var dateStr = date.getDate();
dateStr = ("0" + dateStr).substr(-2);
@@ -276,76 +547,99 @@ function drawDate(){
var dayW = Math.max(g.stringWidth(dayStr), g.stringWidth(monthStr));
var fullDateW = dateW + 10 + dayW;
- g.setFontAlign(-1,1);
+ g.setFontAlign(-1,0);
+ g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-12);
+ g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+11);
+
g.setMediumFont();
g.setColor(g.theme.fg);
- g.drawString(dateStr, W/2 - fullDateW / 2, y+5);
-
- g.setSmallFont();
- g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+3);
- g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-23);
+ g.drawString(dateStr, W/2 - fullDateW / 2, y+2);
}
function drawTime(){
// Draw background
- var y = H/5*2 + (settings.fullscreen ? 0 : 8);
+ var y = H/5*2 + (isFullscreen() ? 0 : 8);
g.setColor(g.theme.fg);
g.fillRect(0,y,W,H);
var date = new Date();
// Draw time
g.setColor(g.theme.bg);
- g.setFontAlign(0,-1);
- var timeStr = locale.time(date,1);
- y += settings.fullscreen ? 14 : 10;
+ g.setFontAlign(0,0);
- var infoEntry = getInfoEntry();
- var infoStr = infoEntry[0];
- var infoImg = infoEntry[1];
- var printImgLeft = infoEntry[2] == "left";
+ var hours = String(date.getHours());
+ var minutes = date.getMinutes();
+ minutes = minutes < 10 ? String("0") + minutes : minutes;
+ var colon = settings.hideColon ? "" : ":";
+ var timeStr = hours + colon + minutes;
+
+ // Set y coordinates correctly
+ y += parseInt((H - y)/2) + 5;
+
+ var menuEntry = getMenuEntry();
+ var menuTextFun = menuEntry[0];
+ var menuImg = menuEntry[1];
+ var printImgLeft = settings.menuPosY != 0;
// Show large or small time depending on info entry
- if(infoStr == null){
- y += 10;
+ if(menuTextFun == null){
g.setLargeFont();
- } else {
- g.setMediumFont();
- }
- g.drawString(timeStr, W/2, y);
-
- // Draw info if set
- if(infoStr == null){
+ g.drawString(timeStr, W/2, y);
return;
+ } else {
+ y -= 15;
+ g.setMediumFont();
+ g.drawString(timeStr, W/2, y);
}
- y += H/5*2-5;
- g.setFontAlign(0,0);
- g.setSmallFont();
- var imgWidth = 0;
- if(infoImg !== undefined){
- imgWidth = infoImg.width;
- var strWidth = g.stringWidth(infoStr);
- g.drawImage(
- infoImg,
- W/2 + (printImgLeft ? -strWidth/2-2 : strWidth/2+2) - infoImg.width/2,
- y - infoImg.height/2
- );
- }
- g.drawString(infoStr, printImgLeft ? W/2 + imgWidth/2 + 2 : W/2 - imgWidth/2 - 2, y+3);
+ // Async set the menu (could be that some data is async fetched)
+ menuTextFun((menuText, oldX, oldY) => {
+
+ // We display the text IFF the user did not change the menu
+ if(settings.menuPosX != oldX || settings.menuPosY != oldY){
+ return;
+ }
+
+ // As its a callback, we have to ensure that the color
+ // font etc. is still correct...
+ g.setColor(g.theme.bg);
+ g.setFontAlign(0,0);
+ y += 35;
+
+ if(menuText.split('\n').length > 1){
+ g.setMiniFont();
+ } else {
+ g.setSmallFont();
+ }
+
+ var imgWidth = 0;
+ if(menuImg){
+ imgWidth = 24.0;
+ var strWidth = g.stringWidth(menuText);
+ var scale = imgWidth / menuImg.width;
+ g.drawImage(
+ menuImg,
+ W/2 + (printImgLeft ? -strWidth/2-4 : strWidth/2+4) - parseInt(imgWidth/2),
+ y - parseInt(imgWidth/2),
+ { scale: scale }
+ );
+ }
+ g.drawString(menuText, printImgLeft ? W/2 + imgWidth/2 + 2 : W/2 - imgWidth/2 - 2, y+3);
+ });
}
function drawLock(){
if(settings.showLock && Bangle.isLocked()){
g.setColor(g.theme.fg);
- g.drawImage(imgLock, W-16, 2);
+ g.drawImage(imgLock(), W-16, 2);
}
}
function drawWidgets(){
- if(settings.fullscreen){
+ if(isFullscreen()){
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
} else {
Bangle.drawWidgets();
@@ -370,17 +664,6 @@ function queueDraw() {
}
-/*
- * Load clock, widgets and listen for events
- */
-Bangle.loadWidgets();
-
-// Clear the screen once, at startup and set the correct theme.
-var bgOrig = g.theme.bg
-var fgOrig = g.theme.fg
-g.setTheme({bg:fgOrig,fg:bgOrig}).clear();
-draw();
-
// Stop updates when LCD is off, restart when on
Bangle.on('lcdPower',on=>{
if (on) {
@@ -394,57 +677,137 @@ Bangle.on('lcdPower',on=>{
Bangle.on('lock', function(isLocked) {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
+
+ if(!isLocked && settings.screen.toLowerCase() == "dynamic"){
+ // If we have to show the widgets again, we load it from our
+ // cache and not through Bangle.loadWidgets as its much faster!
+ for (let wd of WIDGETS) {wd.draw=wd._draw;wd.area=wd._area;}
+ }
+
draw();
});
Bangle.on('charging',function(charging) {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
+
+ // Jump to battery
+ settings.menuPosX = 1;
+ settings.menuPosY = 1;
draw();
});
Bangle.on('touch', function(btn, e){
- var left = parseInt(g.getWidth() * 0.2);
+ var widget_size = isFullscreen() ? 0 : 20; // Its not exactly 24px -- empirically it seems that 20 worked better...
+ var left = parseInt(g.getWidth() * 0.22);
var right = g.getWidth() - left;
- var upper = parseInt(g.getHeight() * 0.2);
+ var upper = parseInt(g.getHeight() * 0.22) + widget_size;
var lower = g.getHeight() - upper;
- var is_left = e.x < left;
- var is_right = e.x > right;
var is_upper = e.y < upper;
var is_lower = e.y > lower;
-
- if(is_upper){
- Bangle.buzz(40, 0.6);
- increaseAlarm();
- drawTime();
- }
+ var is_left = e.x < left && !is_upper && !is_lower;
+ var is_right = e.x > right && !is_upper && !is_lower;
+ var is_center = !is_upper && !is_lower && !is_left && !is_right;
if(is_lower){
Bangle.buzz(40, 0.6);
- decreaseAlarm();
+ settings.menuPosY = (settings.menuPosY+1) % menu[settings.menuPosX].length;
+
+ // Handle custom menu entry function
+ var menuEntry = getMenuEntry();
+ if(menuEntry.length > 2){
+ menuEntry[2]();
+ }
+
+ drawTime();
+ }
+
+ if(is_upper){
+ if(e.y < widget_size){
+ return;
+ }
+
+ Bangle.buzz(40, 0.6);
+ settings.menuPosY = settings.menuPosY-1;
+ settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].length-1 : settings.menuPosY;
+
+ // Handle custom menu entry function
+ var menuEntry = getMenuEntry();
+ if(menuEntry.length > 3){
+ menuEntry[3]();
+ }
+
drawTime();
}
if(is_right){
+ // A bit hacky but we ensure that always the first agenda entry is shown...
+ agendaIdx = 0;
+
Bangle.buzz(40, 0.6);
- settings.showInfo = (settings.showInfo+1) % NUM_INFO;
+ settings.menuPosX = (settings.menuPosX+1) % menu.length;
+ settings.menuPosY = 0;
drawTime();
}
if(is_left){
+ // A bit hacky but we ensure that always the first agenda entry is shown...
+ agendaIdx = 0;
+
Bangle.buzz(40, 0.6);
- settings.showInfo = settings.showInfo-1;
- settings.showInfo = settings.showInfo < 0 ? NUM_INFO-1 : settings.showInfo;
+ settings.menuPosY = 0;
+ settings.menuPosX = settings.menuPosX-1;
+ settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX;
drawTime();
}
+
+ if(is_center){
+ var menuEntry = getMenuEntry();
+ if(menuEntry.length > 4 && menuEntry[4] != null){
+ Bangle.buzz(80, 0.6).then(()=>{
+ try{
+ menuEntry[4]();
+ setTimeout(()=>{
+ Bangle.buzz(80, 0.6);
+ drawTime();
+ }, 250);
+ } catch(ex){
+ // In case it fails, we simply ignore it.
+ }
+ }
+ );
+ }
+ }
});
E.on("kill", function(){
- storage.write(SETTINGS_FILE, settings);
+ try{
+ storage.write(SETTINGS_FILE, settings);
+ } catch(ex){
+ // If this fails, we still kill the app...
+ }
});
+/*
+ * Draw clock the first time
+ */
+// The upper part is inverse i.e. light if dark and dark if light theme
+// is enabled. In order to draw the widgets correctly, we invert the
+// dark/light theme as well as the colors.
+g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear();
+
// Show launcher when middle button pressed
Bangle.setUI("clock");
+
+// Load widgets and draw clock the first time
+Bangle.loadWidgets();
+
+// Cache draw function for dynamic screen to hide / show widgets
+// Bangle.loadWidgets() could also be called later on but its much slower!
+for (let wd of WIDGETS) {wd._draw=wd.draw; wd._area=wd.area;}
+
+// Draw first time
+draw();
diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json
index 8b13cd256..b6896ab5e 100644
--- a/apps/bwclk/metadata.json
+++ b/apps/bwclk/metadata.json
@@ -1,11 +1,11 @@
{
"id": "bwclk",
"name": "BW Clock",
- "version": "0.06",
- "description": "BW Clock.",
+ "version": "0.21",
+ "description": "A very minimalistic clock to mainly show date and time.",
"readme": "README.md",
"icon": "app.png",
- "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}],
+ "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
diff --git a/apps/bwclk/screenshot.png b/apps/bwclk/screenshot.png
index b30ba4166..3a75f13d1 100644
Binary files a/apps/bwclk/screenshot.png and b/apps/bwclk/screenshot.png differ
diff --git a/apps/bwclk/screenshot_2.png b/apps/bwclk/screenshot_2.png
index ea2dc780b..31bf6373e 100644
Binary files a/apps/bwclk/screenshot_2.png and b/apps/bwclk/screenshot_2.png differ
diff --git a/apps/bwclk/screenshot_3.png b/apps/bwclk/screenshot_3.png
index fb5b153b8..8d982cac4 100644
Binary files a/apps/bwclk/screenshot_3.png and b/apps/bwclk/screenshot_3.png differ
diff --git a/apps/bwclk/screenshot_4.png b/apps/bwclk/screenshot_4.png
new file mode 100644
index 000000000..83de5c2ce
Binary files /dev/null and b/apps/bwclk/screenshot_4.png differ
diff --git a/apps/bwclk/settings.js b/apps/bwclk/settings.js
index 0fdaf1a28..116253fda 100644
--- a/apps/bwclk/settings.js
+++ b/apps/bwclk/settings.js
@@ -4,8 +4,9 @@
// initialize with default settings...
const storage = require('Storage')
let settings = {
- fullscreen: false,
+ screen: "Normal",
showLock: true,
+ hideColon: false,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
@@ -16,15 +17,16 @@
storage.write(SETTINGS_FILE, settings)
}
-
+ var screenOptions = ["Normal", "Dynamic", "Full"];
E.showMenu({
'': { 'title': 'BW Clock' },
'< Back': back,
- 'Fullscreen': {
- value: settings.fullscreen,
- format: () => (settings.fullscreen ? 'Yes' : 'No'),
- onchange: () => {
- settings.fullscreen = !settings.fullscreen;
+ 'Screen': {
+ value: 0 | screenOptions.indexOf(settings.screen),
+ min: 0, max: 2,
+ format: v => screenOptions[v],
+ onchange: v => {
+ settings.screen = screenOptions[v];
save();
},
},
@@ -35,6 +37,14 @@
settings.showLock = !settings.showLock;
save();
},
+ },
+ 'Hide Colon': {
+ value: settings.hideColon,
+ format: () => (settings.hideColon ? 'Yes' : 'No'),
+ onchange: () => {
+ settings.hideColon = !settings.hideColon;
+ save();
+ },
}
});
})
diff --git a/apps/calclock/ChangeLog b/apps/calclock/ChangeLog
new file mode 100644
index 000000000..b67f29e94
--- /dev/null
+++ b/apps/calclock/ChangeLog
@@ -0,0 +1,3 @@
+0.01: Initial version
+0.02: More compact rendering & app icon
+0.03: Tell clock widgets to hide.
diff --git a/apps/calclock/README.md b/apps/calclock/README.md
new file mode 100644
index 000000000..2b4e93a0c
--- /dev/null
+++ b/apps/calclock/README.md
@@ -0,0 +1,9 @@
+# Calendar Clock - Your day at a glance
+
+This clock shows a chronological view of your current and future events.
+It uses events synced from Gadgetbridge to achieve this.
+
+The current time and date is highlighted in cyan.
+
+## Screenshot
+
diff --git a/apps/calclock/calclock-icon.js b/apps/calclock/calclock-icon.js
new file mode 100644
index 000000000..9d5514d80
--- /dev/null
+++ b/apps/calclock/calclock-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwgpm5gAB4AVRhgWCAAQWWDCARC/4ACJR4uB54WDAAP8DBotFGIgXLFwv4GAouQC4gwMLooXF/gXJOowXGJBIXBCIgXQxgXLMAIXXMAmIC5OIx4XJhH/wAXIxnIC78IxGIHoIABI44MBC4wQBEQIDB5gXGPAJgEC6IxBC5oABC4wwDa4YTCxAWD5nPDAzvGFYgAB5AXWJBK+GcAq5CGBIuBC5X4GBIJBdoQXB/GIx4CDPJAuEC5JoCDAgWBFwYXJxCBIFwYXKYwoACCwZ3IPQoWIC5YABGYIABCwpHKAQYMBCwwX/C5QAMC8R3/R/4XNhAXNwAXHgGIABgWIAFwA=="))
diff --git a/apps/calclock/calclock.js b/apps/calclock/calclock.js
new file mode 100644
index 000000000..343d95c04
--- /dev/null
+++ b/apps/calclock/calclock.js
@@ -0,0 +1,119 @@
+var calendar = [];
+var current = [];
+var next = [];
+
+function updateCalendar() {
+ calendar = require("Storage").readJSON("android.calendar.json",true)||[];
+ calendar = calendar.filter(e => isActive(e) || getTime() <= e.timestamp);
+ calendar.sort((a,b) => a.timestamp - b.timestamp);
+
+ current = calendar.filter(isActive);
+ next = calendar.filter(e=>!isActive(e));
+}
+
+function isActive(event) {
+ var timeActive = getTime() - event.timestamp;
+ return timeActive >= 0 && timeActive <= event.durationInSeconds;
+}
+function zp(str) {
+ return ("0"+str).substr(-2);
+}
+
+function drawEventHeader(event, y) {
+ g.setFont("Vector", 24);
+
+ var time = isActive(event) ? new Date() : new Date(event.timestamp * 1000);
+ var timeStr = zp(time.getHours()) + ":" + zp(time.getMinutes());
+ g.drawString(timeStr, 5, y);
+ y += 24;
+
+ g.setFont("12x20", 1);
+ if (isActive(event)) {
+ g.drawString(zp(time.getDate())+". " + require("locale").month(time,1),15*timeStr.length,y-21);
+ } else {
+ var offset = 0-time.getTimezoneOffset()/1440;
+ var days = Math.floor((time.getTime()/1000)/86400+offset)-Math.floor(getTime()/86400+offset);
+ if(days > 0) {
+ var daysStr = days===1?/*LANG*/"tomorrow":/*LANG*/"in "+days+/*LANG*/" days";
+ g.drawString(daysStr,15*timeStr.length,y-21);
+ }
+ }
+ return y;
+}
+
+function drawEventBody(event, y) {
+ g.setFont("12x20", 1);
+ var lines = g.wrapString(event.title, g.getWidth()-10);
+ if (lines.length > 2) {
+ lines = lines.slice(0,2);
+ lines[1] = lines[1].slice(0,-3)+"...";
+ }
+ g.drawString(lines.join('\n'), 5, y);
+ y+=20 * lines.length;
+ if(event.location) {
+ g.drawImage(atob("DBSBAA8D/H/nDuB+B+B+B3Dn/j/B+A8A8AYAYAYAAAAAAA=="),5,y);
+ g.drawString(event.location, 20, y);
+ y+=20;
+ }
+ y+=5;
+ return y;
+}
+
+function drawEvent(event, y) {
+ y = drawEventHeader(event, y);
+ y = drawEventBody(event, y);
+ return y;
+}
+
+var curEventHeight = 0;
+
+function drawCurrentEvents(y) {
+ g.setColor("#0ff");
+ g.clearRect(5, y, g.getWidth() - 5, y + curEventHeight);
+ curEventHeight = y;
+
+ if(current.length === 0) {
+ y = drawEvent({timestamp: getTime(), durationInSeconds: 100}, y);
+ } else {
+ y = drawEventHeader(current[0], y);
+ for (var e of current) {
+ y = drawEventBody(e, y);
+ }
+ }
+ curEventHeight = y - curEventHeight;
+ return y;
+}
+
+function drawFutureEvents(y) {
+ g.setColor(g.theme.fg);
+ for (var e of next) {
+ y = drawEvent(e, y);
+ if(y>g.getHeight())break;
+ }
+ return y;
+}
+
+function fullRedraw() {
+ g.clearRect(5,24,g.getWidth()-5,g.getHeight());
+ updateCalendar();
+ var y = 30;
+ y = drawCurrentEvents(y);
+ drawFutureEvents(y);
+}
+
+function redraw() {
+ g.reset();
+ if (current.find(e=>!isActive(e)) || next.find(isActive)) {
+ fullRedraw();
+ } else {
+ drawCurrentEvents(30);
+ }
+}
+
+g.clear();
+fullRedraw();
+var minuteInterval = setInterval(redraw, 60 * 1000);
+
+Bangle.setUI("clock");
+Bangle.loadWidgets();
+Bangle.drawWidgets();
diff --git a/apps/calclock/calclock.png b/apps/calclock/calclock.png
new file mode 100644
index 000000000..5f953c1ee
Binary files /dev/null and b/apps/calclock/calclock.png differ
diff --git a/apps/calclock/location.png b/apps/calclock/location.png
new file mode 100644
index 000000000..619e55775
Binary files /dev/null and b/apps/calclock/location.png differ
diff --git a/apps/calclock/metadata.json b/apps/calclock/metadata.json
new file mode 100644
index 000000000..7bac5c721
--- /dev/null
+++ b/apps/calclock/metadata.json
@@ -0,0 +1,17 @@
+{
+ "id": "calclock",
+ "name": "Calendar Clock",
+ "shortName": "CalClock",
+ "version": "0.03",
+ "description": "Show the current and upcoming events synchronized from Gadgetbridge",
+ "icon": "calclock.png",
+ "type": "clock",
+ "tags": "clock agenda",
+ "supports": ["BANGLEJS2"],
+ "readme": "README.md",
+ "storage": [
+ {"name":"calclock.app.js","url":"calclock.js"},
+ {"name":"calclock.img","url":"calclock-icon.js","evaluate":true}
+ ],
+ "screenshots": [{"url":"screenshot.png"}]
+}
diff --git a/apps/calclock/screenshot.png b/apps/calclock/screenshot.png
new file mode 100644
index 000000000..4ab503f2b
Binary files /dev/null and b/apps/calclock/screenshot.png differ
diff --git a/apps/calendar/ChangeLog b/apps/calendar/ChangeLog
index cc8bb6306..0583ea45f 100644
--- a/apps/calendar/ChangeLog
+++ b/apps/calendar/ChangeLog
@@ -5,3 +5,7 @@
0.05: Update calendar weekend colors for start on Sunday
0.06: Use larger font for dates
0.07: Fix off-by-one-error on previous month
+0.08: Do not register as watch, manually start clock on button
+ read start of week from system settings
+0.09: Fix scope of let variables
+0.10: Use default Bangle formatter for booleans
diff --git a/apps/calendar/calendar.js b/apps/calendar/calendar.js
index 3f4315811..f4676fc22 100644
--- a/apps/calendar/calendar.js
+++ b/apps/calendar/calendar.js
@@ -16,10 +16,15 @@ const white = "#ffffff";
const red = "#d41706";
const blue = "#0000ff";
const yellow = "#ffff00";
+let bgColor = color4;
+let bgColorMonth = color1;
+let bgColorDow = color2;
+let bgColorWeekend = color3;
+let fgOtherMonth = gray1;
+let fgSameMonth = white;
let settings = require('Storage').readJSON("calendar.json", true) || {};
-if (settings.startOnSun === undefined)
- settings.startOnSun = false;
+let startOnSun = ((require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0) === 0;
if (settings.ndColors === undefined)
if (process.env.HWVERSION == 2) {
settings.ndColors = true;
@@ -28,19 +33,12 @@ if (settings.ndColors === undefined)
}
if (settings.ndColors === true) {
- let bgColor = white;
- let bgColorMonth = blue;
- let bgColorDow = black;
- let bgColorWeekend = yellow;
- let fgOtherMonth = blue;
- let fgSameMonth = black;
-} else {
- let bgColor = color4;
- let bgColorMonth = color1;
- let bgColorDow = color2;
- let bgColorWeekend = color3;
- let fgOtherMonth = gray1;
- let fgSameMonth = white;
+ bgColor = white;
+ bgColorMonth = blue;
+ bgColorDow = black;
+ bgColorWeekend = yellow;
+ fgOtherMonth = blue;
+ fgSameMonth = black;
}
function getDowLbls(locale) {
@@ -50,14 +48,14 @@ function getDowLbls(locale) {
case "de_AT":
case "de_CH":
case "de_DE":
- if (settings.startOnSun) {
+ if (startOnSun) {
dowLbls = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
} else {
dowLbls = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
}
break;
case "nl_NL":
- if (settings.startOnSun) {
+ if (startOnSun) {
dowLbls = ["zo", "ma", "di", "wo", "do", "vr", "za"];
} else {
dowLbls = ["ma", "di", "wo", "do", "vr", "za", "zo"];
@@ -66,14 +64,14 @@ function getDowLbls(locale) {
case "fr_BE":
case "fr_CH":
case "fr_FR":
- if (settings.startOnSun) {
+ if (startOnSun) {
dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"];
} else {
dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"];
}
break;
case "sv_SE":
- if (settings.startOnSun) {
+ if (startOnSun) {
dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"];
} else {
dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"];
@@ -81,21 +79,21 @@ function getDowLbls(locale) {
break;
case "it_CH":
case "it_IT":
- if (settings.startOnSun) {
+ if (startOnSun) {
dowLbls = ["Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa"];
} else {
dowLbls = ["Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"];
}
break;
case "oc_FR":
- if (settings.startOnSun) {
+ if (startOnSun) {
dowLbls = ["dg", "dl", "dm", "dc", "dj", "dv", "ds"];
} else {
dowLbls = ["dl", "dm", "dc", "dj", "dv", "ds", "dg"];
}
break;
default:
- if (settings.startOnSun) {
+ if (startOnSun) {
dowLbls = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
} else {
dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
@@ -110,7 +108,7 @@ function drawCalendar(date) {
g.clearRect(0, 0, maxX, maxY);
g.setBgColor(bgColorMonth);
g.clearRect(0, 0, maxX, headerH);
- if (settings.startOnSun){
+ if (startOnSun){
g.setBgColor(bgColorWeekend);
g.clearRect(0, headerH + rowH, colW, maxY);
g.setBgColor(bgColorDow);
@@ -150,7 +148,7 @@ function drawCalendar(date) {
});
date.setDate(1);
- const dow = date.getDay() + (settings.startOnSun ? 1 : 0);
+ const dow = date.getDay() + (startOnSun ? 1 : 0);
const dowNorm = dow === 0 ? 7 : dow;
const monthMaxDayMap = {
@@ -242,5 +240,5 @@ Bangle.on("touch", area => {
});
// Show launcher when button pressed
-Bangle.setUI("clock"); // TODO: ideally don't set 'clock' mode
+setWatch(() => load(), process.env.HWVERSION === 2 ? BTN : BTN3, { repeat: false, edge: "falling" });
// No space for widgets!
diff --git a/apps/calendar/metadata.json b/apps/calendar/metadata.json
index 62d2513ae..48fd52d3e 100644
--- a/apps/calendar/metadata.json
+++ b/apps/calendar/metadata.json
@@ -1,7 +1,7 @@
{
"id": "calendar",
"name": "Calendar",
- "version": "0.07",
+ "version": "0.10",
"description": "Simple calendar",
"icon": "calendar.png",
"screenshots": [{"url":"screenshot_calendar.png"}],
diff --git a/apps/calendar/settings.js b/apps/calendar/settings.js
index 3c8f7d8e8..54ed50a64 100644
--- a/apps/calendar/settings.js
+++ b/apps/calendar/settings.js
@@ -1,8 +1,6 @@
(function (back) {
var FILE = "calendar.json";
var settings = require('Storage').readJSON(FILE, true) || {};
- if (settings.startOnSun === undefined)
- settings.startOnSun = false;
if (settings.ndColors === undefined)
if (process.env.HWVERSION == 2) {
settings.ndColors = true;
@@ -17,17 +15,8 @@
E.showMenu({
"": { "title": "Calendar" },
"< Back": () => back(),
- 'Start Sunday': {
- value: settings.startOnSun,
- format: v => v ? "Yes" : "No",
- onchange: v => {
- settings.startOnSun = v;
- writeSettings();
- }
- },
'B2 Colors': {
value: settings.ndColors,
- format: v => v ? "Yes" : "No",
onchange: v => {
settings.ndColors = v;
writeSettings();
diff --git a/apps/calibration/ChangeLog b/apps/calibration/ChangeLog
new file mode 100644
index 000000000..64bff2b31
--- /dev/null
+++ b/apps/calibration/ChangeLog
@@ -0,0 +1,3 @@
+0.01: New App!
+0.02: Use fractional numbers and scale the points to keep working consistently on whole screen
+0.03: Use default Bangle formatter for booleans
diff --git a/apps/calibration/README.md b/apps/calibration/README.md
new file mode 100644
index 000000000..ed1a29d9e
--- /dev/null
+++ b/apps/calibration/README.md
@@ -0,0 +1,12 @@
+# Banglejs - Touchscreen calibration
+A simple calibration app for the touchscreen
+
+## Usage
+
+Once lauched touch the cross that appear on the screen to make
+another spawn elsewhere.
+
+Each new touch on the screen will help to calibrate the offset
+of your finger on the screen. After four or more inputs, press
+the button to save the calibration and close the application. Quality
+of the calibration gets better with every touch on a cross.
diff --git a/apps/calibration/app-icon.js b/apps/calibration/app-icon.js
new file mode 100644
index 000000000..af66c3f68
--- /dev/null
+++ b/apps/calibration/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwkB/4AJ+EPBhQXg+BBDCyJaGGR5zIDBoQEL4QYOLYR3GBIouJR5AYBGBILBU5QMGFwgiFX4wwIEI4XGGBAgHd44+HD44XHNw4XWM5IIHCIoXWV5IXICQgXvLxAAKCYYXh5nMC6n8C4PPC5MAAA8PC4ZxBACAXOI653hU5zvJABASEC5PwHI4XcMBIXICIoXXJBAXHCAwXXJBAXHB5AfGC4ygJEAwXGQ5BoIQxoiDBYgXECwIuIBgb5ECIQJFGBQmCC4QHEDBwAFCxoYICx5ZELZoZJFiIXpA="))
\ No newline at end of file
diff --git a/apps/calibration/app.js b/apps/calibration/app.js
new file mode 100644
index 000000000..049430d45
--- /dev/null
+++ b/apps/calibration/app.js
@@ -0,0 +1,151 @@
+class BanglejsApp {
+ constructor() {
+ this.maxSamples = 16;
+ this.target = {
+ xMin: Math.floor(0.1 * g.getWidth()),
+ xMax: Math.floor(0.9 * g.getWidth()),
+ yMin: Math.floor(0.1 * g.getHeight()),
+ yMax: Math.floor(0.9 * g.getHeight()),
+ };
+ this.x = 0;
+ this.y = 0;
+ this.step = 0;
+ this.settings = {
+ xoffset: [0],
+ yoffset: [0],
+ xMaxActual: [this.target.xMax],
+ yMaxActual: [this.target.yMax],
+ };
+ }
+
+ load_settings() {
+ let settings = require('Storage').readJSON('calibration.json', true) || {active: false};
+
+ console.log('loaded settings:');
+ console.log(settings);
+
+ return settings;
+ }
+
+ getMedian(array){
+ array.sort();
+ let i = Math.floor(array.length/2);
+ if ( array.length % 2 && array.length > 1 ){
+ return (array[i]+array[i+1])/2;
+ } else {
+ return array[i];
+ }
+ }
+
+ getMedianSettings(){
+ let medianSettings = {
+ xoffset: this.getMedian(this.settings.xoffset),
+ yoffset: this.getMedian(this.settings.yoffset)
+ };
+
+ medianSettings.xscale = this.target.xMax / (medianSettings.xoffset + this.getMedian(this.settings.xMaxActual));
+ medianSettings.yscale = this.target.yMax / (medianSettings.yoffset + this.getMedian(this.settings.yMaxActual));
+ return medianSettings;
+ }
+
+ save_settings() {
+ let settingsToSave = this.getMedianSettings();
+ settingsToSave.active = true;
+ settingsToSave.reload = false;
+ require('Storage').writeJSON('calibration.json', settingsToSave);
+
+ console.log('saved settings:', settingsToSave);
+ }
+
+ explain() {
+ /*
+ * TODO:
+ * Present how to use the application
+ *
+ */
+ }
+
+ drawTarget() {
+ switch (this.step){
+ case 0:
+ this.x = this.target.xMin;
+ this.y = this.target.yMin;
+ break;
+ case 1:
+ this.x = this.target.xMax;
+ this.y = this.target.yMin;
+ break;
+ case 2:
+ this.x = this.target.xMin;
+ this.y = this.target.yMax;
+ break;
+ case 3:
+ this.x = this.target.xMax;
+ this.y = this.target.yMax;
+ break;
+ }
+
+ g.clearRect(0, 0, g.getWidth(), g.getHeight());
+ g.setColor(g.theme.fg);
+ g.drawLine(this.x, this.y - 5, this.x, this.y + 5);
+ g.drawLine(this.x - 5, this.y, this.x + 5, this.y);
+ g.setFont('Vector', 10);
+ let medianSettings = this.getMedianSettings();
+ g.drawString('current offset: ' + medianSettings.xoffset.toFixed(3) + ', ' + medianSettings.yoffset.toFixed(3), 2, (g.getHeight()/2)-6);
+ g.drawString('current scale: ' + medianSettings.xscale.toFixed(3) + ', ' + medianSettings.yscale.toFixed(3), 2, (g.getHeight()/2)+6);
+ }
+
+ setOffset(xy) {
+ switch (this.step){
+ case 0:
+ this.settings.xoffset.push(this.x - xy.x);
+ this.settings.yoffset.push(this.y - xy.y);
+ break;
+ case 1:
+ this.settings.xMaxActual.push(xy.x);
+ this.settings.yoffset.push(this.y - xy.y);
+ break;
+ case 2:
+ this.settings.xoffset.push(this.x - xy.x);
+ this.settings.yMaxActual.push(xy.y);
+ break;
+ case 3:
+ this.settings.xMaxActual.push(xy.x);
+ this.settings.yMaxActual.push(xy.y);
+ break;
+ }
+
+ for (let c in this.settings){
+ if (this.settings[c].length > this.maxSamples) this.settings[c] = this.settings[c].slice(1, this.maxSamples);
+ }
+ }
+
+ nextStep() {
+ this.step++;
+ if ( this.step == 4 ) this.step = 0;
+ }
+}
+
+
+E.srand(Date.now());
+
+calibration = new BanglejsApp();
+calibration.load_settings();
+Bangle.disableCalibration = true;
+
+function touchHandler (btn, xy){
+ if (xy) calibration.setOffset(xy);
+ calibration.nextStep();
+ calibration.drawTarget();
+}
+
+let modes = {
+ mode : 'custom',
+ btn : function(n) {
+ calibration.save_settings(this.settings);
+ load();
+ },
+ touch : touchHandler,
+};
+Bangle.setUI(modes);
+calibration.drawTarget();
diff --git a/apps/calibration/boot.js b/apps/calibration/boot.js
new file mode 100644
index 000000000..03b17a03a
--- /dev/null
+++ b/apps/calibration/boot.js
@@ -0,0 +1,14 @@
+let cal_settings = require('Storage').readJSON("calibration.json", true) || {active: false};
+Bangle.on('touch', function(button, xy) {
+ // do nothing if the calibration is deactivated
+ if (cal_settings.active === false || Bangle.disableCalibration) return;
+
+ // reload the calibration offset at each touch event /!\ bad for the flash memory
+ if (cal_settings.reload === true) {
+ cal_settings = require('Storage').readJSON("calibration.json", true);
+ }
+
+ // apply the calibration offset
+ xy.x = E.clip(Math.round((xy.x + (cal_settings.xoffset || 0)) * (cal_settings.xscale || 1)),0,g.getWidth());
+ xy.y = E.clip(Math.round((xy.y + (cal_settings.yoffset || 0)) * (cal_settings.yscale || 1)),0,g.getHeight());
+});
diff --git a/apps/calibration/calibration.png b/apps/calibration/calibration.png
new file mode 100644
index 000000000..3fb44beee
Binary files /dev/null and b/apps/calibration/calibration.png differ
diff --git a/apps/calibration/metadata.json b/apps/calibration/metadata.json
new file mode 100644
index 000000000..b60650300
--- /dev/null
+++ b/apps/calibration/metadata.json
@@ -0,0 +1,17 @@
+{ "id": "calibration",
+ "name": "Touchscreen Calibration",
+ "shortName":"Calibration",
+ "icon": "calibration.png",
+ "version":"0.03",
+ "description": "A simple calibration app for the touchscreen",
+ "supports": ["BANGLEJS","BANGLEJS2"],
+ "readme": "README.md",
+ "tags": "tool",
+ "storage": [
+ {"name":"calibration.app.js","url":"app.js"},
+ {"name":"calibration.boot.js","url":"boot.js"},
+ {"name":"calibration.settings.js","url":"settings.js"},
+ {"name":"calibration.img","url":"app-icon.js","evaluate":true}
+ ],
+ "data": [{"name":"calibration.json"}]
+}
diff --git a/apps/calibration/settings.js b/apps/calibration/settings.js
new file mode 100644
index 000000000..08c728d96
--- /dev/null
+++ b/apps/calibration/settings.js
@@ -0,0 +1,22 @@
+(function(back) {
+ var FILE = "calibration.json";
+ var settings = Object.assign({
+ active: true,
+ }, require('Storage').readJSON(FILE, true) || {});
+
+ function writeSettings() {
+ require('Storage').writeJSON(FILE, settings);
+ }
+
+ E.showMenu({
+ "" : { "title" : "Calibration" },
+ "< Back" : () => back(),
+ 'Active': {
+ value: !!settings.active,
+ onchange: v => {
+ settings.active = v;
+ writeSettings();
+ }
+ },
+ });
+})
\ No newline at end of file
diff --git a/apps/cassioWatch/ChangeLog b/apps/cassioWatch/ChangeLog
new file mode 100644
index 000000000..883bd2585
--- /dev/null
+++ b/apps/cassioWatch/ChangeLog
@@ -0,0 +1,12 @@
+0.0: Main App.
+0.1: Performance Fixes.
+0.2: Correct Screen Clear and Draw.
+0.3: Minor Fixes.
+0.4: Clear Old Time on Screen Before Draw new Time
+0.5: Update Date and Time to use Native date Funcions
+0.6: Add Settings Page
+0.7: Update Rocket Sequences Scope to not use memory all time
+0.8: Update Some Variable Scopes to not use memory until need
+0.9: Remove ESLint spaces
+0.10: Show daily steps, heartrate and the temperature if weather information is available.
+0.11: Tell clock widgets to hide.
diff --git a/apps/cassioWatch/README.md b/apps/cassioWatch/README.md
new file mode 100644
index 000000000..aaeb3f122
--- /dev/null
+++ b/apps/cassioWatch/README.md
@@ -0,0 +1,11 @@
+# cassioWatch
+
+ 
+
+Clock with Space Cassio Watch Style.
+
+It displays current temperature,day,steps,battery.heartbeat and weather.
+
+
+**To-do**:
+Align and change size of some elements.
diff --git a/apps/cassioWatch/app.js b/apps/cassioWatch/app.js
new file mode 100644
index 000000000..91f6737ad
--- /dev/null
+++ b/apps/cassioWatch/app.js
@@ -0,0 +1,175 @@
+const storage = require('Storage');
+
+require("Font6x12").add(Graphics);
+require("Font8x12").add(Graphics);
+require("Font7x11Numeric7Seg").add(Graphics);
+
+function bigThenSmall(big, small, x, y) {
+ g.setFont("7x11Numeric7Seg", 2);
+ g.drawString(big, x, y);
+ x += g.stringWidth(big);
+ g.setFont("8x12");
+ g.drawString(small, x, y);
+}
+
+function getBackgroundImage() {
+ return require("heatshrink").decompress(atob("2GwwkGIf4AfgMRkUiiIHCiMRiAMDAwYCCBAYVDAHMv/4ACkBIBAgPxBgM/BYXyAoICBCowA5gRADKQUDKAYMCmYCBiBXBCo4A5J4MxiMSKQUf+YBBBgSiBgc/kBXBBAMyCoK2CK/btCiUhfAJLCkBkDiMQgBXDCoUvNAJX+AAU/+MB/8wAQIAC+cQK5hoDgIEBBIQFEAYIPHBIgBBAQQIDBwZXSKIMxgJaBgEjmZYCmBXLgLBBkkAgUhiMxBIM0iMSCoMRkZECkQJEichBINDiETAgISBiQTDK6MvJAXzVIQrBBYMCK5E/K4kwGIJXFgdAMgQQBiYiCDgU0HQSlCgMikIEBEAMTDYJXQ+UikYDBj6nCAAMTWoJ6BK4oVEK4c0oQ+BK4MjAgMDJoJXHNYJXHBwa0BohcDY4QAKgJQE+LzBNwJVBkQMEkBXBCoyvFJAVAKISaBiMiHQRIDkVBoSyCK5CvBAgavNDAJAC+cQn5DCgSpBl4MDgBXBgCsBCoYoMLAKREgIKDBJIdKK5oA/AH4A/AH4A/ADUBIH4APiAFEi1mAGUADrkRKwUGK2ZXes1gK2xXfD8A3/K/4AWgxX/ACtga2AwIHLkAgCwvJw6RcDgIABK+w4cK/I4dsEGP5BXtSAQ6BV/5XSG4RX/K6Y3fK+42CK/5XTGwcGK/5XSVwY5cK+o1DAAayYsAhDsCv4K7BTBK4YeYK7CyFVzJXFFIpXtVwYiYK/rmZKYYDDELJXXG4YiaK/Y0aKgQAEK+gkdKt5XGKzqv5GTpX6ETlgK4xWrKTyxKVthXmAGRX/K/5X/AH5X/K/4gBAH4A/AFz/uAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHNggEGHfEAgAEHKyQXVK0qTCAggbUK+6SDAApzXK/5BRDYZX3KxBBSYqxXngyvaV25XEd4ZCSsAcBAoRZ2dQZXBLwgaQCIYeCAGirCS4YGCDSJXCC6ZaodYICBZzSw4S4I+XDgSv4K4rzCK/47RAQTMaWHI9YV3TscV3aVagByBK3SwCSqyt8AAQ+XK/4A/AH4A/AH4A3gAA/AH4AuZbdggwc3ADpX/K/5XxsEAgA+XK/o8BgBX/K64/WK/4/XK/5X/K/5XvgBX/K64cYHrw4CSTFggCuXK4oDCEQJXYDS6ScDgg4CPKyRCAAZX0HAgBDK+LlYK4oeBAwZ9aK+lgAoQGBgyvzDIIDBK66sCG4JXYCwIBDK7ADCK+xZCHwJXzGoQ8BK7DpBAAaSXSgRXZO4okCK+IaXV4oABEILSWSYjRCHSo3BDSxXEAAIcBAISvyKawcIAYIGCK/4cUH4YlaHS0AHgI1XOg5YBPrY6WHgRXfAGRXDHzBX8VoJX/K68ADjRX6sBX/K/5X/K8wdcK/UAG7B0iKzZYbK/BWDAH4A/hWpzWhIf4ASgOpzIAB0EAhhH/AB8ZzGJ1WazMA4pH/AB+pxOZxOpzVMqA2ugUzmcgD7cKVYOqzGqpnRFw8ykchK8kviEBmQFBgMiFocSCAcSkUQAgMikRsHhWqxOq0Ut4mqBw0DC4IxBD4wpBHAQMCA4cCGJIAFj8hDIQuBkMTCwU/AYQJBiUxFoPxiIVDK4kyxUz4cxl+KK5MfDQXyD4UCmMSmAEBAQQHDgMTmIxHAAqpBmaqCFwMDEYZRBgEjCQQBB+USK5E/ns/0Uzwc6K48ykYkCK4IfCc4I4CK4QHEBAYAMiICBmYuDmQEBh8iAgRXCLISvJO4MqwcklEiK5CADV4oaBV4oHEK6Eve4JNCbwRfCiMTFoMDkMRSAJXCD49azWp0UqzWayJXIQwcAO4cCkMCFIJOCA4XxK6KPBkR6DTwYyBAwYPEAggfFzORpWK1OZyAOHJ4QfERAUSEgQxIIIgAr1URWIOZzOgGtwAhgMZzWq1OaIv4ASKgOqzTkvAEmq1WgFtQA=="));
+}
+
+function getRocketSequences() {
+ return {
+ 1: require("heatshrink").decompress(atob("qFGwkCkQAiiEBEkUgKQhPhE8ogCE8YhCiQoEE7pKEPIgncTQ4neEwpQCPoh1eJYYwCJ7QmHKAh1hZIpOjPAUBJ0ZQCTzEhExZ1lPAZ1kKDQmOJ65O2E65OPOy5O2E64mPOyxO/J2wnPJyx2QJ35O/J2khE0p2POq52PEy4nOiQnlOrEhiSfMJrEggQnLJzB1CPBQmZkInMEzBQDPBImbPBR1ZEoRMCZYImhgQgEE0BzFKAgmaDwLDFKAbqdYQwHBOrcgDgLBFJrsiiRNGYbpLBY4Ymhd4omkkUhE0pQEEwUBJjrHBd4QmCdzoiBDwYrCPLyZHF4QnagQeCE8UgJwYniJwgnIOzwfFO0wJCJzMQE4gyFEzR2FBQombkInDQI4AakAnBTYS+ZE5BMDE0LEES7YnLE0R3FAEQA=")),
+ 2: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYon/AA0gEAQniEwIhCAgYndEIjqBE8CaGKogmgKAp1fKAgncExBQBBQR1gKAp7BJ0IndExR4CE0idaOpYnbExqeYJxxPYEx0BJ0x2XExx2XJ20QE6xONJi5OPGwJOlBwLFkLoLFlBwJOkOwJOlE4JOkTjBOOE/52Pdi5OPEy7FnE5wmXE5xOZT5gmYEoMiiB1lgR4KTLAkDPBJ1WIAYDDKA4mWJwchDwYEDTjQiDJQh4GYLAhHFosSJy6OCTIxaEEywbBKYwjEEzMgUQxQFBogAURwZOGOjTKJdTYnOEryfHE0JQEfIpQgYQMAgJLeAgrtfTI4ndgSaFE4h0bdQkSZQpOfEAgIBO0AnEdrh2FJAb1EdbInEBIpObOwhOEEzYnFXzZ2HE4QlhE4QlDFMKcDYooniO0QnDT0YnCE0ciA")),
+ 3: require("heatshrink").decompress(atob("qFGwkEogAjiMUEkVAKYgnhPYolgOQIniOYZ4FOcLqBE8CaGKojpgKAomhEYUQE7gmHKAIxCE0QkCPYR1gZIgnZExR4CJ0idmE7ZONYzImNgEUJ0p3YJRh2ZJJwnXOpQhBdkpaETsMEGQhOhE7jFLUYpOfTzgmKE4hOiE4hOigEUJ0rvCEywnPEqx2OTjBOOE7ImOTsqeZE5zFYoJOmT5kBJzEAih4LdK5mBAQInKOqoYDEgR4JEypHDEYbxJOq5ABdgZ7CEzZOEJQgnGihOYEIzJFTionCKYxWGEy9ADAYnGUIYmWog/EdBFAEy7KIKAwnjKwLqWE5pMeT48CVQpQfgMjKEtEiAnfEQJQCgJSCTcB6FJzkEdYcUE8FAdQghDOzonKTjh2EZAidcDoInHJzodBOwx/BE8JxcOwsAOwQmhJgSXDObwnFEwUUO0LFGE8aeiE4YmiokQE0tE")),
+ 4: require("heatshrink").decompress(atob("qFGwkCkQAjiMSEkRTFE/4AGkMAgQCBE8MgEIYEDE7whDdQIngTQxVEE0ChFTjxQFE7jnFKAgxCOsBQFZgJ1gE7wmKPAROkTrTEHGAwnYiBHJFAaeXOoyXBEQZPac5AsFgJOhAoh2XJwwnFKoROdE4J9GJzwnIiQmVkInPAC0QE5AJFE64mHY5DFdE4SBEYr5JDJ0hKDJ0jCZJxoACgInmKLAmOTq5OOEy5OPTsxOYE5wmXO5wlYkAnMOqshiRNCgR4LOC8CkJCCEzxHDAgYnJOqpAEDoZ4HEyodDEQpQHdCsQOwwFHEyzoCPYzJGEy0gEwaZGA4acVEQSjHKAomXkQYEYAwlZeRKYDE8gjCYa7zJEwcCkImfKAb4FAD0hdTh4LgRSBOcR0CJz0gYYrrgN4QnEYrxOEE4bEeiAnGF4J2idL6VDE8ohBE0gnFE0J0BE4QGBiROgdIQABgJ2hJoTtjYgZSEE8ScgE4omikUQTcQADA=")),
+ 5: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYonhiAnjkEATIIniEwIhCAgYndEIhQFYUZVEE0BQFOr5QEeQQmiKAL1DOr5QEE7ROCDgZVEAoInZDwchFQQoDPAJOdEQYrBdrZFDOYwncEJDsDVIpOXgJxEE4pObEAgGFgJOaE48BaIhOZJ5ZObY5ROcE441CE6xOGPAwtCJzpGCJ0hHDkI1DJzwoEJzInLFg52dUo5O/J35OzE54mWOx4mXJxx1XE54mXkUhExkSJzCfMOrAlBPBiZXgQDBAQQmgJgh4JOqoYEFYwmaDoZzEFgh1YDgkiiAFEKAroXJJAGFiQmVkCNDTIz5EJy57HKAomXkQYEJoqaYeRadEJrAnJEQUAgJPiAoYmeT4cCkAnBE0BKCJkT1EkDCeJYYiDOkLDFFL5wBE4guCPDhEBEwQiDY70CkInDiQnCJzkhOwhKDdzp2Idb4nEE0B0Bdo4niE0J0CeYhOhgESUYYnidsgnEE0KeCE0gnDE0ciA")),
+ 6: require("heatshrink").decompress(atob("qFGwkCkQA/ABEgKQZPhEwgABEsAoGJkBxBE8JKEAowAbJIhQEgLDiPooAdKA4ncTZAndSwhQEFoInaJQkSKAwlZdgwnfSgYADE4h1ZDwInlcggnIOzAdCE8i7EY5J3XDgYhGd4pOZEI52bSYwGCOAJ2bYIodEOzZOFFAjFcEwwAIE6xOHABBO/J34ndEyx2PJ00BJ00SJ0p1XE54mXOxxO/J5wmYgQnMOrB2BPBgkWiJ1CPBbBYAYR4KiTAXRwIrFTjgZDJYZ4IEyoiEIwrDcEJJQFOqwiBDARxFFwgmXkAYDEogsBF4QmXEQJ7GUYYkBEzDKJAgYmdEQbKFEzonEKYgngJwgmfZggmjKQghgiBRGkBzeTgUikJRgc47LDErTnDEAkQJzkCJwYnEJzonEJIaddOwhJEJzgdBE4hYEJzieJADgnEE0KUCXzoAGkJLEiB2hOgQDBT0TsDT0YmlE4YmjkQ=")),
+ 7: require("heatshrink").decompress(atob("qFGwkCkQAhkIpBiQlhkBSEJ8InlEIIoFE7whEE8pQFE7giBJQoneI4MCTYhQDE7YdCYYondEQYnEPwZ1bE5BQCJzonHkR2ZEAkBE4pNBE7zHFYrYhFUgonaXAQeEEwruZEYcgiROHJ7AfDAwxOeAAURiAmHE65HIOzwmOJ35OPE6xOPO35O/J35O/J1gnPEyx2PEy5OOOq5OnE5xOYO5omZgJQMJrQnLiQnagR4JOq5nCDgZ1fEYRLDE5DoZkUQNoZ4GOrJKGAoomXOw7lCAwYmYDgJSEAAUBA4QDBJzB6FOQrDXJwTJFdLjJKE9jDYZRAmkKAwmhKAgmiKAYmBkApdJIgjCKYIncOQYvJYTovGE84lagR2DE4xOakBOEgJXFOjYnEJAbtdOwggEkAmbDgInDE0B0BE4QgcE5AkiXYbpCOLonGYo4nhPMYnCUEgnBY0kiA==")),
+ 8: require("heatshrink").decompress(atob("qFGwkCkQA/ABBSEJ8MgE4kBEsBPFE7xMCOIJ3hOYgFEE7rCGE70gE4pQBiAndYQwjBUohOZD4ZQFE7YkBE5AICYbZ2GE7sggJRCAA8iYzZOITroALE7EhExh4CAC0QExpPXOponZExx2XJ24nWdh52XdhzF/Yu5O/J35O0E55OXOx5O/J2omXE5x1XO54mYgQnMJrR4LOrciiAmiJgR4KEzIjDPBAlYiAiEeI51YkEBE4J5CD4KceTQQcBJgRQFdTZDCJIjDcNIqhGdTQmCkByFTTInDKgoAEE7ZEEJwhPdE1R1FE0InEE0R3DEwTGcDwomEE7hKFPYqafE8ROCE5DJbE5B/IEqh2ED4gnCJrMCJwgnEiB2bE4qeFEzUggQmIBQLEaEQImHLIImaE4YfcOw4lEFMLECS7onJO8wmkE4QljAAIA==")),
+ };
+}
+
+let rocketSequence = 1;
+let settings = storage.readJSON("cassioWatch.settings.json", true) || {};
+let rocketSpeed = settings.rocketSpeed || 700;
+delete settings;
+
+// schedule a draw for the next minute
+let rocketInterval;
+var drawTimeout;
+function queueDraw() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ }, 60000 - (Date.now() % 60000));
+}
+
+
+function clearIntervals() {
+ if (rocketInterval) clearInterval(rocketInterval);
+ rocketInterval = undefined;
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+}
+
+function drawClock() {
+ g.setFont("7x11Numeric7Seg", 3);
+ g.clearRect(80, 57, 170, 96);
+ g.setColor(0, 255, 255);
+ g.drawRect(80, 57, 170, 96);
+ g.fillRect(80, 57, 170, 96);
+ g.setColor(0, 0, 0);
+ g.drawString(require("locale").time(new Date(), 1), 70, 60);
+ g.setFont("8x12", 2);
+ g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 18, 130);
+ g.setFont("8x12");
+ g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 126);
+ g.setFont("8x12", 2);
+ const time = new Date().getDate();
+ g.drawString(time < 10 ? "0" + time : time, 78, 137);
+}
+
+function drawBattery() {
+ bigThenSmall(E.getBattery(), "%", 135, 21);
+}
+
+function drawRocket() {
+ let Rocket = getRocketSequences();
+ g.clearRect(5, 62, 63, 115);
+ g.setColor(0, 255, 255);
+ g.drawRect(5, 62, 63, 115);
+ g.fillRect(5, 62, 63, 115);
+ g.drawImage(Rocket[rocketSequence], 5, 65, { scale: 0.7 });
+ g.setColor(0, 0, 0);
+ rocketSequence = rocketSequence + 1;
+ if(rocketSequence > 8) rocketSequence = 1;
+}
+
+function getTemperature(){
+ try {
+ var weatherJson = storage.readJSON('weather.json');
+ var weather = weatherJson.weather;
+ return Math.round(weather.temp-273.15);
+
+ } catch(ex) {
+ print(ex)
+ return "?"
+ }
+}
+
+function getSteps() {
+ var steps = 0;
+ try{
+ if (WIDGETS.wpedom !== undefined) {
+ steps = WIDGETS.wpedom.getSteps();
+ } else if (WIDGETS.activepedom !== undefined) {
+ steps = WIDGETS.activepedom.getSteps();
+ } else {
+ steps = Bangle.getHealthStatus("day").steps;
+ }
+ } catch(ex) {
+ // In case we failed, we can only show 0 steps.
+ return "? k";
+ }
+
+ steps = Math.round(steps/1000);
+ return steps + "k";
+}
+
+
+function draw() {
+ queueDraw();
+
+ g.reset();
+ g.clear();
+ g.setColor(0, 255, 255);
+ g.fillRect(0, 0, g.getWidth(), g.getHeight());
+ let background = getBackgroundImage();
+ g.drawImage(background, 0, 0, { scale: 1 });
+ g.setColor(0, 0, 0);
+ g.setFont("6x12");
+ g.drawString("Launching Process", 30, 20);
+ g.setFont("8x12");
+ g.drawString("ACTIVATE", 40, 35);
+
+ g.setFontAlign(0,-1);
+ g.setFont("8x12", 2);
+ g.drawString(getTemperature(), 155, 132);
+ g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), 109, 98);
+ g.drawString(getSteps(), 158, 98);
+
+ g.setFontAlign(-1,-1);
+ drawClock();
+ drawRocket();
+ drawBattery();
+
+ // Hide widgets
+ for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
+}
+
+Bangle.on("lcdPower", (on) => {
+ if (on) {
+ draw();
+ } else {
+ clearIntervals();
+ }
+});
+
+
+Bangle.on("lock", (locked) => {
+ clearIntervals();
+ draw();
+ if (!locked) {
+ rocketInterval = setInterval(drawRocket, rocketSpeed);
+ }
+});
+
+Bangle.setUI("clock");
+
+// Load widgets, but don't show them
+Bangle.loadWidgets();
+
+g.reset();
+g.clear();
+draw();
diff --git a/apps/cassioWatch/app.png b/apps/cassioWatch/app.png
new file mode 100644
index 000000000..3f9bbb36e
Binary files /dev/null and b/apps/cassioWatch/app.png differ
diff --git a/apps/cassioWatch/icon.js b/apps/cassioWatch/icon.js
new file mode 100644
index 000000000..4e4428f88
--- /dev/null
+++ b/apps/cassioWatch/icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("lkswkGswAHtGIxGGBhATJAAYXNCYoWOFIwWNChQWKBYWYCxIqTxGJFgwnDnACBkUiCwuYFQo9ECYIAClAsJxIUElAUDwQWEyxAHBwITBmczmUiCwprHCgMjmUhiIYBA4JCGIAeCwQUBCYIXBiUyFggVCFQsziMjmdCkcxiZbBiJCEFQZUBmND93uolEochFgpWEIAUUCgIACp00iZVBzIVEAoJABmUeConu8cRiNEoMZNwIrCzEiiUxpwVF901IwNN6JuBtGJlAVBkchCg3umkSiMNCoIAEQIJWFkgCB8UYinUjIVFxEhKwszDAUU7tRCg2hilTH4xVB6kRzAUGBQIVECYVEorEDAAeBptBiUenw7BCYQACieCCosd6MYwUVBwNUAQYvBeYOJCYVoxVeoK5BAAq0C8MjiM4LALFCilNCAQpCN4lBmUoFQQVCxTlBiMieI3uqUoagIVDRAeCkclCgvlkciFYdmsxwDlESmTdE8lRmSCECosiFgMhqgUDkZABBwWYCoNpIIYXBmchiKLBkYqBNggVBNwIsECwMikQUBlEiBgWJCoRCEEQITDNIJrEIAQABZYOYnIWBFoQUGIAZCCTYYWCAAQUExIUDFggNDAA5AEFg4AIIAhvGEYU4ChgWHAAoUIWYpdFFRIWHChxEICZoWFFBIA=="))
diff --git a/apps/cassioWatch/metadata.json b/apps/cassioWatch/metadata.json
new file mode 100644
index 000000000..abfee9b93
--- /dev/null
+++ b/apps/cassioWatch/metadata.json
@@ -0,0 +1,18 @@
+{
+ "id": "cassioWatch",
+ "name": "Cassio Watch",
+ "description": "Animated Clock with Space Cassio Watch Style",
+ "screenshots": [{ "url": "screens/screen_night.png" },{ "url": "screens/screen_day.png" }],
+ "icon": "app.png",
+ "version": "0.11",
+ "type": "clock",
+ "tags": "clock, weather, cassio, retro",
+ "supports": ["BANGLEJS2"],
+ "allow_emulator": true,
+ "readme": "README.md",
+ "storage": [
+ { "name": "cassioWatch.app.js", "url": "app.js" },
+ {"name":"cassioWatch.settings.js","url":"settings.js"},
+ { "name": "cassioWatch.img", "url": "icon.js", "evaluate": true }
+ ]
+}
diff --git a/apps/cassioWatch/screens/screen_day.png b/apps/cassioWatch/screens/screen_day.png
new file mode 100644
index 000000000..ba150b4f7
Binary files /dev/null and b/apps/cassioWatch/screens/screen_day.png differ
diff --git a/apps/cassioWatch/screens/screen_night.png b/apps/cassioWatch/screens/screen_night.png
new file mode 100644
index 000000000..4055e0943
Binary files /dev/null and b/apps/cassioWatch/screens/screen_night.png differ
diff --git a/apps/cassioWatch/settings.js b/apps/cassioWatch/settings.js
new file mode 100644
index 000000000..b07c6c58f
--- /dev/null
+++ b/apps/cassioWatch/settings.js
@@ -0,0 +1,24 @@
+(function(back) {
+ var FILE = "cassioWatch.settings.json";
+ var settings = Object.assign({
+ rocketSpeed: 700,
+ }, require('Storage').readJSON(FILE, true) || {});
+
+ function writeSettings() {
+ require('Storage').writeJSON(FILE, settings);
+ }
+
+
+ E.showMenu({
+ "" : { "title" : "Cassio Watch" },
+ "< Back" : () => back(),
+ 'Rocket Speed': {
+ value: 0|settings.rocketSpeed,
+ min: 100, max: 60000,
+ onchange: v => {
+ settings.rocketSpeed = v;
+ writeSettings();
+ }
+ },
+ });
+ })
\ No newline at end of file
diff --git a/apps/chimer/ChangeLog b/apps/chimer/ChangeLog
new file mode 100644
index 000000000..01bd00a0a
--- /dev/null
+++ b/apps/chimer/ChangeLog
@@ -0,0 +1,2 @@
+0.01: Initial Creation
+0.02: Fixed some sleep bugs. Added a sleep mode toggle
\ No newline at end of file
diff --git a/apps/chimer/README.MD b/apps/chimer/README.MD
new file mode 100644
index 000000000..a78c677f2
--- /dev/null
+++ b/apps/chimer/README.MD
@@ -0,0 +1,11 @@
+# Chimer - For the BangleJS
+
+A fork of [Hour Chime](https://github.com/espruino/BangleApps/tree/master/apps/widchime) that adds extra features such as:
+
+- Buzz or beep on every 60, 30 or 15 minutes.
+- Repeat Chime up to 3 times
+- Set hours to disable chime
+
+Setting the hours you don't want your watch to chime for is done by setting the hour you want it to stop, and the hour you want it to start.
+
+Hours range from 0 - 23.
diff --git a/apps/chimer/icon.txt b/apps/chimer/icon.txt
new file mode 100644
index 000000000..cc969bc81
--- /dev/null
+++ b/apps/chimer/icon.txt
@@ -0,0 +1,2 @@
+
+widget.png: "https://icons8.com/icon/114436/alarm"
\ No newline at end of file
diff --git a/apps/chimer/metadata.json b/apps/chimer/metadata.json
new file mode 100644
index 000000000..d5bc04950
--- /dev/null
+++ b/apps/chimer/metadata.json
@@ -0,0 +1,16 @@
+{
+ "id": "chimer",
+ "name": "Chimer",
+ "version": "0.02",
+ "description": "A fork of Hour Chime that adds extra features such as: \n - Buzz or beep on every 60, 30 or 15 minutes. \n - Reapeat Chime up to 3 times \n - Set hours to disable chime",
+ "icon": "widget.png",
+ "type": "widget",
+ "tags": "widget",
+ "supports": ["BANGLEJS", "BANGLEJS2"],
+ "readme": "README.MD",
+ "storage": [
+ { "name": "chimer.wid.js", "url": "widget.js" },
+ { "name": "chimer.settings.js", "url": "settings.js" }
+ ],
+ "data": [{ "name": "chimer.json" }]
+}
diff --git a/apps/chimer/settings.js b/apps/chimer/settings.js
new file mode 100644
index 000000000..55160c9be
--- /dev/null
+++ b/apps/chimer/settings.js
@@ -0,0 +1,94 @@
+/**
+ * @param {function} back Use back() to return to settings menu
+ */
+
+(function (back) {
+ // default to buzzing
+ var FILE = "chimer.json";
+ var settings = {};
+ const chimes = ["Off", "Buzz", "Beep", "Both"];
+ const frequency = ["60 min", "30 min", "15 min", "1 min"];
+
+ var showMainMenu = () => {
+ E.showMenu({
+ "": { title: "Chimer" },
+ "< Back": () => back(),
+ "Chime Type": {
+ value: settings.type,
+ min: 0,
+ max: 2, // both is just silly
+ format: (v) => chimes[v],
+ onchange: (v) => {
+ settings.type = v;
+ writeSettings(settings);
+ },
+ },
+ Frequency: {
+ value: settings.freq,
+ min: 0,
+ max: 2,
+ format: (v) => frequency[v],
+ onchange: (v) => {
+ settings.freq = v;
+ writeSettings(settings);
+ },
+ },
+ Repetition: {
+ value: settings.repeat,
+ min: 1,
+ max: 5,
+ format: (v) => v,
+ onchange: (v) => {
+ settings.repeat = v;
+ writeSettings(settings);
+ },
+ },
+ "Sleep Mode": {
+ value: !!settings.sleep,
+ onchange: (v) => {
+ settings.sleep = v;
+ writeSettings(settings);
+ },
+ },
+ "Sleep Start": {
+ value: settings.start,
+ min: 0,
+ max: 23,
+ format: (v) => v,
+ onchange: (v) => {
+ settings.start = v;
+ writeSettings(settings);
+ },
+ },
+ "Sleep End": {
+ value: settings.end,
+ min: 0,
+ max: 23,
+ format: (v) => v,
+ onchange: (v) => {
+ settings.end = v;
+ writeSettings(settings);
+ },
+ },
+ });
+ };
+
+ var readSettings = () => {
+ var settings = require("Storage").readJSON(FILE, 1) || {
+ type: 1,
+ freq: 0,
+ repeat: 1,
+ sleep: true,
+ start: 6,
+ end: 22,
+ };
+ return settings;
+ };
+
+ var writeSettings = (settings) => {
+ require("Storage").writeJSON(FILE, settings);
+ };
+
+ settings = readSettings();
+ showMainMenu();
+});
diff --git a/apps/chimer/widget.js b/apps/chimer/widget.js
new file mode 100644
index 000000000..18358df9e
--- /dev/null
+++ b/apps/chimer/widget.js
@@ -0,0 +1,134 @@
+(function () {
+ // 0: off, 1: buzz, 2: beep, 3: both
+ var FILE = "chimer.json";
+
+ var readSettings = () => {
+ var settings = require("Storage").readJSON(FILE, 1) || {
+ type: 1,
+ freq: 0,
+ repeat: 1,
+ sleep: true,
+ start: 6,
+ end: 22,
+ };
+ return settings;
+ };
+
+ var settings = readSettings();
+
+ function sleep(milliseconds) {
+ const date = Date.now();
+ let currentDate = null;
+ do {
+ currentDate = Date.now();
+ } while (currentDate - date < milliseconds);
+ }
+
+ function chime() {
+ for (var i = 0; i < settings.repeat; i++) {
+ if (settings.type === 1) {
+ Bangle.buzz(100);
+ } else if (settings.type === 2) {
+ Bangle.beep();
+ } else {
+ return;
+ }
+ sleep(150);
+ }
+ }
+
+ let lastHour = new Date().getHours();
+ let lastMinute = new Date().getMinutes(); // don't chime when (re)loaded at a whole hour
+ function check() {
+ const now = new Date(),
+ h = now.getHours(),
+ m = now.getMinutes(),
+ s = now.getSeconds(),
+ ms = now.getMilliseconds();
+ if (
+ (settings.sleep && h > settings.end) ||
+ (settings.sleep && h >= settings.end && m !== 0) ||
+ (settings.sleep && h < settings.start)
+ ) {
+ var mLeft = 60 - m,
+ sLeft = mLeft * 60 - s,
+ msLeft = sLeft * 1000 - ms;
+ setTimeout(check, msLeft);
+ return;
+ }
+ if (settings.freq === 1) {
+ if ((m !== lastMinute && m === 0) || (m !== lastMinute && m === 30))
+ chime();
+ lastHour = h;
+ lastMinute = m;
+ // check again in 30 minutes
+ switch (true) {
+ case m / 30 >= 1:
+ var mLeft = 30 - (m - 30),
+ sLeft = mLeft * 60 - s,
+ msLeft = sLeft * 1000 - ms;
+ break;
+ case m / 30 < 1:
+ var mLeft = 30 - m,
+ sLeft = mLeft * 60 - s,
+ msLeft = sLeft * 1000 - ms;
+ break;
+ }
+ setTimeout(check, msLeft);
+ } else if (settings.freq === 2) {
+ if (
+ (m !== lastMinute && m === 0) ||
+ (m !== lastMinute && m === 15) ||
+ (m !== lastMinute && m === 30) ||
+ (m !== lastMinute && m === 45)
+ )
+ chime();
+ lastHour = h;
+ lastMinute = m;
+ // check again in 15 minutes
+ switch (true) {
+ case m / 15 >= 3:
+ var mLeft = 15 - (m - 45),
+ sLeft = mLeft * 60 - s,
+ msLeft = sLeft * 1000 - ms;
+ break;
+ case m / 15 >= 2:
+ var mLeft = 15 - (m - 30),
+ sLeft = mLeft * 60 - s,
+ msLeft = sLeft * 1000 - ms;
+ break;
+ case m / 15 >= 1:
+ var mLeft = 15 - (m - 15),
+ sLeft = mLeft * 60 - s,
+ msLeft = sLeft * 1000 - ms;
+ break;
+ case m / 15 < 1:
+ var mLeft = 15 - m,
+ sLeft = mLeft * 60 - s,
+ msLeft = sLeft * 1000 - ms;
+ break;
+ }
+ setTimeout(check, msLeft);
+ } else if (settings.freq === 3) {
+ if (m !== lastMinute) chime();
+ lastHour = h;
+ lastMinute = m;
+ // check again in 1 minute
+
+ var mLeft = 1,
+ sLeft = mLeft * 60 - s,
+ msLeft = sLeft * 1000 - ms;
+ setTimeout(check, msLeft);
+ } else {
+ if (h !== lastHour && m === 0) chime();
+ lastHour = h;
+ // check again in 60 minutes
+ var mLeft = 60 - m,
+ sLeft = mLeft * 60 - s,
+ msLeft = sLeft * 1000 - ms;
+ setTimeout(check, msLeft);
+ }
+ }
+
+ check();
+})();
diff --git a/apps/chimer/widget.png b/apps/chimer/widget.png
new file mode 100644
index 000000000..14edf4150
Binary files /dev/null and b/apps/chimer/widget.png differ
diff --git a/apps/chronowid/ChangeLog b/apps/chronowid/ChangeLog
index ed230b737..08a9ac828 100644
--- a/apps/chronowid/ChangeLog
+++ b/apps/chronowid/ChangeLog
@@ -4,3 +4,4 @@
0.04: Change to 7 segment font, move to top widget bar
Better auto-update behaviour, less RAM used
0.05: Fix error running app on new firmwares (fix #1140)
+0.06: Use default Bangle formatter for booleans
diff --git a/apps/chronowid/app.js b/apps/chronowid/app.js
index ab363ed17..b0ee7625a 100644
--- a/apps/chronowid/app.js
+++ b/apps/chronowid/app.js
@@ -79,7 +79,6 @@ function showMenu() {
},
'Timer on': {
value: settingsChronowid.started,
- format: v => v ? "On" : "Off",
onchange: v => {
settingsChronowid.started = v;
updateSettings();
diff --git a/apps/chronowid/metadata.json b/apps/chronowid/metadata.json
index 7cb32709f..69a5d3a2e 100644
--- a/apps/chronowid/metadata.json
+++ b/apps/chronowid/metadata.json
@@ -2,7 +2,7 @@
"id": "chronowid",
"name": "Chrono Widget",
"shortName": "Chrono Widget",
- "version": "0.05",
+ "version": "0.06",
"description": "Chronometer (timer) which runs as widget.",
"icon": "app.png",
"tags": "tool,widget",
diff --git a/apps/circlesclock/ChangeLog b/apps/circlesclock/ChangeLog
index 46eddd32b..c398a89b6 100644
--- a/apps/circlesclock/ChangeLog
+++ b/apps/circlesclock/ChangeLog
@@ -23,3 +23,6 @@
0.11: New color option: foreground color
Improve performance, reduce memory usage
Small optical adjustments
+0.12: Allow configuration of update interval
+0.13: Load step goal from Bangle health app as fallback
+ Memory optimizations
diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js
index a6aa1a8b1..fc501a5d0 100644
--- a/apps/circlesclock/app.js
+++ b/apps/circlesclock/app.js
@@ -1,10 +1,5 @@
const locale = require("locale");
const storage = require("Storage");
-const SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
-
-const shoesIcon = atob("EBCBAAAACAAcAB4AHgAeABwwADgGeAZ4AHgAMAAAAHAAIAAA");
-const temperatureIcon = atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA");
-
Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) {
// Actual height 39 (40 - 2)
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAB8AAAAAAAfAAAAAAAPwAAAAAAB8AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA4AAAAAAB+AAAAAAD/gAAAAAD/4AAAAAH/4AAAAAP/wAAAAAP/gAAAAAf/gAAAAAf/AAAAAA/+AAAAAB/+AAAAAB/8AAAAAD/4AAAAAH/4AAAAAD/wAAAAAA/wAAAAAAPgAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///wAAAB////gAAA////8AAA/////gAAP////8AAH8AAA/gAB8AAAD4AA+AAAAfAAPAAAADwADwAAAA8AA8AAAAPAAPAAAADwADwAAAA8AA8AAAAPAAPgAAAHwAB8AAAD4AAfwAAD+AAD/////AAA/////wAAH////4AAAf///4AAAB///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAPgAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPAAAAAAAH/////wAB/////8AA//////AAP/////wAD/////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAfgAADwAAP4AAB8AAH+AAA/AAD/gAAfwAB/AAAf8AAfAAAP/AAPgAAH7wAD4AAD88AA8AAB+PAAPAAA/DwADwAAfg8AA8AAPwPAAPAAH4DwADwAH8A8AA+AD+APAAPwB/ADwAB/D/gA8AAf//gAPAAD//wADwAAf/wAA8AAD/4AAPAAAHwAADwAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAADgAAAHwAA+AAAD8AAP4AAB/AAD/AAA/wAA/wAAf4AAD+AAHwAAAPgAD4APAB8AA+ADwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA8AH4APAAPgD+AHwAB8B/wD4AAf7/+B+AAD//v//AAA//x//wAAD/4P/4AAAf8B/4AAAAYAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAHwAAAAAAH8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/vAAAAAB/jwAAAAA/g8AAAAA/wPAAAAAfwDwAAAAf4A8AAAAf4APAAAAP8ADwAAAP8AA8AAAH8AAPAAAD/////8AA//////AAP/////wAD/////8AA//////AAAAAAPAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAB/APwAAH//wD+AAD//8A/wAA///AH+AAP//wAPgAD/B4AB8AA8A+AAfAAPAPAADwADwDwAA8AA8A8AAPAAPAPAADwADwD4AA8AA8A+AAPAAPAPwAHwADwD8AD4AA8AfwD+AAPAH///AADwA///wAA8AH//4AAPAAf/4AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAD//+AAAAD///4AAAD////AAAB////4AAA/78D/AAAfw8AH4AAPweAA+AAD4PgAHwAB8DwAA8AAfA8AAPAAHgPAADwAD4DwAA8AA+A8AAPAAPAPgAHwADwD4AB8AA8AfgA+AAPAH+B/gAAAA///wAAAAH//4AAAAA//8AAAAAH/8AAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAA8AAAABAAPAAAABwADwAAAB8AA8AAAB/AAPAAAB/wADwAAD/8AA8AAD/8AAPAAD/4AADwAD/4AAA8AD/4AAAPAH/wAAADwH/wAAAA8H/wAAAAPH/wAAAAD3/gAAAAA//gAAAAAP/gAAAAAD/gAAAAAA/AAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwA/4AAAH/Af/AAAH/8P/4AAD//n//AAA//7//4AAfx/+A+AAHwD+AHwAD4AfgB8AA8AHwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA+AH4AfAAHwD+AHwAB/D/4D4AAP/+/n+AAD//n//AAAf/w//gAAB/wH/wAAAHwA/4AAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/8AAAAAD//wAAAAB//+AAAAA///wAAAAf4H+APAAH4AfgDwAD8AB8A8AA+AAfAPAAPAADwDwADwAA8B8AA8AAPAfAAPAADwHgADwAA8D4AA+AAeB+AAHwAHg/AAB+ADwfgAAP8D4/4AAD////8AAAf///8AAAB///+AAAAP//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAOAAAB8AAHwAAAfgAD8AAAH4AA/AAAB8AAHwAAAOAAA4AAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("DRUcHBwcHBwcHBwcDA=="), 50+(scale<<8)+(1<<16));
@@ -22,10 +17,16 @@ let settings = Object.assign(
storage.readJSON("circlesclock.default.json", true) || {},
storage.readJSON(SETTINGS_FILE, true) || {}
);
-// Load step goal from pedometer widget as fallback
+
+// Load step goal from health app and pedometer widget as fallback
if (settings.stepGoal == undefined) {
- const d = storage.readJSON("wpedom.json", true) || {};
- settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000;
+ let d = storage.readJSON("health.json", true) || {};
+ settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.stepGoal : undefined;
+
+ if (settings.stepGoal == undefined) {
+ d = storage.readJSON("wpedom.json", true) || {};
+ settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000;
+ }
}
/*
@@ -125,20 +126,11 @@ function draw() {
g.setFontAlign(0, 0);
g.drawString(locale.date(new Date()), w / 2, h2);
g.drawString(locale.dow(new Date()), w / 2, h2 + dowOffset);
-
- // draw the circles a little bit delayed so we decrease the blocking time
- setTimeout(function() {
- drawCircle(1);
- }, 1);
- setTimeout(function() {
- drawCircle(2);
- }, 1);
- setTimeout(function() {
- drawCircle(3);
- }, 1);
- setTimeout(function() {
- if (circleCount >= 4) drawCircle(4);
- }, 1);
+
+ drawCircle(1);
+ drawCircle(2);
+ drawCircle(3);
+ if (circleCount >= 4) drawCircle(4);
}
function drawCircle(index) {
@@ -294,7 +286,7 @@ function drawSteps(w) {
writeCircleText(w, shortValue(steps));
- g.drawImage(getImage(shoesIcon, getCircleIconColor("steps", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
+ g.drawImage(getImage(atob("EBCBAAAACAAcAB4AHgAeABwwADgGeAZ4AHgAMAAAAHAAIAAA"), getCircleIconColor("steps", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
}
function drawStepsDistance(w) {
@@ -319,7 +311,7 @@ function drawStepsDistance(w) {
writeCircleText(w, shortValue(stepsDistance));
- g.drawImage(getImage(shoesIcon, getCircleIconColor("stepsDistance", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
+ g.drawImage(getImage(atob("EBCBAAAACAAcAB4AHgAeABwwADgGeAZ4AHgAMAAAAHAAIAAA"), getCircleIconColor("stepsDistance", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
}
function drawHeartRate(w) {
@@ -490,8 +482,8 @@ function drawTemperature(w) {
if (temperature)
writeCircleText(w, locale.temp(temperature));
-
- g.drawImage(getImage(temperatureIcon, getCircleIconColor("temperature", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
+
+ g.drawImage(getImage(atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"), getCircleIconColor("temperature", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
});
}
@@ -517,7 +509,7 @@ function drawPressure(w) {
if (pressure)
writeCircleText(w, Math.round(pressure));
- g.drawImage(getImage(temperatureIcon, getCircleIconColor("pressure", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
+ g.drawImage(getImage(atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"), getCircleIconColor("pressure", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
});
}
@@ -543,7 +535,7 @@ function drawAltitude(w) {
if (altitude)
writeCircleText(w, locale.distance(Math.round(altitude)));
- g.drawImage(getImage(temperatureIcon, getCircleIconColor("altitude", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
+ g.drawImage(getImage(atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"), getCircleIconColor("altitude", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
});
}
@@ -614,8 +606,8 @@ function getWeatherIconByCode(code) {
default:
return weatherCloudy;
}
- default:
- return undefined;
+ default:
+ return undefined;
}
}
@@ -641,6 +633,7 @@ function formatSeconds(s) {
function getSunData() {
if (location != undefined && location.lat != undefined) {
+ const SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
// get today's sunlight times for lat/lon
return SunCalc ? SunCalc.getTimes(new Date(), location.lat, location.lon) : undefined;
}
@@ -848,8 +841,8 @@ Bangle.loadWidgets();
// schedule a draw for the next minute
setTimeout(function() {
- // draw every 60 seconds
- setInterval(draw,60000);
+ // draw in interval
+ setInterval(draw, settings.updateInterval * 1000);
}, 60000 - (Date.now() % 60000));
draw();
diff --git a/apps/circlesclock/default.json b/apps/circlesclock/default.json
index cb6bfcff8..ea00dc347 100644
--- a/apps/circlesclock/default.json
+++ b/apps/circlesclock/default.json
@@ -21,5 +21,6 @@
"circle2colorizeIcon": true,
"circle3colorizeIcon": true,
"circle4colorizeIcon": false,
- "hrmValidity": 60
+ "hrmValidity": 60,
+ "updateInterval": 60
}
diff --git a/apps/circlesclock/metadata.json b/apps/circlesclock/metadata.json
index b16f14c06..837fcaa88 100644
--- a/apps/circlesclock/metadata.json
+++ b/apps/circlesclock/metadata.json
@@ -1,7 +1,7 @@
{ "id": "circlesclock",
"name": "Circles clock",
"shortName":"Circles clock",
- "version":"0.11",
+ "version":"0.13",
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
"icon": "app.png",
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
diff --git a/apps/circlesclock/settings.js b/apps/circlesclock/settings.js
index 0b9e94aca..fb23f8d5e 100644
--- a/apps/circlesclock/settings.js
+++ b/apps/circlesclock/settings.js
@@ -58,6 +58,16 @@
min: 0, max: 2,
format: v => weatherData[v],
onchange: x => save('weatherCircleData', weatherData[x]),
+ },
+ /*LANG*/'update interval': {
+ value: settings.updateInterval,
+ min: 0,
+ max : 3600,
+ step: 30,
+ format: x => {
+ return x + 's';
+ },
+ onchange: x => save('updateInterval', x),
}
};
E.showMenu(menu);
@@ -100,7 +110,7 @@
/*LANG*/'valid period': {
value: settings.hrmValidity,
min: 10,
- max : 600,
+ max : 1800,
step: 10,
format: x => {
return x + "s";
@@ -117,9 +127,9 @@
/*LANG*/'< Back': ()=>showMainMenu(),
/*LANG*/'goal': {
value: settings.stepGoal,
- min: 2000,
+ min: 1000,
max : 50000,
- step: 2000,
+ step: 500,
format: x => {
return x;
},
@@ -127,9 +137,9 @@
},
/*LANG*/'distance goal': {
value: settings.stepDistanceGoal,
- min: 2000,
- max : 30000,
- step: 1000,
+ min: 1000,
+ max : 50000,
+ step: 500,
format: x => {
return x;
},
diff --git a/apps/clockcal/ChangeLog b/apps/clockcal/ChangeLog
index 8b40a87ac..27d4fc7f4 100644
--- a/apps/clockcal/ChangeLog
+++ b/apps/clockcal/ChangeLog
@@ -1,3 +1,6 @@
0.01: Initial upload
0.02: Added scrollable calendar and swipe gestures
0.03: Configurable drag gestures
+0.04: Use default Bangle formatter for booleans
+0.05: Improved colors (connected vs disconnected)
+0.06: Tell clock widgets to hide.
diff --git a/apps/clockcal/app.js b/apps/clockcal/app.js
index 5e8c7f796..58ddd7ef5 100644
--- a/apps/clockcal/app.js
+++ b/apps/clockcal/app.js
@@ -1,3 +1,4 @@
+Bangle.setUI("clock");
Bangle.loadWidgets();
var s = Object.assign({
@@ -123,7 +124,7 @@ function drawMinutes() {
var d = new Date();
var hours = s.MODE24 ? d.getHours().toString().padStart(2, ' ') : ((d.getHours() + 24) % 12 || 12).toString().padStart(2, ' ');
var minutes = d.getMinutes().toString().padStart(2, '0');
- var textColor = NRF.getSecurityStatus().connected ? '#fff' : '#f00';
+ var textColor = NRF.getSecurityStatus().connected ? '#99f' : '#fff';
var size = 50;
var clock_x = (w - 20) / 2;
if (dimSeconds) {
@@ -307,4 +308,4 @@ NRF.on('disconnect', BTevent);
dimSeconds = Bangle.isLocked();
drawWatch();
-Bangle.setUI("clock");
+
diff --git a/apps/clockcal/metadata.json b/apps/clockcal/metadata.json
index 3998215d7..872211495 100644
--- a/apps/clockcal/metadata.json
+++ b/apps/clockcal/metadata.json
@@ -1,7 +1,7 @@
{
"id": "clockcal",
"name": "Clock & Calendar",
- "version": "0.03",
+ "version": "0.06",
"description": "Clock with Calendar",
"readme":"README.md",
"icon": "app.png",
diff --git a/apps/clockcal/settings.js b/apps/clockcal/settings.js
index abedad99b..d4cc4df68 100644
--- a/apps/clockcal/settings.js
+++ b/apps/clockcal/settings.js
@@ -26,7 +26,6 @@
"< Back": () => back(),
'Buzz(dis)conn.?': {
value: settings.BUZZ_ON_BT,
- format: v => v ? "On" : "Off",
onchange: v => {
settings.BUZZ_ON_BT = v;
writeSettings();
@@ -59,7 +58,6 @@
},
'Red Saturday?': {
value: settings.REDSAT,
- format: v => v ? "On" : "Off",
onchange: v => {
settings.REDSAT = v;
writeSettings();
@@ -67,7 +65,6 @@
},
'Red Sunday?': {
value: settings.REDSUN,
- format: v => v ? "On" : "Off",
onchange: v => {
settings.REDSUN = v;
writeSettings();
diff --git a/apps/cogclock/15x32.png b/apps/cogclock/15x32.png
new file mode 100644
index 000000000..0af326e71
Binary files /dev/null and b/apps/cogclock/15x32.png differ
diff --git a/apps/cogclock/ChangeLog b/apps/cogclock/ChangeLog
new file mode 100644
index 000000000..f4bfe77a5
--- /dev/null
+++ b/apps/cogclock/ChangeLog
@@ -0,0 +1,3 @@
+0.01: New clock
+0.02: Use ClockFace library, add settings
+0.03: Use ClockFace_menu.addSettingsFile
diff --git a/apps/cogclock/app.js b/apps/cogclock/app.js
new file mode 100644
index 000000000..d24031684
--- /dev/null
+++ b/apps/cogclock/app.js
@@ -0,0 +1,111 @@
+Graphics.prototype.setFont15x32N = function() {
+ this.setFontCustom(atob(
+ // 15x32.png, converted using http://ebfc.mattbrailsford.com/
+ "/////oAAAAKAAAACgAAAAoAAAAKAAAACgf//AoEAAQKB//8CgAAAAoAAAAKAAAACgAAAAoAAAAL////+/wAB/oEAAQKBAAECgf//AoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAAC////AgAAAQIAAAH+/w///oEIAAKBCAACgQgAAoEIAAKBCAACgQg/AoEIIQKB+CECgAAhAoAAIQKAACECgAAhAoAAIQL//+H+/w/h/oEIIQKBCCECgQghAoEIIQKBCCECgQghAoEIIQKB+D8CgAAAAoAAAAKAAAACgAAAAoAAAAL////+///gAIAAIACAACAAgAAgAIAAIAD/+CAAAAggAAAIIAAACD/+//gAAoAAAAKAAAACgAAAAoAAAAL////+///h/oAAIQKAACECgAAhAoAAIQKAACECgfghAoEIIQKBCD8CgQgAAoEIAAKBCAACgQgAAoEIAAL/D//+/////oAAAAKAAAACgAAAAoAAAAKAAAACgfg/AoEIIQKBCD8CgQgAAoEIAAKBCAACgQgAAoEIAAL/D//+/wAAAIEAAACBAAAAgQAAAIEAAACBAAAAgQAAAIH///6AAAACgAAAAoAAAAKAAAACgAAAAoAAAAL////+/////oAAAAKAAAACgAAAAoAAAAKAAAACgfg/AoEIIQKB+D8CgAAAAoAAAAKAAAACgAAAAoAAAAL////+///h/oAAIQKAACECgAAhAoAAIQKAACECgfghAoEIIQKB+D8CgAAAAoAAAAKAAAACgAAAAoAAAAL////+"
+ ), "0".charCodeAt(0), 15, 32);
+};
+
+/**
+ * Add coordinates for nth tooth to vertices
+ * @param {array} poly Array to add points to
+ * @param {number} n Tooth number
+ */
+function addTooth(poly, n) {
+ const
+ tau = Math.PI*2, arc = tau/clock.teeth,
+ e = arc*clock.edge, p = arc*clock.point, s = (arc-(e+p))/2; // edge,point,slopes
+ const sin = Math.sin, cos = Math.cos,
+ x = clock.x, y = clock.y,
+ r2 = clock.r2, r3 = clock.r3;
+ let r = (n-1)*arc+e/2; // rads
+ poly.push(x+r2*sin(r), y-r2*cos(r));
+ r += s;
+ poly.push(x+r3*sin(r), y-r3*cos(r));
+ r += p;
+ poly.push(x+r3*sin(r), y-r3*cos(r));
+ r += s;
+ poly.push(x+r2*sin(r), y-r2*cos(r));
+}
+
+/**
+ * @param {number} n Tooth number to fill (1-based)
+ * @param col Fill color
+ */
+function fillTooth(n, col) {
+ if (!n) return; // easiest to check here
+ let poly = [];
+ addTooth(poly, n);
+ g.setColor(col).fillPoly(poly)
+ .setColor(g.theme.fg2).drawPoly(poly); // fillPoly colored over the outline
+}
+
+const ClockFace = require("ClockFace");
+const clock = new ClockFace({
+ precision: 1,
+ settingsFile: "cogclock.settings.json",
+ init: function() {
+ this.r1 = 84; // inner radius
+ this.r3 = Math.min(Bangle.appRect.w/2, Bangle.appRect.h/2); // outer radius
+ this.r2 = (this.r1*3+this.r3*2)/5;
+ this.teeth = 12;
+ this.edge = 0.45;
+ this.point = 0.35; // as fraction of arc
+ this.x = Bangle.appRect.x+Bangle.appRect.w/2;
+ this.y = Bangle.appRect.y+Bangle.appRect.h/2;
+ },
+ draw: function(d) {
+ const x = this.x, y = this.y;
+ g.setColor(g.theme.bg2).fillCircle(x, y, this.r2) // fill cog
+ .setColor(g.theme.bg).fillCircle(x, y, this.r1) // clear center
+ .setColor(g.theme.fg2).drawCircle(x, y, this.r1); // draw inner border
+ let poly = []; // complete teeth outline
+ for(let t = 1; t<=this.teeth; t++) {
+ fillTooth(t, g.theme.bg2);
+ addTooth(poly, t);
+ }
+ g.drawPoly(poly, true); // draw outer border
+ if (!this.showDate) {
+ // draw top/bottom rectangles (instead of year/date)
+ g.reset()
+ .fillRect(x-30, y-60, x+29, y-33).clearRect(x-28, y-58, x+27, y-33)
+ .fillRect(x-30, y+60, x+29, y+30).clearRect(x-28, y+58, x+27, y+30);
+ }
+ this.tooth = 0;
+ this.update(d, {s: 1, m: 1, h: 1, d: 1});
+ },
+ update: function(d, c) {
+ g.reset();
+ const pad2 = num => (num<10 ? "0" : "")+num,
+ year = d.getFullYear(),
+ date = pad2(d.getDate())+pad2(d.getMonth()),
+ time = pad2(d.getHours())+pad2(d.getMinutes()),
+ tooth = Math.round(d.getSeconds()/60*this.teeth);
+ const x = this.x, y = this.y;
+ if (c.m) {
+ g.setFont("15x32N:2").setFontAlign(0, 0) // center middle
+ .drawString(time, x, y, true);
+ }
+ if (this.showDate) {
+ if (c.d) {
+ g.setFont("15x32N").setFontAlign(0, -1) // center top
+ .drawString(year, x, y+32, true)
+ .setFont("15x32N").setFontAlign(0, 1) // center bottom
+ .drawString(date, x, y-32, true);
+ }
+ }
+
+ if (tooth!==this.tooth) {
+ if (tooth>this.tooth) {
+ for(let t = this.tooth; t<=tooth; t++) { // fill missing teeth
+ fillTooth(t, g.theme.fg2);
+ }
+ } else {
+ for(let t = this.tooth; t>tooth; t--) { // erase extraneous teeth
+ fillTooth(t, g.theme.bg2);
+ }
+ }
+ }
+ this.tooth = tooth;
+ }
+});
+clock.start();
diff --git a/apps/cogclock/icon.js b/apps/cogclock/icon.js
new file mode 100644
index 000000000..899cfc7c1
--- /dev/null
+++ b/apps/cogclock/icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwhC/ACcikQXpCQUCC4MgBAgqMCQIXEAgQXNBwIDCAggXOABAXLHwQAHMYQXmmczI5oiCBwUjCwIABmQXDEgJ0KCwMwCwMDDAgyGLYoWBgAXBgAYBMZIXEkYWBC4YYBGAh7FFwgHCC4YEBPRIwCFwYXFGAaqHC56oIIwgXFJAbUJLwpgHI4qPDIwpIFR4wWDLwa6BAAQHDVIYYCC/gYCC453MPIR3HU5gADd5bXHC4rvJMAYAECwJeCd5MjGAjVDC4ZHGNARIFGAgNDFw5IJUogwFC4gwBDAhGBBghIFBQhhBbYguEPAweCDAgACCwZACNg5LFXQYsIC5QAFdg4XcCxJHNBwYTEC6A+BJYQEEC5YYBMYhbCCxo0GCaIXbAHgA="))
\ No newline at end of file
diff --git a/apps/cogclock/icon.png b/apps/cogclock/icon.png
new file mode 100644
index 000000000..8520fcf5d
Binary files /dev/null and b/apps/cogclock/icon.png differ
diff --git a/apps/cogclock/metadata.json b/apps/cogclock/metadata.json
new file mode 100644
index 000000000..29000b589
--- /dev/null
+++ b/apps/cogclock/metadata.json
@@ -0,0 +1,20 @@
+{
+ "id": "cogclock",
+ "name": "Cog Clock",
+ "version": "0.03",
+ "description": "A cross-shaped clock inside a cog",
+ "icon": "icon.png",
+ "screenshots": [{"url":"screenshot.png"}],
+ "type": "clock",
+ "tags": "clock",
+ "supports": ["BANGLEJS"],
+ "allow_emulator": true,
+ "storage": [
+ {"name":"cogclock.app.js","url":"app.js"},
+ {"name":"cogclock.settings.js","url":"settings.js"},
+ {"name":"cogclock.img","url":"icon.js","evaluate":true}
+ ],
+ "data": [
+ {"name": "cogclock.settings.json"}
+ ]
+}
diff --git a/apps/cogclock/screenshot.png b/apps/cogclock/screenshot.png
new file mode 100644
index 000000000..f49709aef
Binary files /dev/null and b/apps/cogclock/screenshot.png differ
diff --git a/apps/cogclock/settings.js b/apps/cogclock/settings.js
new file mode 100644
index 000000000..a91b033d0
--- /dev/null
+++ b/apps/cogclock/settings.js
@@ -0,0 +1,10 @@
+(function(back) {
+ let menu = {
+ "": {"title": /*LANG*/"Cog Clock"},
+ /*LANG*/"< Back": back,
+ };
+ require("ClockFace_menu").addSettingsFile(menu, "cogclock.settings.json", [
+ "showDate", "loadWidgets"
+ ]);
+ E.showMenu(menu);
+});
diff --git a/apps/color_catalog/Changelog b/apps/color_catalog/ChangeLog
similarity index 100%
rename from apps/color_catalog/Changelog
rename to apps/color_catalog/ChangeLog
diff --git a/apps/colorful_clock/ChangeLog b/apps/colorful_clock/ChangeLog
new file mode 100644
index 000000000..54ee389e3
--- /dev/null
+++ b/apps/colorful_clock/ChangeLog
@@ -0,0 +1,3 @@
+...
+0.03: First update with ChangeLog Added
+0.04: Tell clock widgets to hide.
diff --git a/apps/colorful_clock/app.js b/apps/colorful_clock/app.js
index afc6b321f..ba6272e9b 100644
--- a/apps/colorful_clock/app.js
+++ b/apps/colorful_clock/app.js
@@ -3,6 +3,8 @@
let outerRadius = Math.min(CenterX,CenterY) * 0.9;
+ Bangle.setUI('clock');
+
Bangle.loadWidgets();
/**** updateClockFaceSize ****/
@@ -241,7 +243,3 @@
refreshDisplay();
}
});
-
- Bangle.loadWidgets();
-
- Bangle.setUI('clock');
diff --git a/apps/colorful_clock/metadata.json b/apps/colorful_clock/metadata.json
index 5b6dbe87e..237acf81c 100644
--- a/apps/colorful_clock/metadata.json
+++ b/apps/colorful_clock/metadata.json
@@ -1,7 +1,7 @@
{ "id": "colorful_clock",
"name": "Colorful Analog Clock",
"shortName":"Colorful Clock",
- "version":"0.03",
+ "version":"0.04",
"description": "a colorful analog clock",
"icon": "app-icon.png",
"type": "clock",
diff --git a/apps/compass/ChangeLog b/apps/compass/ChangeLog
index d1adafc4c..deb1072f5 100644
--- a/apps/compass/ChangeLog
+++ b/apps/compass/ChangeLog
@@ -4,3 +4,4 @@
0.04: Fix for Bangle.js 2 and themes
0.05: Fix bearing not clearing correctly (visible in single or double digit bearings)
0.06: Add button for force compass calibration
+0.07: Use 360-heading to output the correct heading value (fix #1866)
diff --git a/apps/compass/compass.js b/apps/compass/compass.js
index 4730111ac..dd398ffa6 100644
--- a/apps/compass/compass.js
+++ b/apps/compass/compass.js
@@ -34,7 +34,7 @@ var oldHeading = 0;
Bangle.on('mag', function(m) {
if (!Bangle.isLCDOn()) return;
g.reset();
- if (isNaN(m.heading)) {
+ if (isNaN(m.heading)) {
if (!wasUncalibrated) {
g.clearRect(0,24,W,48);
g.setFontAlign(0,-1).setFont("6x8");
@@ -49,7 +49,7 @@ Bangle.on('mag', function(m) {
g.setFontAlign(0,0).setFont("6x8",3);
var y = 36;
g.clearRect(M-40,24,M+40,48);
- g.drawString(Math.round(m.heading),M,y,true);
+ g.drawString(Math.round(360-m.heading),M,y,true);
}
diff --git a/apps/compass/metadata.json b/apps/compass/metadata.json
index 3e3b37f72..a3995a123 100644
--- a/apps/compass/metadata.json
+++ b/apps/compass/metadata.json
@@ -1,7 +1,7 @@
{
"id": "compass",
"name": "Compass",
- "version": "0.06",
+ "version": "0.07",
"description": "Simple compass that points North",
"icon": "compass.png",
"screenshots": [{"url":"screenshot_compass.png"}],
diff --git a/apps/configurable_clock/ChangeLog b/apps/configurable_clock/ChangeLog
new file mode 100644
index 000000000..9d55c1a91
--- /dev/null
+++ b/apps/configurable_clock/ChangeLog
@@ -0,0 +1,3 @@
+...
+0.02: First update with ChangeLog Added
+0.03: Tell clock widgets to hide.
diff --git a/apps/configurable_clock/app.js b/apps/configurable_clock/app.js
index 157d57741..45c86c7e9 100644
--- a/apps/configurable_clock/app.js
+++ b/apps/configurable_clock/app.js
@@ -5,6 +5,7 @@
let ScreenWidth = g.getWidth(), CenterX;
let ScreenHeight = g.getHeight(), CenterY, outerRadius;
+ Bangle.setUI('clock');
Bangle.loadWidgets();
/**** updateClockFaceSize ****/
@@ -1377,4 +1378,3 @@
}
});
- Bangle.setUI('clock');
diff --git a/apps/configurable_clock/metadata.json b/apps/configurable_clock/metadata.json
index 28feae7e4..687a5b212 100644
--- a/apps/configurable_clock/metadata.json
+++ b/apps/configurable_clock/metadata.json
@@ -1,7 +1,7 @@
{ "id": "configurable_clock",
"name": "Configurable Analog Clock",
"shortName":"Configurable Clock",
- "version":"0.02",
+ "version":"0.03",
"description": "an analog clock with several kinds of faces, hands and colors to choose from",
"icon": "app-icon.png",
"type": "clock",
diff --git a/apps/contourclock/ChangeLog b/apps/contourclock/ChangeLog
index 032edc9b5..d415a604d 100644
--- a/apps/contourclock/ChangeLog
+++ b/apps/contourclock/ChangeLog
@@ -6,3 +6,4 @@
0.24: Added previews to the customizer.
0.25: Fixed a bug that would let widgets change the color of the clock.
0.26: Time formatted to locale
+0.27: Fixed the timing code, which sometimes did not update for one minute
diff --git a/apps/contourclock/app.js b/apps/contourclock/app.js
index cdfadd217..d5c97edfa 100644
--- a/apps/contourclock/app.js
+++ b/apps/contourclock/app.js
@@ -7,6 +7,13 @@ if (settings.fontIndex==undefined) {
require('Storage').writeJSON("myapp.json", settings);
}
+function queueDraw() {
+ setTimeout(function() {
+ draw();
+ queueDraw();
+ }, 60000 - (Date.now() % 60000));
+}
+
function draw() {
var date = new Date();
// Draw day of the week
@@ -24,7 +31,5 @@ Bangle.setUI("clock");
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
+queueDraw();
draw();
-setTimeout(function() {
- setInterval(draw,60000);
-}, 60000 - Date.now() % 60000);
diff --git a/apps/contourclock/metadata.json b/apps/contourclock/metadata.json
index ec29c390b..eb0dd39fb 100644
--- a/apps/contourclock/metadata.json
+++ b/apps/contourclock/metadata.json
@@ -1,7 +1,7 @@
{ "id": "contourclock",
"name": "Contour Clock",
"shortName" : "Contour Clock",
- "version":"0.26",
+ "version":"0.27",
"icon": "app.png",
"description": "A Minimalist clockface with large Digits. Now with more fonts!",
"screenshots" : [{"url":"cc-screenshot-1.png"},{"url":"cc-screenshot-2.png"}],
diff --git a/apps/coretemp/ChangeLog b/apps/coretemp/ChangeLog
index ad6f0742d..7386bbc35 100644
--- a/apps/coretemp/ChangeLog
+++ b/apps/coretemp/ChangeLog
@@ -1,3 +1,4 @@
0.01: New app
0.02: Cleanup interface and add settings, widget, add skin temp reporting.
0.03: Move code for recording to this app
+0.04: Use default Bangle formatter for booleans
diff --git a/apps/coretemp/metadata.json b/apps/coretemp/metadata.json
index cb12624ae..87cb42722 100644
--- a/apps/coretemp/metadata.json
+++ b/apps/coretemp/metadata.json
@@ -1,7 +1,7 @@
{
"id": "coretemp",
"name": "CoreTemp",
- "version": "0.03",
+ "version": "0.04",
"description": "Display CoreTemp device sensor data",
"icon": "coretemp.png",
"type": "app",
diff --git a/apps/coretemp/settings.js b/apps/coretemp/settings.js
index 3fc2dfbf2..23ea09167 100644
--- a/apps/coretemp/settings.js
+++ b/apps/coretemp/settings.js
@@ -35,7 +35,6 @@ const menu = {
'< Back' : back,
'Enabled' : {
value : !!s.enabled,
- format : v => v ? "Yes" : "No",
onchange : v => {
s.enabled = v;
updateSettings();
diff --git a/apps/counter/ChangeLog b/apps/counter/ChangeLog
index f3f1c4eac..8402b3467 100644
--- a/apps/counter/ChangeLog
+++ b/apps/counter/ChangeLog
@@ -1,3 +1,4 @@
0.01: New App!
0.02: Added decrement and touch functions
0.03: Set color - ensures widgets don't end up coloring the counter's text
+0.04: Adopted for BangleJS 2
diff --git a/apps/counter/counter.js b/apps/counter/counter.js
index 3e0687944..0054ada6d 100644
--- a/apps/counter/counter.js
+++ b/apps/counter/counter.js
@@ -1,45 +1,104 @@
var counter = 0;
+const BANGLEJS2 = process.env.HWVERSION == 2;
+
+if (BANGLEJS2) {
+ var drag;
+ var y = 45;
+ var x = 5;
+} else {
+ var y = 100;
+ var x = 25;
+}
function updateScreen() {
- g.clearRect(0, 50, 250, 150);
- g.setColor(0xFFFF);
+ if (BANGLEJS2) {
+ g.clearRect(0, 50, 250, 130);
+ } else {
+ g.clearRect(0, 50, 250, 150);
+ }
+ g.setBgColor(g.theme.bg).setColor(g.theme.fg);
g.setFont("Vector",40).setFontAlign(0,0);
g.drawString(Math.floor(counter), g.getWidth()/2, 100);
- g.drawString('-', 45, 100);
- g.drawString('+', 185, 100);
+ if (!BANGLEJS2) {
+ g.drawString('-', 45, 100);
+ g.drawString('+', 185, 100);
+ }
}
-// add a count by using BTN1 or BTN5
-setWatch(() => {
- counter += 1;
- updateScreen();
-}, BTN1, {repeat:true});
+if (BANGLEJS2) {
+ setWatch(() => {
+ counter = 0;
+ updateScreen();
+ }, BTN1, {repeat:true});
+ Bangle.on("drag", e => {
+ if (!drag) { // start dragging
+ drag = {x: e.x, y: e.y};
+ } else if (!e.b) { // released
+ const dx = e.x-drag.x, dy = e.y-drag.y;
+ drag = null;
+ if (Math.abs(dx)>Math.abs(dy)+10) {
+ // horizontal
+ if (dx < dy) {
+ //console.log("left " + dx + " " + dy);
+ } else {
+ //console.log("right " + dx + " " + dy);
+ }
+ } else if (Math.abs(dy)>Math.abs(dx)+10) {
+ // vertical
+ if (dx < dy) {
+ //console.log("down " + dx + " " + dy);
+ if (counter > 0) counter -= 1;
+ updateScreen();
+ } else {
+ //console.log("up " + dx + " " + dy);
+ counter += 1;
+ updateScreen();
+ }
+ } else {
+ //console.log("tap " + e.x + " " + e.y);
+ }
+ }
+ });
+ } else {
-setWatch(() => {
- counter += 1;
- updateScreen();
-}, BTN5, {repeat:true});
+ // add a count by using BTN1 or BTN5
+ setWatch(() => {
+ counter += 1;
+ updateScreen();
+ }, BTN1, {repeat:true});
+
+ setWatch(() => {
+ counter += 1;
+ updateScreen();
+ }, BTN5, {repeat:true});
+
+ // subtract a count by using BTN3 or BTN4
+ setWatch(() => {
+ if (counter > 0) counter -= 1;
+ updateScreen();
+ }, BTN4, {repeat:true});
+
+ setWatch(() => {
+ if (counter > 0) counter -= 1;
+ updateScreen();
+ }, BTN3, {repeat:true});
+
+ // reset by using BTN2
+ setWatch(() => {
+ counter = 0;
+ updateScreen();
+ }, BTN2, {repeat:true});
+}
-// subtract a count by using BTN3 or BTN4
-setWatch(() => {
- counter -= 1;
- updateScreen();
-}, BTN4, {repeat:true});
-
-setWatch(() => {
- counter -= 1;
- updateScreen();
-}, BTN3, {repeat:true});
-
-// reset by using BTN2
-setWatch(() => {
- counter = 0;
- updateScreen();
-}, BTN2, {repeat:true});
g.clear(1).setFont("6x8");
-g.drawString('Tap right or BTN1 to increase\nTap left or BTN3 to decrease\nPress BTN2 to reset.', 25, 200);
+g.setBgColor(g.theme.bg).setColor(g.theme.fg);
+if (BANGLEJS2) {
+ g.drawString('Swipe up to increase\nSwipe down to decrease\nPress button to reset.', x, 100 + y);
+} else {
+ g.drawString('Tap right or BTN1 to increase\nTap left or BTN3 to decrease\nPress BTN2 to reset.', x, 100 + y);
+}
Bangle.loadWidgets();
Bangle.drawWidgets();
diff --git a/apps/counter/metadata.json b/apps/counter/metadata.json
index e455fda95..daba58d39 100644
--- a/apps/counter/metadata.json
+++ b/apps/counter/metadata.json
@@ -1,11 +1,11 @@
{
"id": "counter",
"name": "Counter",
- "version": "0.03",
+ "version": "0.04",
"description": "Simple counter",
"icon": "counter_icon.png",
"tags": "tool",
- "supports": ["BANGLEJS"],
+ "supports": ["BANGLEJS", "BANGLEJS2"],
"screenshots": [{"url":"bangle1-counter-screenshot.png"}],
"allow_emulator": true,
"storage": [
diff --git a/apps/crowclk/ChangeLog b/apps/crowclk/ChangeLog
index 4f48bdd14..1c4f6f43b 100644
--- a/apps/crowclk/ChangeLog
+++ b/apps/crowclk/ChangeLog
@@ -1,3 +1,4 @@
0.01: New App!
0.02: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up".
0.03: Fix the clock for dark mode.
+0.04: Tell clock widgets to hide.
diff --git a/apps/crowclk/crow_clock.js b/apps/crowclk/crow_clock.js
index eee1653cb..7e608ef19 100644
--- a/apps/crowclk/crow_clock.js
+++ b/apps/crowclk/crow_clock.js
@@ -136,9 +136,9 @@ Bangle.on('lcdPower', (on) => {
g.clear();
+// Show launcher when button pressed
+Bangle.setUI("clock");
Bangle.loadWidgets();
Bangle.drawWidgets();
startTimers();
-// Show launcher when button pressed
-Bangle.setUI("clock");
diff --git a/apps/crowclk/metadata.json b/apps/crowclk/metadata.json
index 6985cf11a..265a0398b 100644
--- a/apps/crowclk/metadata.json
+++ b/apps/crowclk/metadata.json
@@ -1,7 +1,7 @@
{
"id": "crowclk",
"name": "Crow Clock",
- "version": "0.03",
+ "version": "0.04",
"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"}],
diff --git a/apps/daisy/ChangeLog b/apps/daisy/ChangeLog
index d5844c62b..829ff3d13 100644
--- a/apps/daisy/ChangeLog
+++ b/apps/daisy/ChangeLog
@@ -4,3 +4,4 @@
0.04: added heart rate which is switched on when cycled to it through up/down touch on rhs
0.05: changed text to uppercase, just looks better, removed colons on text
0.06: better contrast for light theme, use fg color instead of dithered for ring
+0.07: Use default Bangle formatter for booleans
diff --git a/apps/daisy/metadata.json b/apps/daisy/metadata.json
index 5073db603..802ba6834 100644
--- a/apps/daisy/metadata.json
+++ b/apps/daisy/metadata.json
@@ -1,6 +1,6 @@
{ "id": "daisy",
"name": "Daisy",
- "version":"0.06",
+ "version":"0.07",
"dependencies": {"mylocation":"app"},
"description": "A beautiful digital clock with large ring guage, idle timer and a cyclic information line that includes, day, date, steps, battery, sunrise and sunset times",
"icon": "app.png",
diff --git a/apps/daisy/settings.js b/apps/daisy/settings.js
index 044eee0d1..6397a81f4 100644
--- a/apps/daisy/settings.js
+++ b/apps/daisy/settings.js
@@ -41,7 +41,6 @@
},
'Idle Warning': {
value: !!s.idle_check,
- format: v => v ? /*LANG*/"Yes":/*LANG*/"No",
onchange: v => {
s.idle_check = v;
save();
diff --git a/apps/dane_tcr/ChangeLog b/apps/dane_tcr/ChangeLog
index 4f6fe2edc..69424b1f4 100644
--- a/apps/dane_tcr/ChangeLog
+++ b/apps/dane_tcr/ChangeLog
@@ -4,4 +4,5 @@
0.04: Move code to Arwes Module
0.05: Add icon
0.06: remove app image as it is unused
-0.07: Bump version number for change to apps.json causing 404 on upload
\ No newline at end of file
+0.07: Bump version number for change to apps.json causing 404 on upload
+0.08: Use default Bangle formatter for booleans
diff --git a/apps/dane_tcr/metadata.json b/apps/dane_tcr/metadata.json
index 817d0c59b..5527c846d 100644
--- a/apps/dane_tcr/metadata.json
+++ b/apps/dane_tcr/metadata.json
@@ -2,7 +2,7 @@
"id": "dane_tcr",
"name": "DANE Touch Launcher",
"shortName": "DANE Toucher",
- "version": "0.07",
+ "version": "0.08",
"description": "Touch enable left to right launcher in the style of the DANE Watchface",
"icon": "app.png",
"type": "launch",
diff --git a/apps/dane_tcr/settings.js b/apps/dane_tcr/settings.js
index 9d28d1b30..46988ec26 100644
--- a/apps/dane_tcr/settings.js
+++ b/apps/dane_tcr/settings.js
@@ -41,7 +41,6 @@
},
"Animation" : {
value : settings.animation,
- format : v => v?"On":"Off",
onchange : saveChange('animation')
},
"Frame rate" : {
@@ -51,7 +50,6 @@
},
"Debug" : {
value : settings.debug,
- format : v => v?"On":"Off",
onchange : saveChange('debug')
},
'< Back': back
diff --git a/apps/diceroll/ChangeLog b/apps/diceroll/ChangeLog
new file mode 100644
index 000000000..89dff4011
--- /dev/null
+++ b/apps/diceroll/ChangeLog
@@ -0,0 +1 @@
+0.01: App created
\ No newline at end of file
diff --git a/apps/diceroll/app-icon.js b/apps/diceroll/app-icon.js
new file mode 100644
index 000000000..4d6e7da16
--- /dev/null
+++ b/apps/diceroll/app-icon.js
@@ -0,0 +1 @@
+E.toArrayBuffer(atob("ICABAAAAAAAAAAAAAAAAAAHAAAAP8AAAfn4AA/APwA+DwfAPg8HwD+AH8Az4HzAMPnwwDAfgMAwBgDAMCYAwDA2YMAwhmDAMIZAwDCGDMA2BgzAMgYAwDAGAMA8BgPADwYPAAPGPgAB9ngAAH/gAAAfgAAABgAAAAAAAAAAAAAAAAAA="))
diff --git a/apps/diceroll/app.js b/apps/diceroll/app.js
new file mode 100644
index 000000000..d514ce92f
--- /dev/null
+++ b/apps/diceroll/app.js
@@ -0,0 +1,108 @@
+var init_message = true;
+var acc_data;
+var die_roll = 1;
+var selected_die = 0;
+var roll = 0;
+const dices = [4, 6, 10, 12, 20];
+
+g.setFontAlign(0,0);
+
+Bangle.on('touch', function(button, xy) {
+ // Change die if not rolling
+ if(roll < 1){
+ if(selected_die <= 3){
+ selected_die++;
+ }else{
+ selected_die = 0;
+ }
+ }
+ //Disable initial message
+ init_message = false;
+});
+
+function rect(){
+ x1 = g.getWidth()/2 - 35;
+ x2 = g.getWidth()/2 + 35;
+ y1 = g.getHeight()/2 - 35;
+ y2 = g.getHeight()/2 + 35;
+ g.drawRect(x1, y1, x2, y2);
+}
+
+function pentagon(){
+ x1 = g.getWidth()/2;
+ y1 = g.getHeight()/2 - 50;
+ x2 = g.getWidth()/2 - 50;
+ y2 = g.getHeight()/2 - 10;
+ x3 = g.getWidth()/2 - 30;
+ y3 = g.getHeight()/2 + 30;
+ x4 = g.getWidth()/2 + 30;
+ y4 = g.getHeight()/2 + 30;
+ x5 = g.getWidth()/2 + 50;
+ y5 = g.getHeight()/2 - 10;
+ g.drawPoly([x1, y1, x2, y2, x3, y3, x4, y4, x5, y5], true);
+}
+
+function triangle(){
+ x1 = g.getWidth()/2;
+ y1 = g.getHeight()/2 - 57;
+ x2 = g.getWidth()/2 - 50;
+ y2 = g.getHeight()/2 + 23;
+ x3 = g.getWidth()/2 + 50;
+ y3 = g.getHeight()/2 + 23;
+ g.drawPoly([x1, y1, x2, y2, x3, y3], true);
+}
+
+function drawDie(variant) {
+ if(variant == 1){
+ //Rect, 6
+ rect();
+ }else if(variant == 3){
+ //Pentagon, 12
+ pentagon();
+ }else{
+ //Triangle, 4, 10, 20
+ triangle();
+ }
+}
+
+function initMessage(){
+ g.setFont("6x8", 2);
+ g.drawString("Dice-n-Roll", g.getWidth()/2, 20);
+ g.drawString("Shake to roll", g.getWidth()/2, 60);
+ g.drawString("Tap to change", g.getWidth()/2, 80);
+ g.drawString("Tap to start", g.getWidth()/2, 150);
+}
+
+function rollDie(){
+ acc_data = Bangle.getAccel();
+ if(acc_data.diff > 0.3){
+ roll = 3;
+ }
+ //Mange the die "roll" by chaning the number a few times
+ if(roll > 0){
+ g.drawString("Rolling!", g.getWidth()/2, 150);
+ die_roll = Math.abs(E.hwRand()) % dices[selected_die] + 1;
+ roll--;
+ }
+ //Draw dice graphics
+ drawDie(selected_die);
+ //Draw dice number
+ g.setFontAlign(0,0);
+ g.setFont("Vector", 45);
+ g.drawString(die_roll, g.getWidth()/2, g.getHeight()/2);
+ //Draw selected die in right corner
+ g.setFont("6x8", 2);
+ g.drawString(dices[selected_die], g.getWidth()-15, 15);
+}
+
+function main() {
+ g.clear();
+ if(init_message){
+ initMessage();
+ }else{
+ rollDie();
+ }
+ Bangle.setLCDPower(1);
+}
+
+var interval = setInterval(main, 300);
\ No newline at end of file
diff --git a/apps/diceroll/app.png b/apps/diceroll/app.png
new file mode 100644
index 000000000..b695b7080
Binary files /dev/null and b/apps/diceroll/app.png differ
diff --git a/apps/diceroll/diceroll_screenshot.png b/apps/diceroll/diceroll_screenshot.png
new file mode 100644
index 000000000..71024edbb
Binary files /dev/null and b/apps/diceroll/diceroll_screenshot.png differ
diff --git a/apps/diceroll/metadata.json b/apps/diceroll/metadata.json
new file mode 100644
index 000000000..81a2f8bfd
--- /dev/null
+++ b/apps/diceroll/metadata.json
@@ -0,0 +1,14 @@
+{ "id": "diceroll",
+ "name": "Dice-n-Roll",
+ "shortName":"Dice-n-Roll",
+ "icon": "app.png",
+ "version":"0.01",
+ "description": "A dice app with a few different dice.",
+ "screenshots": [{"url":"diceroll_screenshot.png"}],
+ "tags": "game",
+ "supports": ["BANGLEJS2"],
+ "storage": [
+ {"name":"diceroll.app.js","url":"app.js"},
+ {"name":"diceroll.img","url":"app-icon.js","evaluate":true}
+ ]
+ }
\ No newline at end of file
diff --git a/apps/dinoClock/README.md b/apps/dinoClock/README.md
new file mode 100644
index 000000000..7568731d9
--- /dev/null
+++ b/apps/dinoClock/README.md
@@ -0,0 +1,17 @@
+# dinoClock
+
+Watchface with T-Rex Dinosaur from Chrome.
+It displays current temperature and weather.
+
+**Warning**: Element position and styles can change in the future.
+
+Based on the [Weather Clock](https://github.com/espruino/BangleApps/tree/master/apps/weatherClock).
+
+# Requirements
+
+**This clock requires Gadgetbridge and the weather app in order to get weather data!**
+
+See the [Bangle.js Gadgetbridge documentation](https://www.espruino.com/Gadgetbridge) for instructions on setting up Gadgetbridge and weather.
+
+
+
diff --git a/apps/dinoClock/app.js b/apps/dinoClock/app.js
new file mode 100644
index 000000000..82192d234
--- /dev/null
+++ b/apps/dinoClock/app.js
@@ -0,0 +1,219 @@
+const storage = require('Storage');
+const locale = require("locale");
+
+
+
+
+// add modifiied 4x5 numeric font
+(function(graphics) {
+ graphics.prototype.setFont4x5NumPretty = function() {
+ this.setFontCustom(atob("IQAQDJgH4/An4QXr0Fa/BwnwdrcH63BCHwfr8Ha/"),45,atob("AwIEBAQEBAQEBAQEBA=="),5);
+ };
+})(Graphics);
+
+// add font for days of the week
+(function(graphics) {
+ graphics.prototype.setFontDoW = function() {
+ this.setFontCustom(atob("///////ADgB//////+AHAD//////gAAAH//////4D8B+A///////4AcAOAH//////4AcAOAAAAAB//////wA4AcAP//////wAAAAAAAA//////4AcAP//////wA4Af//////gAAAH//////5z85+c/OfnOAA4AcAOAH//////4AcAOAAAAAB//////wcAOAHB//////wAAAAAAAA///////ODnBzg5wc4AAAAD//////84OcH//8/+fAAAAAAAAAAAAA/z/5/8/OfnPz/5/8/wAAAD//////84OcH//////AAAAAAAAAAAAA/z/5/8/OfnPz/5/8/wAAAD//////gBwA///////AAAAAAAAAAAAA"),48,24,13);
+ };
+})(Graphics);
+
+
+const SUN = 1;
+const PART_SUN = 2;
+const CLOUD = 3;
+const SNOW = 4;
+const RAIN = 5;
+const STORM = 6;
+const ERR = 7;
+
+/**
+Choose weather icon based on weather const
+Weather icons from https://icons8.com/icon/set/weather/ios-glyphs
+Error icon from https://icons8.com/icon/set/error-cloud/ios-glyphs
+**/
+function weatherIcon(weather) {
+ switch (weather) {
+ case SUN:
+ return atob("Hh4BAAAAAAAMAAAAMAAAAMAAAAMAABgMBgBwADgA4AHAAY/GAAB/gAAD/wAAH/4AAP/8AAP/8AfP/8+fP/8+AP/8AAP/8AAH/4AAD/wAAB/gAAY/GAA4AHABwADgBgMBgAAMAAAAMAAAAMAAAAMAAAAAAAA=");
+ case PART_SUN:
+ return atob("Hh4BAAAAAAAAAAAMAAAAMAAAEMIAAOAcAAGAYAAAeAAAA/AAAB/gAA5/gAA5/g+AB+D/gA4H/wAR//wGD//4OD//4EH//4AH//4Af//+Af//+A////A////A////A///+Af//+AH//4AAAAAAAAAAAAAAAA=");
+ case CLOUD:
+ return atob("Hh4BAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAf+AAA//AAB//gAf//gB///wB///wD///wD///wP///8f///+f///+////////////////////f///+f///+P///8D///wAAAAAAAAAAAAAAAAAAAAAAAAAA=");
+ case SNOW:
+ return atob("Hh4BAAAAAAAAAAAAAAAAAHwAAAf8AAA/+AAH/+AAf//AAf8/AA/8/AB/gHgH/wP4H/wP4P/gH8P/8/8P/8/8P///4H///4B///gAAAAAAMAAAAMAAAB/gGAA/AfgA/AfgB/gfgAMAfgAMAGAAAAAAAAAAAA=");
+ case RAIN:
+ return atob("Hh4BAAAAAAAAAAAAAAAAAHwAAAf8AAA/+AAH/+AAf//AAf//AA///AB///gH///4H///4P///8P///8P///8P///4H///4B///gAAAAAAAAAABgBgABgBgABhhhgABgBgABgBgAAAAAAAAAAAAAAAAAAAAA=");
+ case STORM:
+ return atob("Hh4BAAAAAAAAAAAAAAAAAHwAAAf8AAA/+AAH/+AAf//AAf//AA///AB///gH///4H/x/4P/g/8P/k/8P/E/8P/M/4H+MP4B+cHgAAfgAAA/gABg/AABgHAABgGBgAAGBgAAEBgAAEAAAAAAAAAAAAAAAAAA=");
+ case ERR:
+ default:
+ return atob("Hh4BAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAf+AAA//AAB//gAf//gB///wB/z/wD/z/wD/z/wP/z/8f/z/+f/z/+//z//////////////z//f/z/+f///+P///8D///wAAAAAAAAAAAAAAAAAAAAAAAAAA=");
+ }
+}
+
+
+/**
+Choose weather icon to display based on condition.
+Based on function from the Bangle weather app so it should handle all of the conditions
+sent from gadget bridge.
+*/
+function chooseIcon(condition) {
+ condition = condition.toLowerCase();
+ if (condition.includes("thunderstorm")) return weatherIcon(STORM);
+ if (condition.includes("freezing")||condition.includes("snow")||
+ condition.includes("sleet")) {
+ return weatherIcon(SNOW);
+ }
+ if (condition.includes("drizzle")||
+ condition.includes("shower")) {
+ return weatherIcon(RAIN);
+ }
+ if (condition.includes("rain")) return weatherIcon(RAIN);
+ if (condition.includes("clear")) return weatherIcon(SUN);
+ if (condition.includes("few clouds")) return weatherIcon(PART_SUN);
+ if (condition.includes("scattered clouds")) return weatherIcon(CLOUD);
+ if (condition.includes("clouds")) return weatherIcon(CLOUD);
+ if (condition.includes("mist") ||
+ condition.includes("smoke") ||
+ condition.includes("haze") ||
+ condition.includes("sand") ||
+ condition.includes("dust") ||
+ condition.includes("fog") ||
+ condition.includes("ash") ||
+ condition.includes("squalls") ||
+ condition.includes("tornado")) {
+ return weatherIcon(CLOUD);
+ }
+ return weatherIcon(CLOUD);
+}
+
+/*
+* Choose weather icon to display based on weather conditition code
+* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
+*/
+function chooseIconByCode(code) {
+ const codeGroup = Math.round(code / 100);
+ switch (codeGroup) {
+ case 2: return weatherIcon(STORM);
+ case 3: return weatherIcon(RAIN);
+ case 5: return weatherIcon(RAIN);
+ case 6: return weatherIcon(SNOW);
+ case 7: return weatherIcon(CLOUD);
+ case 8:
+ switch (code) {
+ case 800: return weatherIcon(SUN);
+ case 801: return weatherIcon(PART_SUN);
+ default: return weatherIcon(CLOUD);
+ }
+ default: return weatherIcon(CLOUD);
+ }
+}
+
+/**
+Get weather stored in json file by weather app.
+*/
+function getWeather() {
+ let jsonWeather = storage.readJSON('weather.json');
+ return jsonWeather;
+}
+
+// timeout used to update every minute
+var drawTimeout;
+
+// schedule a draw for the next minute
+function queueDraw() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ },60000-(Date.now()%60000));
+}
+
+// only draw the first time
+function drawBg() {
+ var bgImg = require("heatshrink").decompress(atob("2E7wINKn///+AEaIVUgIUB//wCs/5CtRXrCvMD8AVTg4LFCv4VZ/iSLCrwWMCrMOAQMPCp7cBCojjFCo/xFgIVQgeHCopABCpcH44Vuh/AQQX/wAV7+F/Cq/nCsw/CCqyvRCvgODCqfAgEDCp4QCSIIVQgIOBDQgGDABX/NgIECCp8HCrM/CgP4CqKaCCqSfCCqq1BCqBuB54VqgYVG/gCECp0BwgCDCp8HgYCDCo/wCo0MgHAjACBj7rDABS1Bv4lBv4rPAAsPCo3+gbbPJAIVFiAXMFZ2AUQsAuAQHiOAgJeEA"));
+ g.reset();
+ g.drawImage(bgImg,0,101);
+}
+
+function square(x,y,w,e) {
+ g.setColor("#000").fillRect(x,y,x+w,y+w);
+ g.setColor("#fff").fillRect(x+e,y+e,x+w-e,y+w-e);
+}
+
+function draw() {
+ var d = new Date();
+ var h = d.getHours(), m = d.getMinutes();
+ h = ("0"+h).substr(-2);
+ m = ("0"+m).substr(-2);
+
+ var day = d.getDate(), mon = d.getMonth(), dow = d.getDay();
+ day = ("0"+day).substr(-2);
+ mon = ("0"+(mon+1)).substr(-2);
+ dow = ((dow+6)%7).toString();
+ date = day+"."+mon;
+
+ var weatherJson = getWeather();
+ var wIcon;
+ var temp;
+ if(weatherJson && weatherJson.weather){
+ var currentWeather = weatherJson.weather;
+ temp = locale.temp(currentWeather.temp-273.15).match(/^(\D*\d*)(.*)$/);
+ const code = currentWeather.code||-1;
+ if (code > 0) {
+ wIcon = chooseIconByCode(code);
+ } else {
+ wIcon = chooseIcon(currentWeather.txt);
+ }
+ }else{
+ temp = "";
+ wIcon = weatherIcon(ERR);
+ }
+ g.reset();
+ g.clearRect(22,35,153,75);
+ g.setFont("4x5NumPretty",8);
+ g.fillRect(84,42,92,49);
+ g.fillRect(84,60,92,67);
+ g.drawString(h,22,35);
+ g.drawString(m,98,35);
+
+ g.clearRect(22,95,22+4*2*4+2*4,95+2*5);
+ g.setFont("4x5NumPretty",2);
+ g.drawString(date,22,95);
+
+ g.clearRect(22,79,22+24,79+13);
+ g.setFont("DoW");
+ g.drawString(dow,22,79);
+
+ g.drawImage(wIcon,126,81);
+
+ g.clearRect(108,114,176,114+4*5);
+ if (temp != "") {
+ var tempWidth;
+ const mid=126+15;
+ if (temp[1][0]=="-") {
+ // do not account for - when aligning
+ const minusWidth=3*4;
+ tempWidth = minusWidth+(temp[1].length-1)*4*4;
+ x = mid-Math.round((tempWidth-minusWidth)/2)-minusWidth;
+ } else {
+ tempWidth = temp[1].length*4*4;
+ x = mid-Math.round(tempWidth/2);
+ }
+ g.setFont("4x5NumPretty",4);
+ g.drawString(temp[1],x,114);
+ square(x+tempWidth,114,6,2);
+ }
+
+ // queue draw in one minute
+ queueDraw();
+}
+
+g.clear();
+drawBg();
+Bangle.setUI("clock"); // Show launcher when middle button pressed
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+draw();
+
diff --git a/apps/dinoClock/app.png b/apps/dinoClock/app.png
new file mode 100644
index 000000000..c05276ee3
Binary files /dev/null and b/apps/dinoClock/app.png differ
diff --git a/apps/dinoClock/icon.js b/apps/dinoClock/icon.js
new file mode 100644
index 000000000..2410dad14
--- /dev/null
+++ b/apps/dinoClock/icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwgJC/AAVh/E/hgFC/O/AoMB8EZwc8AoUYgYFBgFgjAXDAowXBAo8B/ARBn4FGAAsBmAFE2ADBhwFEj4VEn+AgPvAontgfwv+ABIMCMwIVCgf4FIWAAoN3sAFCwERoEB0MHwF3gEF0MPwFEAoW/4ALD/4tCg/hAoYhB/5ZDwF+Aok0gEIkEf/4AB8eMBoM2bkw="))
diff --git a/apps/dinoClock/metadata.json b/apps/dinoClock/metadata.json
new file mode 100644
index 000000000..a61ce122b
--- /dev/null
+++ b/apps/dinoClock/metadata.json
@@ -0,0 +1,17 @@
+{
+ "id": "dinoClock",
+ "name": "Dino Clock",
+ "description": "Clock with dino from Chrome",
+ "screenshots": [{"url":"screens/screen1.png"}],
+ "icon": "app.png",
+ "version": "0.01",
+ "type": "clock",
+ "tags": "clock, weather, dino, trex, chrome",
+ "supports": ["BANGLEJS2"],
+ "allow_emulator": true,
+ "readme": "README.md",
+ "storage": [
+ {"name":"dinoClock.app.js","url":"app.js"},
+ {"name":"dinoClock.img","url":"icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/dinoClock/screens/screen1.png b/apps/dinoClock/screens/screen1.png
new file mode 100644
index 000000000..ca4386449
Binary files /dev/null and b/apps/dinoClock/screens/screen1.png differ
diff --git a/apps/distortclk/ChangeLog b/apps/distortclk/ChangeLog
new file mode 100644
index 000000000..4c7291526
--- /dev/null
+++ b/apps/distortclk/ChangeLog
@@ -0,0 +1,2 @@
+0.01: New face!
+0.02: Improved clock
diff --git a/apps/distortclk/README.md b/apps/distortclk/README.md
new file mode 100644
index 000000000..8c7c433c1
--- /dev/null
+++ b/apps/distortclk/README.md
@@ -0,0 +1,17 @@
+# Distort Watchface
+Was playing around with custom fonts and made something with it
+Made for Bangle.js 2
+
+
+
+## Features
+
+Has a dark mode
+
+## Requests
+
+If you have any issues or would like to suggest a feature, click here to send a message -> [here](https://github.com/elykittytee/BangleApps/issues/new?title=Poketch%20Clock%20Bug).
+
+## Creator
+
+Eleanor Tayam
diff --git a/apps/distortclk/app-icon.js b/apps/distortclk/app-icon.js
new file mode 100644
index 000000000..c375de96e
--- /dev/null
+++ b/apps/distortclk/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("j0ewkBiIAxHIQMJiBJEIxAaCAIQfHDgIUFDwwNCHYgVFiAVBHYgIDEghKCCIQGCFYoaDAYgORGIJ2DBwYIBHgQOPgAOIPIYOGAgQOFFgh7DHZQeDBwhoFQgh3JEAgOFFoqkHYRzgOfx4bCJ4gNGSIaJEABA7EAGA"))
diff --git a/apps/distortclk/app.js b/apps/distortclk/app.js
new file mode 100644
index 000000000..a9fdd1ef2
--- /dev/null
+++ b/apps/distortclk/app.js
@@ -0,0 +1,65 @@
+Graphics.prototype.setFontSixCaps = function(scale) {
+ // Actual height 60 (59 - 0)
+ this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMzMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMzMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0VniarM3u4AAAAAAAAAAAAAAAAAAAAAAAAAAABEZoiqzN3u////////8AAAAAAAAAAAAAAAAAAAJFZ4iqzN3u////////////////8AAAAAAAAAAARGZ4mrzd7v////////////////////////8AAAAAAAqszd7v////////////////////////////7u3MoAAAAAAA//////////////////////////7t3MqohmRCAAAAAAAAAA//////////////////7t3MqohmRAAAAAAAAAAAAAAAAAAA/////////+7dzKqYdlQwAAAAAAAAAAAAAAAAAAAAAAAAAA/+7dzKqIZkQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZkQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWazMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMqVAAAAAAAAAa7//////////////////////////////////+oQAAAAAAC/////////////////////////////////////+wAAAAAAf//////////////////////////////////////3AAAAAA3///7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u///9AAAAAA///GREREREREREREREREREREREREREREREREbP//AAAAAA//8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///AAAAAA///GREREREREREREREREREREREREREREREREbP//AAAAAA3///7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u///9AAAAAAf//////////////////////////////////////3AAAAAAC/////////////////////////////////////+wAAAAAAAa7//////////////////////////////////+oQAAAAAAAAWazMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMqVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACqoAAAAAAA//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAA//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAA//3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3/8AAAAAAA//////////////////////////////////////8AAAAAAA//////////////////////////////////////8AAAAAAA//////////////////////////////////////8AAAAAAA3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADu4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAes3d3d3d0AAAAAAAAAAAAAAAAAAAAAAAADVorAAAAAAAA8////////8AAAAAAAAAAAAAAAAAAAAlaKze///wAAAAAAHf////////8AAAAAAAAAAAAAAAFGis3v///////wAAAAAAn/////////8AAAAAAAAAAARom83v///////////wAAAAAA7//+3d3d3d0AAAAAAEV5rN7//////////////v/wAAAAAA//xTAAAAAAAAA1eKze//////////////7cqXVP/wAAAAAA//UAAAAAFGis3v/////////////+3Kl2QAAAAP/wAAAAAA//6XZoq83v/////////////+26hkEAAAAAAAAP/wAAAAAA3//////////////////tyoZSAAAAAAAAAAAAAP/wAAAAAAb//////////////tuoZAAAAAAAAAAAAAAAAAAP/wAAAAAACv/////////tuXUwAAAAAAAAAAAAAAAAAAAAAP/wAAAAAAAI3////9yoZAAAAAAAAAAAAAAAAAAAAAAAAAAP/wAAAAAAAAJ4qodRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKqgAAAAAAAAWazMzMzMzMzAAAAAAAAAAAAAzMzMzMzMzMqVAAAAAAAAAa7//////////wAAAAAAAAAAAA///////////+oQAAAAAADP///////////wAAAAAAAAAAAA/////////////AAAAAAAf////////////wAAAAiIgAAAAA/////////////3AAAAAA3///7u7u7u7u7gAAAA//8AAAAA7u7u7u7u7u///9AAAAAA//11RERERERERAAAAA//8AAAAAREREREREREV9//AAAAAA//QAAAAAAAAAAAAAAB//8QAAAAAAAAAAAAAAAE//AAAAAA//11RERERERERERERa//+lREREREREREREREV9//AAAAAA3///7u7u7u7u7u7u7/////7u7u7u7u7u7u7u///9AAAAAAf//////////////////////////////////////3AAAAAAC/////////////////+q//////////////////+wAAAAAAAa7///////////////0i3////////////////+oQAAAAAAAAWazMzMzMzMzMzMyoIAKKzMzMzMzMzMzMzMqVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkRGZniIqqoAAAAAAAAAAAAAAAAAAAAAAkRGZniIqqrMzd3u7v//////8AAAAAAAAAAAAAAAqqrMzd3u7v////////////////////8AAAAAAAAAAAAAAA//////////////////////////////8AAAAAAAAAAAAAAA//////////////////////////////8AAAAAAAAAAAAAAA///////////////+7t3czKqpiHZm//8AAAAAAAAAAAAAAA//7u3dzMuqqIhmZUQwAAAAAAAAAA//8AAAAAAAAAAAAAAAZmREAAAAAAAAAARERERERERERERE//9EREREREQAAAAAAAAAAAAAAAAAAAAA7u7u7u7u7u7u7u///u7u7u7u4AAAAAAAAAAAAAAAAAAAAA////////////////////////8AAAAAAAAAAAAAAAAAAAAA////////////////////////8AAAAAAAAAAAAAAAAAAAAA////////////////////////8AAAAAAAAAAAAAAAAAAAAAzMzMzMzMzMzMzMzMzMzMzMzMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARERERERERERERERERERAAABEREREREREREREAAAAAAAAAA7u7u7u7u7u7u7u7u7u7gAADu7u7u7u7u7u7u2TAAAAAAAA///////////////////wAAD//////////////+YAAAAAAA///////////////////wAAD///////////////4wAAAAAA///////////////////wAAD///////////////+gAAAAAA//zMzMzMzMzMzMzM3//AAADMzMzMzMzMzMzM7//wAAAAAA//AAAAAAAAAAAAAG//cAAAAAAAAAAAAAAAAAKf/wAAAAAA//AAAAAAAAAAAAAN//UAAAAAAAAAAAAAAAAAB//wAAAAAA//AAAAAAAAAAAAAP//6qqqqqqqqqqqqqqqqqv//wAAAAAA//AAAAAAAAAAAAAP///////////////////////AAAAAAA//AAAAAAAAAAAAAN//////////////////////9AAAAAAA//AAAAAAAAAAAAAF7/////////////////////gAAAAAAA//AAAAAAAAAAAAAAWu//////////////////7GAAAAAAAAZmAAAAAAAAAAAAAAADZmZmZmZmZmZmZmZmZmQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADREREREREREREREREREREREREREREREREMAAAAAAAAAADne7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7ZMAAAAAAABd////////////////////////////////////1QAAAAAALv/////////////////////////////////////iAAAAAAr//////////////////////////////////////6AAAAAA///szMzMzMzMzMzMzP//zMzMzMzMzMzMzMzM3///AAAAAA//ogAAAAAAAAAAAAC//5AAAAAAAAAAAAAAAACf//AAAAAA//cAAAAAAAAAAAAAD//zAAAAAAAAAAAAAAAABf//AAAAAA//+6qqqqqqqqAAAAD//8qqqqqqqqqqqqqqqqrv//AAAAAAz///////////AAAAD//////////////////////8AAAAAAT///////////AAAADv/////////////////////0AAAAAACP//////////AAAAB/////////////////////+AAAAAAAAGzv////////AAAAAGzv/////////////////sYAAAAAAAAABGZmZmZmZmAAAAAABGZmZmZmZmZmZmZmZmZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQAAAAAAA//AAAAAAAAAAAAAAAAAAAAAAAAACRGZ4iqrM3e4AAAAAAA//AAAAAAAAAAAAAAACRGZ4iqrM3e7v////////8AAAAAAA//AAAAACRGZ4iqrM3e7v//////////////////8AAAAAAA//iqrM3e7v////////////////////////////8AAAAAAA//////////////////////////////////7u3cwAAAAAAA///////////////////////+7t3MyqmIZmRCAAAAAAAAAA/////////////u3dzLqoiGZUQgAAAAAAAAAAAAAAAAAAAA//7t3cyqqIhmVEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZlRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWazMzMzMqphjAAAAAAAAAARniqrMzMzMzMqVAAAAAAAAAa7//////////+yWEAAAAVi97////////////+oQAAAAAAC///////////////2VAGrf////////////////+wAAAAAAf////////////////+vP///////////////////3AAAAAA3///7u7u7u7/////////////////7u7u7u7u///9AAAAAA///GRERERERmis3////////typhmREREREREbP//AAAAAA//8wAAAAAAAAAAJ8/////8hgAAAAAAAAAAAAA///AAAAAA///GRERERERWis3////////typhmREREREREbP//AAAAAA3///7u7u7u7/////////////////7u7u7u7u///9AAAAAAf////////////////+vP///////////////////3AAAAAAC///////////////2VAGrf////////////////+wAAAAAAAa7//////////+yWIAAAAVi97////////////+oQAAAAAAAAWazMzMzMqphjAAAAAAAAAARniqrMzMzMzMqVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1ZmZmZmZmZmZmZmZmQgAAAAAAZmZmZmZmYwAAAAAAAAAFvv////////////////7rUAAAAA/////////rUAAAAAAAB+////////////////////9gAAAA//////////9wAAAAAAP//////////////////////gAAAA///////////zAAAAAAv//////////////////////wAAAA///////////7AAAAAA///7qqqqqqqqqqqqqqqq3//wAAAAqqqqqqqqrP//AAAAAA//9wAAAAAAAAAAAAAAAAT//wAAAAAAAAAAAAAH//AAAAAA//9wAAAAAAAAAAAAAAAAn//AAAAAAAAAAAAAAZ//AAAAAA///8zMzMzMzMzMzMzMzM///MzMzMzMzMzMzMzf//AAAAAAv//////////////////////////////////////7AAAAAAP//////////////////////////////////////zAAAAAABu////////////////////////////////////5gAAAAAAAEre7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7aQAAAAAAAAAAUREREREREREREREREREREREREREREREREQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMzMwAAAAAAAAAAAzMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAA///wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAA///wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAA///wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAA///wAAAAAAAAAAAAAAAAAAAAAAAAAAAMzMwAAAAAAAAAAAzMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("CAwODQ4PDw8QDA8QCA=="), 69+(scale<<8)+(4<<16));
+ return this;
+};
+
+const offset = 25;
+const width = g.getWidth();
+const height = g.getHeight();
+
+var drawTimeout;
+var fgTime = 0xf800;
+var bgTime = 0x3333ff;
+var dayDate = 0x000;
+
+function queueDraw() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ }, 60000 - (Date.now() % 60000));
+}
+
+function time() {
+ require("Font4x5").add(Graphics);
+ var d = new Date();
+ var day = d.getDate();
+ var time = require("locale").time(d,1);
+ var date = require("locale").date(d);
+ var mo = require("date_utils").month(d.getMonth()+1,0);
+
+ g.setFontAlign(0,0);
+ g.setFontSixCaps(2).setColor(fgTime).drawString(time, width/2, height/2+10);
+
+ g.setFont("4x5",2);
+ g.setFontAlign(0,0);
+ g.setColor(dayDate).drawString(mo,width-55, height-16);
+ g.drawString(day,width-10, height-16);
+}
+
+function draw() {
+ g.setColor(bgTime).fillRect(0,40,width,height-offset);
+ time();
+ queueDraw();
+}
+
+//program start
+g.clear(); // Clear the screen once, at startup
+
+if (g.theme.dark==true){
+ dayDate = 0xffff;
+}
+else {
+ dayDate=0x000;
+}
+
+draw(); // draw immediately at first
+
+
+
+// Show launcher when middle button pressed
+Bangle.setUI("clock");
+// Load widgets
+Bangle.loadWidgets();
+Bangle.drawWidgets();
diff --git a/apps/distortclk/app.png b/apps/distortclk/app.png
new file mode 100644
index 000000000..b82a0913e
Binary files /dev/null and b/apps/distortclk/app.png differ
diff --git a/apps/distortclk/metadata.json b/apps/distortclk/metadata.json
new file mode 100644
index 000000000..125dac590
--- /dev/null
+++ b/apps/distortclk/metadata.json
@@ -0,0 +1,17 @@
+{
+ "id": "distortclk",
+ "name": "Distort Clock",
+ "shortName":"Distort Clock",
+ "version": "0.02",
+ "description": "A clockface",
+ "icon": "app.png",
+ "type": "clock",
+ "tags": "clock",
+ "supports": ["BANGLEJS2"],
+ "allow_emulator": true,
+ "readme":"README.md",
+ "storage": [
+ {"name":"distortclk.app.js","url":"app.js"},
+ {"name":"distortclk.img","url":"app-icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/distortclk/screenshot.png b/apps/distortclk/screenshot.png
new file mode 100644
index 000000000..3207b4e1e
Binary files /dev/null and b/apps/distortclk/screenshot.png differ
diff --git a/apps/drinkcounter/ChangeLog b/apps/drinkcounter/ChangeLog
new file mode 100644
index 000000000..d8d174c4c
--- /dev/null
+++ b/apps/drinkcounter/ChangeLog
@@ -0,0 +1,4 @@
+0.10: Initial release - still work in progress
+0.15: Added settings and calculations
+0.20: Added status saving
+0.25: Adopted for Bangle.js 1 - kind of
\ No newline at end of file
diff --git a/apps/drinkcounter/README.md b/apps/drinkcounter/README.md
new file mode 100644
index 000000000..5638ee066
--- /dev/null
+++ b/apps/drinkcounter/README.md
@@ -0,0 +1,15 @@
+# Drink Counter
+
+Counts drinks you had for science. Calculates BAC.
+
+## Usage
+
+Swipe left/right to select drink. Swipe up/down to add/remove drinks.
+
+## Important notes
+
+No warranty whatsoever. Use at your own risk. Calculations might be wrong. Do not drink and drive - even if BAC is low.
+
+## Creator
+
+Hank - contact at http://forum.espruino.com
diff --git a/apps/drinkcounter/app.js b/apps/drinkcounter/app.js
new file mode 100644
index 000000000..323d9fb41
--- /dev/null
+++ b/apps/drinkcounter/app.js
@@ -0,0 +1,291 @@
+g.reset().clear();
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+require("Font8x16").add(Graphics);
+
+const BANGLEJS2 = process.env.HWVERSION == 2;
+const SETTINGSFILE = "drinkcounter.json";
+setting = require("Storage").readJSON("setting.json",1);
+E.setTimeZone(setting.timezone); // timezone = 1 for MEZ, = 2 for MESZ
+var _12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false;
+var ampm = "AM";
+let drag;
+
+var icoBeer = require("heatshrink").decompress(atob("lEoxH+AG2BAAoecEpAoWC4fXAAIGGAAowTDxAmJE4YGGE5QeJE5QHHE7owJE0pQKE7pQJE86fnE5QJSE5YUHBAIJQYxIpFAAvGBBAJIExYoGDgIACBBApFExonCDYoAOFSAnbFJYnE6vVDYYFHAwakQE4YaFAoQGJEIYoME7QoEE7ogFE/4neTBgntY84n/E+7HUE64mDE8IAFEw4nDTBifIE9gmId7gALE5IGCAooGDE6gASE8yaME7gmOFIgAREqIAhA=="));
+var icoCocktail = require("heatshrink").decompress(atob("lEoxH+AH4AJtgABEkgmiEiXGAAIllAAiXeEAPXAQQDCFBYmTEgYqDFBZNWAIZRME6IfBEAYuEE5J2UwIAaJ5QncFBB3DB4YGCACQnKTQgoXE5bIEE6qfKPAZRFA4MUABgmNPAonBCgQnPExgpFPIgoNEyBSF4wGBFBgmSABCjJTZwoXEzwoHE0AoFE0QnCFAQmhKAonjFAInCE0Qn/E/4n/E/4n/wInDFEAhBEwQoDFLYdCEwooEFTAjHAAwoYIYgAMPDglT"));
+var icoShot = require("heatshrink").decompress(atob("lEoxH+AH4A/AH4A/AH4AqwIAgE+HXADRPME8ZQM5AnSZBQkGAAYngEYonfJA5QQE8zGJFAYfKFBwmKE4iYIE7rpIeYgAJE5woEEpQKHTxhQIIpJaHJxgn/E8zGQZBAnQYxxQRFQYnlFgon5FCYmDE6LjHZRQmPE5AAOE/4njFCTGQKCwmRKAgATE54oWEyAqTDZY"));
+var icoReset = require("heatshrink").decompress(atob("j0egILI8ACBh4DC/4DBh4DCv8f4ED8EPwEPEQMAvEAnkB4EA+AKBCAM8DYOA8EB//HwED/wXBg/wnAOC+EAjkDDoMgg+AJoRFCEIIAB/kHgEB/l8FwP/DYIDBC4MD/ASBgYeCAAw"));
+var drawTimeout;
+var activeDrink = 0;
+var drinks = [0,0,0];
+const maxDrinks = 2; // 3 drinks
+var firstDrinkTime = null;
+var firstDrinkTimeTime = null;
+
+var confBeerSize;
+var confSex;
+var confWeight;
+var confWeightUnit;
+
+
+// Load Status ===============
+var drinkStatus = require("Storage").open("drinkcounter.status.json", "r");
+var test = drinkStatus.read(drinkStatus.getLength());
+if(test!== undefined) {
+ drinkStatus = JSON.parse(test);
+ //console.log("read status: " + test);
+ for (let i = 0; i <= maxDrinks; i++) {
+ drinks[i] = drinkStatus.drinks[i];
+ }
+ firstDrinkTime = Date.parse(drinkStatus.firstDrinkTime);
+ //console.log("read firstDrinkTime: " + firstDrinkTime);
+ if (firstDrinkTime) firstDrinkTimeTime = require("locale").time(new Date(firstDrinkTime), 1);
+ //console.log("read firstDrinkTimeTime: " + firstDrinkTimeTime);
+} else {
+ drinkStatus = {
+ drinks: [0,0,0]
+ };
+ //console.log("no status file - applying default");
+}
+// Load Status ===============
+
+
+var drinksAlcohol = [12,16,5.6]; // in gramm
+// Beer: 0.3L 12g - 0.5L 20g
+// Radler: 0.3L 6g - 0.5L 10g
+// Wine: 0.2L 16g
+// Jäger Shot: 0.02L 5.6g
+
+// sex: Women 60 - Men 70 (Percent)
+// Formula: Alcohol in g /(Body weight in kg x sex) – (0,15 x Hours) = bac per mille
+// Example: 5 Beer (0.3L=12g), 80KG, Male (70%), 5 hours
+// (5 * 12) / (80 / 100 * 70) - (0.15 * 5)
+
+function drawBac(){
+ if (firstDrinkTime) {
+ var sum_drinks = (drinks[0] * drinksAlcohol[0]) + (drinks[1] * drinksAlcohol[1]) + (drinks[2] * drinksAlcohol[2]);
+
+ if (confSex == "male") {
+ sex = 70;
+ } else {
+ sex = 60;
+ }
+ var weight = confWeight;
+
+ if (confWeightUnit == "US Pounds") {
+ weight = weight * 0.45359237;
+ }
+ var currentTime = new Date();
+ var time_diff = Math.floor(((currentTime - firstDrinkTime) % 86400000) / 3600000); // in hours!
+ //console.log("currentTime: " + currentTime)
+ //console.log("firstDrinkTime: " + firstDrinkTime)
+
+ //console.log("timediff: " + time_diff);
+ ebac = Math.round( ((sum_drinks) / (weight / 100 * sex) - (0.15 * time_diff) ) * 100) / 100;
+
+ //console.log("BAC: " + ebac + " weight: " + confWeight + " weightInKilo: " + weight + " Unit: " + confWeightUnit);
+ //console.log("sum_drinks: " + sum_drinks);
+ g.clearRect(0,34 + 20 + 8,176,34 + 20 + 20 + 8); //Clear
+ g.setFontAlign(0,0).setFont("8x16").setColor(g.theme.fg).drawString("BAC: " + ebac, 90, 74);
+ }
+}
+
+
+// Load settings
+function loadMySettings() {
+ // Helper function default setting
+ function def (value, def) {return value !== undefined ? value : def;}
+
+ var settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
+ confBeerSize = def(settings.beerSize, "0.3L");
+ confSex = def(settings.sex, "male");
+ confWeight = def(settings.weight, 80);
+ confWeightUnit = def(settings.weightUnit, "Kilo");
+ //console.log("Read config - weight: " + confWeight);
+}
+
+
+function updateTime(){
+ var d = require("locale").time(new Date(), 1);
+
+ //console.log(d);
+ var time = d.split(":");
+ var hours = time[0];
+ var minutes = time[1];
+ if (_12hour){
+ //do 12 hour stuff
+ if (hours > 12) {
+ ampm = "PM";
+ hours = hours - 12;
+ if (hours < 10) hours = doublenum(hours);
+ } else {
+ ampm = "AM";
+ }
+ } else {
+ ampm = "";
+ }
+ g.setBgColor(g.theme.bg).clearRect(0,24,176,44); //Clear
+ g.setFontAlign(0,0); // center font
+ g.setBgColor(g.theme.bg).setColor(g.theme.fg);
+ g.setFont("8x16").drawString("Time: " + hours + ":" + minutes + " " + ampm,90,34);
+ queueDrawTime();
+}
+
+function queueDrawTime() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ updateTime();
+ }, 20000 - (Date.now() % 20000));
+}
+
+
+function updateDrinks(){
+ g.setBgColor(g.theme.bg).clearRect(0,145,176,176); //Clear
+ for (let i = 0; i <= maxDrinks; i++) {
+ if (i == activeDrink) {
+ g.setColor(g.theme.fg).fillRect((40 * (i + 1)) - 40 ,145,(40 * (i + 1)),176);
+ g.setColor(g.theme.bg);
+ } else {
+ g.setColor(g.theme.fg);
+ }
+ g.setFont("Vector",20).drawString(drinks[i], (40 * (i + 1)) - 20, 160);
+ g.setColor(g.theme.fg);
+ drinkStatus.drinks[i] = drinks[i];
+ }
+
+ g.setBgColor(g.theme.bg).setColor(g.theme.fg);
+ if (BANGLEJS2) {
+ g.drawImage(icoReset,145,145);
+ }
+
+ drinkStatus.firstDrinkTime = firstDrinkTime;
+ settings_file = require("Storage").open("drinkcounter.status.json", "w");
+ settings_file.write(JSON.stringify(drinkStatus));
+
+ drawBac();
+}
+
+function updateFirstDrinkTime(){
+ if (firstDrinkTime){
+ g.setFont("8x16");
+ g.setFontAlign(0,0).drawString("1st drink @ " + firstDrinkTimeTime, 90, 34 + 20 );
+ }
+}
+
+function addDrink(){
+ if (!firstDrinkTime){
+ firstDrinkTime = new Date();
+ firstDrinkTimeTime = require("locale").time(new Date(), 1);
+ //console.log("init drinking! " + firstDrinkTime);
+ }
+ drinks[activeDrink] = drinks[activeDrink] + 1;
+ updateFirstDrinkTime();
+ updateDrinks();
+}
+
+function removeDrink(){
+ if (drinks[activeDrink] > 0) drinks[activeDrink] = drinks[activeDrink] - 1;
+ updateDrinks();
+
+ if ((!BANGLEJS2) && (drinks[0] == 0) && (drinks[1] == 0) && (drinks[2] == 0)) {
+ resetDrinksFn()
+ }
+}
+
+function previousDrink(){
+ if (activeDrink > 0) activeDrink = activeDrink - 1;
+ updateDrinks();
+}
+
+function nextDrink(){
+ if (activeDrink < maxDrinks) activeDrink = activeDrink + 1;
+ updateDrinks();
+}
+
+function showDrinks() {
+ g.setBgColor(g.theme.bg);
+ g.drawImage(icoBeer,0,100);
+ g.drawImage(icoCocktail,40,100);
+ g.drawImage(icoShot,80,100);
+}
+
+function resetDrinksFn() {
+ g.clearRect(0,34,176,176); //Clear
+ resetDrinks = E.showPrompt("Reset drinks?", {
+ title: "Confirm",
+ buttons: { Yes: true, No: false },
+ });
+ resetDrinks.then((confirm) => {
+ if (confirm) {
+ for (let i = 0; i <= maxDrinks; i++) {
+ drinks[i] = 0;
+ }
+ //console.log("reset to default");
+ }
+ //console.log("reset " + confirm);
+ firstDrinkTime = null;
+ showDrinks();
+ updateDrinks();
+ updateTime();
+ updateFirstDrinkTime();
+ });
+}
+
+
+function initDragEvents() {
+
+if (BANGLEJS2) {
+ Bangle.on("drag", e => {
+ if (!drag) { // start dragging
+ drag = {x: e.x, y: e.y};
+ } else if (!e.b) { // released
+ const dx = e.x-drag.x, dy = e.y-drag.y;
+ drag = null;
+ if (Math.abs(dx)>Math.abs(dy)+10) {
+ // horizontal
+ if (dx < dy) {
+ //console.log("left " + dx + " " + dy);
+ previousDrink();
+ } else {
+ //console.log("right " + dx + " " + dy);
+ nextDrink();
+ }
+ } else if (Math.abs(dy)>Math.abs(dx)+10) {
+ // vertical
+ if (dx < dy) {
+ //console.log("down " + dx + " " + dy);
+ removeDrink();
+ } else {
+ //console.log("up " + dx + " " + dy);
+ addDrink();
+ }
+ } else {
+ //console.log("tap " + e.x + " " + e.y);
+ if (e.x > 145 && e.y > 145) {
+ resetDrinksFn();
+ }
+ }
+ }
+ });
+ } else {
+ setWatch(addDrink, BTN1, { repeat: true, debounce:50 });
+ setWatch(removeDrink, BTN3, { repeat: true, debounce:50 });
+ setWatch(previousDrink, BTN4, { repeat: true, debounce:50 });
+ setWatch(nextDrink, BTN5, { repeat: true, debounce:50 });
+ }
+}
+
+
+loadMySettings();
+showDrinks();
+
+
+if (drawTimeout) clearTimeout(drawTimeout);
+drawTimeout = undefined;
+updateTime();
+queueDrawTime();
+initDragEvents();
+updateDrinks();
+updateFirstDrinkTime();
+
diff --git a/apps/drinkcounter/drinkcounter-icon.js b/apps/drinkcounter/drinkcounter-icon.js
new file mode 100644
index 000000000..e7b95f9ef
--- /dev/null
+++ b/apps/drinkcounter/drinkcounter-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AAWBAAomkFpAweD4fXAAIGGAAo4bExAuJF4YGGF6QmJF5QHHF8o4JF1pgSF7pgRF96/vF5QJSF6YcHBAIJQdyIxFAAvGBBAJIFyYwGEgIACBBAxFFyovCEYoAOGTAvbGKYvE6vVEYYFHAwbEYF4YiFAoQGJFIYwUF7QwEF8ooFF/4v2XBgv1d94v/F/7vsF64uDF9IAFFx4vDXBi/IF+guQR6wvCFSIvOAwQFFAwYvcACQvuXSgvcFywxEACItZAH4A/AH4AlA=="))
diff --git a/apps/drinkcounter/drinkcounter.png b/apps/drinkcounter/drinkcounter.png
new file mode 100644
index 000000000..91a0cd4ad
Binary files /dev/null and b/apps/drinkcounter/drinkcounter.png differ
diff --git a/apps/drinkcounter/metadata.json b/apps/drinkcounter/metadata.json
new file mode 100644
index 000000000..2b8d7fe71
--- /dev/null
+++ b/apps/drinkcounter/metadata.json
@@ -0,0 +1,24 @@
+{
+ "id": "drinkcounter",
+ "name": "Drink Counter",
+ "shortName": "Drink Counter",
+ "version": "0.25",
+ "description": "Counts drinks you had for science. Calculates blood alcohol content (BAC)",
+ "allow_emulator":true,
+ "icon": "drinkcounter.png",
+ "type": "app",
+ "tags": "health",
+ "screenshots": [{"url":"screenshot_drnkcnt.png"}],
+ "supports": ["BANGLEJS", "BANGLEJS2"],
+ "readme": "README.md",
+ "storage": [
+ {"name":"drinkcounter.app.js","url":"app.js"},
+ {"name":"drinkcounter.img","url":"drinkcounter-icon.js","evaluate":true},
+ {"name":"drinkcounter.settings.js","url":"settings.js"}
+ ],
+ "data": [
+ {"name":"drinkcounter.settings.json"},
+ {"name":"drinkcounter.json"},
+ {"name":"drinkcounter.status.json"}
+ ]
+}
\ No newline at end of file
diff --git a/apps/drinkcounter/screenshot_drnkcnt.png b/apps/drinkcounter/screenshot_drnkcnt.png
new file mode 100644
index 000000000..7547eb63f
Binary files /dev/null and b/apps/drinkcounter/screenshot_drnkcnt.png differ
diff --git a/apps/drinkcounter/settings.js b/apps/drinkcounter/settings.js
new file mode 100644
index 000000000..336229b73
--- /dev/null
+++ b/apps/drinkcounter/settings.js
@@ -0,0 +1,58 @@
+(function(back) {
+ var FILE = "drinkcounter.json";
+ var settings = Object.assign({
+ secondsOnUnlock: false,
+ }, require('Storage').readJSON(FILE, true) || {});
+
+ function writeSettings() {
+ require('Storage').writeJSON(FILE, settings);
+ }
+
+ // Helper method which uses int-based menu item for set of string values
+ function stringItems(startvalue, writer, values) {
+ return {
+ value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
+ format: v => values[v],
+ min: 0,
+ max: values.length - 1,
+ wrap: true,
+ step: 1,
+ onchange: v => {
+ writer(values[v]);
+ writeSettings();
+ }
+ };
+ }
+
+ // Helper method which breaks string set settings down to local settings object
+ function stringInSettings(name, values) {
+ return stringItems(settings[name], v => settings[name] = v, values);
+ }
+
+ var mainmenu = {
+ "": {
+ "title": "Drink counter"
+ },
+ "< Back": () => back(),
+
+ "Beer size": stringInSettings("beerSize", ["0.3L", "0.5L"]),
+
+
+ "Sex": stringInSettings("sex", ["male", "female"]),
+
+ 'Weight': {
+ value: 80|settings.weight,
+ min: 40, max: 500,
+ onchange: v => {
+ settings.weight = v;
+ writeSettings();
+ }
+ },
+ "Weight unit": stringInSettings("weightUnit", ["Kilo", "US Pounds"])
+
+
+ };
+
+ E.showMenu(mainmenu);
+
+});
diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog
index 95952b9fe..16c550334 100644
--- a/apps/dtlaunch/ChangeLog
+++ b/apps/dtlaunch/ChangeLog
@@ -11,3 +11,6 @@
0.11: Fix bangle.js 1 white icons not displaying
0.12: On Bangle 2 change to swiping up/down to move between pages as to match page indicator. Swiping from left to right now loads the clock.
0.13: Added swipeExit setting so that left-right to exit is an option
+0.14: Don't move pages when doing exit swipe - Bangle 2.
+0.15: 'Swipe to exit'-code is slightly altered to be more reliable - Bangle 2.
+0.16: Use default Bangle formatter for booleans
diff --git a/apps/dtlaunch/README.md b/apps/dtlaunch/README.md
index bea20ef65..1835bc842 100644
--- a/apps/dtlaunch/README.md
+++ b/apps/dtlaunch/README.md
@@ -27,8 +27,8 @@ Bangle 2:
## Controls- Bangle 2
-**Touch** - icon to select, scond touch launches app
+**Touch** - icon to select, second touch launches app
-**Swipe Left** - move to next page of app icons
+**Swipe Left/Up** - move to next page of app icons
-**Swipe Right** - move to previous page of app icons
+**Swipe Right/Down** - move to previous page of app icons
diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js
index 8466a7414..8cd5790bb 100644
--- a/apps/dtlaunch/app-b2.js
+++ b/apps/dtlaunch/app-b2.js
@@ -89,22 +89,16 @@ function drawPage(p){
Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{
selected = 0;
oldselected=-1;
- if(settings.swipeExit && dirLeftRight==1) showClock();
+ if(settings.swipeExit && dirLeftRight==1) load();
if (dirUpDown==-1||dirLeftRight==-1){
++page; if (page>maxPage) page=0;
drawPage(page);
- } else if (dirUpDown==1||dirLeftRight==1){
+ } else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){
--page; if (page<0) page=maxPage;
drawPage(page);
}
});
-function showClock(){
- var app = require("Storage").readJSON('setting.json', 1).clock;
- if (app) load(app);
- else E.showMessage("clock\nnot found");
-}
-
function isTouched(p,n){
if (n<0 || n>3) return false;
var x1 = (n%2)*72+XOFF; var y1 = n>1?72+YOFF:YOFF;
diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json
index 7784972ca..36728f342 100644
--- a/apps/dtlaunch/metadata.json
+++ b/apps/dtlaunch/metadata.json
@@ -1,7 +1,7 @@
{
"id": "dtlaunch",
"name": "Desktop Launcher",
- "version": "0.13",
+ "version": "0.16",
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
"icon": "icon.png",
diff --git a/apps/dtlaunch/settings-b1.js b/apps/dtlaunch/settings-b1.js
index f3101da16..fe5546edb 100644
--- a/apps/dtlaunch/settings-b1.js
+++ b/apps/dtlaunch/settings-b1.js
@@ -15,7 +15,6 @@
"< Back" : () => back(),
'Show clocks': {
value: settings.showClocks,
- format: v => v?"On":"Off",
onchange: v => {
settings.showClocks = v;
writeSettings();
@@ -23,7 +22,6 @@
},
'Show launchers': {
value: settings.showLaunchers,
- format: v => v?"On":"Off",
onchange: v => {
settings.showLaunchers = v;
writeSettings();
diff --git a/apps/dtlaunch/settings-b2.js b/apps/dtlaunch/settings-b2.js
index 7ead63be0..fac9c0fff 100644
--- a/apps/dtlaunch/settings-b2.js
+++ b/apps/dtlaunch/settings-b2.js
@@ -18,7 +18,6 @@
"< Back" : () => back(),
'Show clocks': {
value: settings.showClocks,
- format: v => v?"On":"Off",
onchange: v => {
settings.showClocks = v;
writeSettings();
@@ -26,7 +25,6 @@
},
'Show launchers': {
value: settings.showLaunchers,
- format: v => v?"On":"Off",
onchange: v => {
settings.showLaunchers = v;
writeSettings();
@@ -34,7 +32,6 @@
},
'Direct launch': {
value: settings.direct,
- format: v => v?"On":"Off",
onchange: v => {
settings.direct = v;
writeSettings();
@@ -42,7 +39,6 @@
},
'Swipe Exit': {
value: settings.swipeExit,
- format: v => v?"On":"Off",
onchange: v => {
settings.swipeExit = v;
writeSettings();
@@ -50,7 +46,6 @@
},
'One click exit': {
value: settings.oneClickExit,
- format: v => v?"On":"Off",
onchange: v => {
settings.oneClickExit = v;
writeSettings();
diff --git a/apps/dvdbounce/ChangeLog b/apps/dvdbounce/ChangeLog
new file mode 100644
index 000000000..6d1dc4ce4
--- /dev/null
+++ b/apps/dvdbounce/ChangeLog
@@ -0,0 +1 @@
+0.01: Created the app. The logo bounces and buzz when it hits the angles.
diff --git a/apps/dvdbounce/README.md b/apps/dvdbounce/README.md
new file mode 100644
index 000000000..50f3ef0e9
--- /dev/null
+++ b/apps/dvdbounce/README.md
@@ -0,0 +1,9 @@
+# Bouncing DVD logo
+
+Have you ever wanted to admire the bouncing DVD logo on your watch? Now you can! Let's hope it touches an angle.
+
+
+
+## Creator
+
+I'm [TrinTragula](https://github.com/TrinTragula) on Github. Feel free to reach me.
\ No newline at end of file
diff --git a/apps/dvdbounce/dvdbounce-icon.js b/apps/dvdbounce/dvdbounce-icon.js
new file mode 100644
index 000000000..0625c2394
--- /dev/null
+++ b/apps/dvdbounce/dvdbounce-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwhC/AH4A/AH4A/ACWIAA4MJwAXPhALKC5AlCBZYADE4gXELQ4SDC4gCBC4IDCAwYTEBAghCBYYCEC5YUFAooOIBBAKDMwwIFCwQIDNIZtGTRANEEIiyMVYrYHLIq0GQA4OGABIPPAA77GC8CDGABAfFBAbpCAIIfCAQqmJhAXDhB4CCIMIEgIYETAoaCC5zYHIIRQDDATAKPJasWAH4A/AH4A/AE4"))
\ No newline at end of file
diff --git a/apps/dvdbounce/dvdbounce.app.js b/apps/dvdbounce/dvdbounce.app.js
new file mode 100644
index 000000000..39037df34
--- /dev/null
+++ b/apps/dvdbounce/dvdbounce.app.js
@@ -0,0 +1,108 @@
+// The DVD logo
+var dvdLogo = require("heatshrink").decompress(atob("3dTwIFC/4AG/ALCgYJEwAcDj4XHBgYJF4AJCg4WH8AMCn4KFF4YWH/wLCh4KJIpA7CgIKG+BnHOg1/BYxGCCw4LDHQ/8IpQ6CQBBRBIpBpDBY4uCKA6jDUQy8FTIn3EQYIBGYSQDBYTVF/x0DKITMGFwh4D96jDKILuDDoSvDEAn9AQIeDB4glCwA5ELwW/FIRNB/x2BVQSvDHIgIB+ZvCNwWPOwZ7DAYKEFDwJoBAYPhHgYeC8ADCP4QEB85YCEQRFELoIqBBA5oFL4SMEwBFEBAWfDwQFB4K1EJoPwIooIB+InCHIQ8EOgYIHA4PAIQP8IogqCBYQIFw4TBAoRICIoQiB/AaDBAfwJAOAUwTrEFQZFHAQPwCYPwCIIxBWIZFIwICBSIIKBIo4IHFYXgSwRFKBARFCFYLlC8DDCRZTaDOIPPHgngaIhFFBoQfB/YhCIojRDBAg/B/gaCGYRFEHgaUED4IEBD4JBDIAIDBLgQEBIoYbC4AMCHAQcBG4IbCAgJFDCQRlBJIIzCIogJCBAgWCD4IICHARFDCwQIEIAY7DGYRmBEAIWCBAJOCNwYfBPAQSBEIWDMog8DAAQfBUYYzEAATkCBAq+CGgQzEYQgIGD4Q3CGYJnDKYhFGCwLtEVAjQDIow2BawY8HNQQIFCwR0CfYgWFBAoWDEAJ5CIogWCIooHCEAR5CJQQWFIoYPCOgg0BHgiJBIooHDUYaCCIoRLCABl/CB4AFg5MFACBNGAB8fQYYARgL/CACc/CysHLisAuAWVhgWVgL/BCyn/WyX/AAx4Mv4VHAARLJFZAAEbA5VBABwWFh4WPJAs/CZoODOA3A/8H/k//BNBK4N/AQQPB/wWF/kf4P/w4jBg4+BKIMAgYuC/BEF4P4n/w//gEIPwCYJYBCAYLBT4f4n0P/0f/k+g/+FoPwn4uDHQYACwZFB4ZHB8A2BKAQYBCAXwW4nwuF//BCBFgJ3CwZ/BCAR1BUIkEJYJHCFgINB+F/GgIAC4BLENgXhI4J5B8BfCI4KMEFwY0BKoK6BvwSDYoIoDCAIuE8APBXQMPwJaC/kPDALqEGgf8NAK6NAoLpDTYS6CCAKCCAoK+BCwhYBMYQiBXQOALQQ2BAQQWFXggAMGoIAECx6gBAArVEABJDDAAhQDABCTBABIYJ4AVKAAbCDChYA="));
+
+// Screen width
+const WIDTH = g.getWidth();
+// Screen height
+const HEIGHT = g.getHeight();
+// dvd logo image width
+const IMG_WIDTH = 94;
+/// dvd logo image height
+const IMG_HEIGHT = 42;
+
+// Assign a random X and Y initial speed between 1.5 and 1
+var speedX = 1.5 - Math.random() / 2;
+var speedY = 1.5 - Math.random() / 2;
+// The logo X and Y position
+var posX = 0;
+var posY = 0;
+
+// The current logo color
+var currentColor = "#ff00ff";
+
+// Get a random value between "ff" and "00"
+function getHexColor() {
+ if (Math.round(Math.random())) {
+ return "ff";
+ } else {
+ return "00";
+ }
+}
+
+// Get a new 8 bit color
+function getNewColor() {
+ return "#" + getHexColor() + getHexColor() + getHexColor();
+}
+
+// Change the dvd logo color on impact
+// Only allow colors different from the current one
+// and different from the bg
+function changeColor() {
+ var newColor = getNewColor();
+ while (newColor == currentColor || newColor == "#000000") {
+ newColor = getNewColor();
+ }
+ currentColor = newColor;
+ g.setColor(newColor);
+}
+
+// Draw the logo
+function draw() {
+ // Move it
+ posX += speedX;
+ posY += speedY;
+
+ var collisions = 0;
+ // Collision detection
+ if (posX <= 0) {
+ speedX = -speedX;
+ posX = 0;
+ collisions++;
+ }
+ if (posY <= 0) {
+ speedY = -speedY;
+ posY = 0;
+ collisions++;
+ }
+ if (posX >= (WIDTH - IMG_WIDTH)) {
+ speedX = -speedX;
+ posX = WIDTH - IMG_WIDTH;
+ collisions++;
+ }
+ if (posY >= (HEIGHT - IMG_HEIGHT)) {
+ speedY = -speedY;
+ posY = HEIGHT - IMG_HEIGHT;
+ collisions++;
+ }
+
+ // If we detected 2 collisions, we touched an angle, HURRAY!
+ if (collisions > 1) {
+ Bangle.buzz();
+ }
+
+ // Change logo color on collision
+ if (collisions > 0) {
+ changeColor();
+ }
+
+ // Actually draw the logo
+ g.clear();
+ g.drawImage(dvdLogo, posX, posY, {
+ scale: 0.5
+ });
+ setTimeout(function () {
+ draw();
+ }, 15);
+}
+
+// Set the background to black
+g.setBgColor(0, 0, 0);
+// Start from purple
+g.setColor(currentColor);
+// Clear the screen
+g.clear();
+// Start drawing
+draw();
+
+// Exit on button press
+setWatch(Bangle.showLauncher, BTN, { repeat: false, edge: "falling" });
diff --git a/apps/dvdbounce/dvdbounce.png b/apps/dvdbounce/dvdbounce.png
new file mode 100644
index 000000000..a44e7a4ba
Binary files /dev/null and b/apps/dvdbounce/dvdbounce.png differ
diff --git a/apps/dvdbounce/metadata.json b/apps/dvdbounce/metadata.json
new file mode 100644
index 000000000..f1c3e8343
--- /dev/null
+++ b/apps/dvdbounce/metadata.json
@@ -0,0 +1,31 @@
+{
+ "id": "dvdbounce",
+ "name": "Bouncing DVD logo",
+ "shortName": "Bouncing DVD",
+ "version": "0.01",
+ "description": "Have you ever wanted to admire the bouncing DVD logo on your watch? Now you can! Let's hope it touches an angle.",
+ "icon": "dvdbounce.png",
+ "tags": "game",
+ "supports": [
+ "BANGLEJS",
+ "BANGLEJS2"
+ ],
+ "readme": "README.md",
+ "allow_emulator": true,
+ "storage": [
+ {
+ "name": "dvdbounce.app.js",
+ "url": "dvdbounce.app.js"
+ },
+ {
+ "name": "dvdbounce.img",
+ "url": "dvdbounce-icon.js",
+ "evaluate": true
+ }
+ ],
+ "screenshots": [
+ {
+ "url": "screenshot.gif"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/apps/dvdbounce/screenshot.gif b/apps/dvdbounce/screenshot.gif
new file mode 100644
index 000000000..6c82438bc
Binary files /dev/null and b/apps/dvdbounce/screenshot.gif differ
diff --git a/apps/espruinoctrl/README.md b/apps/espruinoctrl/README.md
index a7bca662c..7b2e434e7 100644
--- a/apps/espruinoctrl/README.md
+++ b/apps/espruinoctrl/README.md
@@ -17,7 +17,7 @@ showing available Espruino devices is popped up.
device being connected to. Use this if you want to print data - eg: `print(E.getBattery())`
When done, click 'Upload'. Your changes will be saved to local storage
-so they'll be remembered next time you upload from the same device.s
+so they'll be remembered next time you upload from the same device.
## Usage
diff --git a/apps/espruinoctrl/app-icon.js b/apps/espruinoctrl/app-icon.js
index 70d2dd062..3f9572f72 100644
--- a/apps/espruinoctrl/app-icon.js
+++ b/apps/espruinoctrl/app-icon.js
@@ -1 +1 @@
-require("heatshrink").decompress(atob("mEwhH+AH4A/AH4A/AH4AFwIuuAAIllAAYIGF041IF34AKqwuuAANXF9QuCAANdGHqQgGBwvdGCIud5mjGB4udAAIwPFz3MSR61VFxQwNci4vGeh4uXGAguHGBK3WGA4AIegtXc69dGBxoBGAouWO4IwNe4gwZa4YwLFwikEFzAwLFwwwCFzQwKFw68YGB4AdF5AwmF5IwlF5QwkF5Yw/F8IwEL9WBB4IuuADwuzGxAugFAgliGBYutAH4A/AH4A/ADA="))
+require("heatshrink").decompress(atob("mEw4UA///muVt9TgH+Jf4AQgILKgtABI9VqkVqAgHqoABC48FBYQKGhEVBQNUBY0qyoLJ1WlEZMq1ILJhWqBZMC1QwCBY0PGAYLGn/qGAQLG/4wDBIkggf8GARfF1ED+BhCTQgTBgfAMISaF1WAAYM61SBG0ADB/wLFgNq1EAHoIcDXYVaCYMP+EqC4kVqwTBn/AhDqFqowBn72HqowCBZAwCBZAwCBZAwCBZIwIiowKBYVWC5VUkAvJXYiaDBYS7FTQVUgr2HC4IgHAAYgHAH4AJA=="))
diff --git a/apps/espruinoctrl/metadata.json b/apps/espruinoctrl/metadata.json
index 5798c7842..5107bc6ae 100644
--- a/apps/espruinoctrl/metadata.json
+++ b/apps/espruinoctrl/metadata.json
@@ -5,8 +5,8 @@
"version": "0.01",
"description": "Send commands to other Espruino devices via the Bluetooth UART interface. Customisable commands!",
"icon": "app.png",
- "tags": "",
- "supports": ["BANGLEJS"],
+ "tags": "tool,bluetooth",
+ "supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"custom": "custom.html",
"storage": [
diff --git a/apps/espruinoprog/ChangeLog b/apps/espruinoprog/ChangeLog
new file mode 100644
index 000000000..6fdcad1d6
--- /dev/null
+++ b/apps/espruinoprog/ChangeLog
@@ -0,0 +1,4 @@
+0.01: New App!
+0.02: Add 'pre' code that can erase the device
+ Wait more between sending code snippets
+ Now force use of 'Storage' (assume 2v00 or later)
diff --git a/apps/espruinoprog/README.md b/apps/espruinoprog/README.md
new file mode 100644
index 000000000..aef4cccad
--- /dev/null
+++ b/apps/espruinoprog/README.md
@@ -0,0 +1,43 @@
+# Espruino Programmer
+
+Finds Bluetooth devices with a specific name (eg `Puck.js`), connects and uploads code. Great for programming many devices at once!
+
+**WARNING:** This will reprogram **any matching Espruino device within range** while
+the app is running. Unless you are careful to remove other devices from the area or
+turn them off, you could find some of your devices unexpectedly get programmed!
+
+## Customising
+
+Click on the Customise button in the app loader to set up the programmer.
+
+* First you need to choose the kind of devices you want to upload to. This is
+the text that should match the Bluetooth advertising name. So `Puck.js` for Puck.js
+devices, or `Bangle.js` for Bangles.
+* In the next box, you have code to run before the upload of the main code. By default
+the code `require("Storage").list().forEach(f=>require("Storage").erase(f));reset();` will
+erase all files on the device and reset it.
+* Now paste in the code you want to write to the device. This is automatically
+written to flash (`.bootcde`). See https://www.espruino.com/Saving#save-on-send-to-flash-
+for more information.
+* Now enter the code that should be sent **after** programming. This code
+should make the device so it doesn't advertise on Bluetooth with the Bluetooth
+name you entered for the first item. It may also help if it indicates to you that
+the device is programmed properly.
+ * You could turn advertising off with `NRF.sleep()`
+ * You could change the advertising name with `NRF.setAdvertising({},{name:"Ok"});`
+ * On a Bangle, you could turn it off with `Bangle.off()`
+* Finally scroll down and click `Upload`
+* Now you can run the new `Programmer` app on the Bangle.
+
+## Usage
+
+Just run the app, and as soon as it starts it'll start scanning for
+devices to upload to!
+
+To stop scanning, long-press the button to return to the clock.
+
+## Notes
+
+* This assumes the device being written to is at least version 2v00 of Espruino
+* Currently, code is not minified before upload (so you need to supply pre-minified
+ code if you want that)
diff --git a/apps/espruinoprog/app-icon.js b/apps/espruinoprog/app-icon.js
new file mode 100644
index 000000000..532c60eea
--- /dev/null
+++ b/apps/espruinoprog/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEw4cA/4AB7wJB8/5uX+7uUgH41lSKf4AKpMkyQCCggEDAQVtCAMCCNWUx9JufSrmkCJeKqsiytICJtFkWRCJWAEaARCI5BkEoAGBymJ9eSvXkCJZ9JCLI1DyM9uQRLNYWRpRZMR5ARWAwSPCuWR9MuCJZZIgARGPouTCIcSA4OQAoMW7dt2wCEEZECCI1oCJAADrZyBAAcDuQRByOABQkKDAvbtwRBxu24AMFAAcGCIY/B7AQIhpOC3MjKIVsCJe3jYRCwiPEkARBQg227ieDAQO0CJPhCKHJCK1N0ARI28JCIjUDEY4OBzWRfAoRG3ARBygRH3oPBswRB4QjFfAYgCt9pYoJoEkmbCJONCI1ACJGSiQRE7TXDCIuQEYkmCIhpDEYSCFCIj2DCIOTrYRE6ARDAH4AHA"))
diff --git a/apps/espruinoprog/app.js b/apps/espruinoprog/app.js
new file mode 100644
index 000000000..58fac4a0b
--- /dev/null
+++ b/apps/espruinoprog/app.js
@@ -0,0 +1,100 @@
+var uart; // require("ble_uart")
+var device; // BluetoothDevice
+var uploadTimeout; // a timeout used during upload - if we disconnect, kill this
+Bangle.loadWidgets();
+
+var json = require("Storage").readJSON("espruinoprog.json",1);
+/*var json = { // for example
+ namePrefix : "Puck.js ",
+ code : "E.setBootCode('digitalPulse(LED2,1,100);')",
+ post : "LED.set();NRF.sleep()",
+};*/
+
+if ("object" != typeof json) {
+ E.showAlert("JSON not found","Programmer").then(() => load());
+ throw new Error("JSON not found");
+ // stops execution
+}
+
+// Set up terminal
+var R = Bangle.appRect;
+var termg = Graphics.createArrayBuffer(R.w, R.h, 1, {msb:true});
+termg.setFont("6x8");
+var term;
+
+function showTerminal() {
+ E.showMenu(); // clear anything that was drawn
+ if (term) term.print(""); // redraw terminal
+}
+
+function scanAndConnect() {
+ termg.clear();
+ term = require("VT100").connect(termg, {
+ charWidth : 6,
+ charHeight : 8
+ });
+ term.print = str => {
+ for (var i of str) term.char(i);
+ g.reset().drawImage(termg,R.x,R.y);
+ };
+ term.print(`\r\nScanning...\r\n`);
+ NRF.requestDevice({ filters: [{ namePrefix: json.namePrefix }] }).then(function(dev) {
+ term.print(`Found ${dev.name||dev.id.substr(0,17)}\r\n`);
+ device = dev;
+
+ term.print(`Connect to ${dev.name||dev.id.substr(0,17)}...\r\n`);
+ device.removeAllListeners();
+ device.on('gattserverdisconnected', function(reason) {
+ if (!uart) return;
+ term.print(`\r\nDISCONNECTED (${reason})\r\n`);
+ uart = undefined;
+ device = undefined;
+ if (uploadTimeout) clearTimeout(uploadTimeout);
+ uploadTimeout = undefined;
+ setTimeout(scanAndConnect, 1000);
+ });
+ require("ble_uart").connect(device).then(function(u) {
+ uart = u;
+ term.print("Connected...\r\n");
+ uart.removeAllListeners();
+ uart.on('data', function(d) { term.print(d); });
+ term.print("Upload initial...\r\n");
+ uart.write((json.pre||"")+"\n").then(() => {
+ term.print("\r\Done.\r\n");
+ uploadTimeout = setTimeout(function() {
+ uploadTimeout = undefined;
+ term.print("\r\nUpload Code...\r\n");
+ uart.write((json.code||"")+"\n").then(() => {
+ term.print("\r\Done.\r\n");
+ // main upload completed - wait a bit
+ uploadTimeout = setTimeout(function() {
+ uploadTimeout = undefined;
+ term.print("\r\Upload final...\r\n");
+ // now upload the code to run after...
+ uart.write((json.post||"")+"\n").then(() => {
+ term.print("\r\nDone.\r\n");
+ // now wait and disconnect (if not already done!)
+ uploadTimeout = setTimeout(function() {
+ uploadTimeout = undefined;
+ term.print("\r\nDisconnecting...\r\n");
+ if (uart) uart.disconnect();
+ }, 500);
+ });
+ }, 2000);
+ });
+ }, 2000);
+ });
+ });
+ }).catch(err => {
+ if (err.toString().startsWith("No device found")) {
+ // expected - try again
+ scanAndConnect();
+ } else
+ term.print(`\r\ERROR ${err.toString()}\r\n`);
+ });
+}
+
+// now start
+Bangle.drawWidgets();
+showTerminal();
+scanAndConnect();
diff --git a/apps/espruinoprog/app.png b/apps/espruinoprog/app.png
new file mode 100644
index 000000000..b2b435f04
Binary files /dev/null and b/apps/espruinoprog/app.png differ
diff --git a/apps/espruinoprog/custom.html b/apps/espruinoprog/custom.html
new file mode 100644
index 000000000..a12189707
--- /dev/null
+++ b/apps/espruinoprog/custom.html
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Upload code to devices with names starting with:
+
+ Enter the code to send before upload here:
+
+ Enter your program to upload here:
+
+ Enter the code to send after upload here:
+
+ Then click Upload
+ Click here to reset to defaults.
+
+
+
+
diff --git a/apps/espruinoprog/metadata.json b/apps/espruinoprog/metadata.json
new file mode 100644
index 000000000..ebb55b23d
--- /dev/null
+++ b/apps/espruinoprog/metadata.json
@@ -0,0 +1,17 @@
+{
+ "id": "espruinoprog",
+ "name": "Espruino Programmer",
+ "shortName": "Programmer",
+ "version": "0.02",
+ "description": "Finds Bluetooth devices with a specific name (eg 'Puck.js'), connects and uploads code. Great for programming many devices at once!",
+ "icon": "app.png",
+ "tags": "tool,bluetooth",
+ "supports": ["BANGLEJS","BANGLEJS2"],
+ "readme": "README.md",
+ "custom": "custom.html",
+ "storage": [
+ {"name":"espruinoprog.app.js","url":"app.js"},
+ {"name":"espruinoprog.img","url":"app-icon.js","evaluate":true},
+ {"name":"espruinoprog.json"}
+ ]
+}
diff --git a/apps/nato/changelog.txt b/apps/espruinoterm/ChangeLog
similarity index 100%
rename from apps/nato/changelog.txt
rename to apps/espruinoterm/ChangeLog
diff --git a/apps/espruinoterm/README.md b/apps/espruinoterm/README.md
new file mode 100644
index 000000000..df26d59a0
--- /dev/null
+++ b/apps/espruinoterm/README.md
@@ -0,0 +1,22 @@
+# Espruino Terminal
+
+Send commands to other Espruino devices via the Bluetooth UART interface and
+see the result on a terminal.
+
+## Customising
+
+Once installed and you're connected to the Bangle you can click the button next to the app in the app loader
+to change the commands (they will be read from the device).
+
+When done, click `Save to Bangle.js` and your changes will be saved to the same device.
+
+## Usage
+
+* Load the app and after a few seconds you'll see a menu with Espruino devices
+in the vicinity.
+* Tap on the device you want to connect to
+* A terminal will pop up showing `Connecting...` and then `Connected`
+* Now tap on the right (or press the button) to bring up a menu with options for commands, or the option to disconnect.
+
+You can also choose `Custom` in which case a keyboard (using the currently installed text input method) will
+be displayed and you can enter the command you would like to send.
diff --git a/apps/espruinoterm/app-icon.js b/apps/espruinoterm/app-icon.js
new file mode 100644
index 000000000..f566aedf7
--- /dev/null
+++ b/apps/espruinoterm/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwcCpMkyQC/AVW//4AK/oR/COD8LCP4R/CK8DCKNsCKFt2BHPhu2CJ8BCKAjQI4OQNaIUB23bsCPMCJzp/CP4Rf/4AKCKwC/AVIA=="))
diff --git a/apps/espruinoterm/app.js b/apps/espruinoterm/app.js
new file mode 100644
index 000000000..348190db4
--- /dev/null
+++ b/apps/espruinoterm/app.js
@@ -0,0 +1,101 @@
+var uart; // require("ble_uart")
+var device; // BluetoothDevice
+var customCommand = "";
+// Set up terminal
+Bangle.loadWidgets();
+var R = Bangle.appRect;
+var termg = Graphics.createArrayBuffer(R.w, R.h, 1, {msb:true});
+var termVisible = false;
+termg.setFont("6x8");
+term = require("VT100").connect(termg, {
+ charWidth : 6,
+ charHeight : 8
+});
+term.print = str => {
+ for (var i of str) term.char(i);
+ if (termVisible) g.reset().drawImage(termg,R.x,R.y).setFont("6x8").setFontAlign(0,-1,1).drawString("MORE",R.w-1,(R.y+R.y2)/2);
+};
+
+function showConnectMenu() {
+ termVisible = false;
+ var m = { "" : {title:"Devices"} };
+ E.showMessage("Scanning...");
+ NRF.findDevices(devices => {
+ devices.forEach(dev=>{
+ m[dev.name||dev.id.substr(0,17)] = ()=>{
+ connectTo(dev);
+ };
+ });
+ m["< Back"] = () => showConnectMenu();
+ E.showMenu(m);
+ },{filters:[
+ { namePrefix: 'Puck.js' },
+ { namePrefix: 'Pixl.js' },
+ { namePrefix: 'MDBT42Q' },
+ { namePrefix: 'Bangle.js' },
+ { namePrefix: 'Espruino' },
+ { services: [ "6e400001-b5a3-f393-e0a9-e50e24dcca9e" ] }
+ ],active:true,timeout:4000});
+}
+
+function showOptionsMenu() {
+ if (!uart) showConnectMenu();
+ termVisible = false;
+ var menu = {"":{title:/*LANG*/"Options"},
+ "< Back" : () => showTerminal(),
+ };
+ var json = require("Storage").readJSON("espruinoterm.json",1);
+ if (Array.isArray(json)) {
+ json.forEach(j => { menu[j.title] = () => sendCommand(j.cmd); });
+ } else {
+ Object.assign(menu,{
+ "Version" : () => sendCommand("process.env.VERSION"),
+ "Battery" : () => sendCommand("E.getBattery()"),
+ "Flash LED" : () => sendCommand("LED.set();setTimeout(()=>LED.reset(),1000);")
+ });
+ }
+ menu[/*LANG*/"Custom"] = () => { require("textinput").input({text:customCommand}).then(result => {
+ customCommand = result;
+ sendCommand(customCommand);
+ })};
+ menu[/*LANG*/"Disconnect"] = () => { showTerminal(); term.print("\r\nDisconnecting...\r\n"); uart.disconnect(); }
+
+ E.showMenu(menu);
+}
+
+function showTerminal() {
+ E.showMenu();
+ Bangle.setUI({
+ mode : "custom",
+ btn : n=> { showOptionsMenu(); },
+ touch : (n,e) => { if (n==2) showOptionsMenu(); }
+ });
+ termVisible = true;
+ term.print(""); // redraw terminal
+}
+
+function sendCommand(cmd) {
+ showTerminal();
+ uart.write(cmd+"\n");
+}
+
+function connectTo(dev) {
+ device = dev;
+ showTerminal();
+ term.print(`\r\nConnect to ${dev.name||dev.id.substr(0,17)}...\r\n`);
+ device.on('gattserverdisconnected', function(reason) {
+ term.print(`\r\nDISCONNECTED (${reason})\r\n`);
+ uart = undefined;
+ device = undefined;
+ setTimeout(showConnectMenu, 1000);
+ });
+ require("ble_uart").connect(device).then(function(u) {
+ uart = u;
+ term.print("Connected...\r\n");
+ uart.on('data', function(d) { term.print(d); });
+ });
+}
+
+// now start
+Bangle.drawWidgets();
+showConnectMenu();
diff --git a/apps/espruinoterm/app.json b/apps/espruinoterm/app.json
new file mode 100644
index 000000000..72a12e635
--- /dev/null
+++ b/apps/espruinoterm/app.json
@@ -0,0 +1,5 @@
+[
+ {"title":"Version", "cmd":"process.env.VERSION"},
+ {"title":"Battery", "cmd":"E.getBattery()"},
+ {"title":"Flash LED", "cmd":"LED.set();setTimeout(()=>LED.reset(),1000);"}
+]
diff --git a/apps/espruinoterm/app.png b/apps/espruinoterm/app.png
new file mode 100644
index 000000000..e9a8c3758
Binary files /dev/null and b/apps/espruinoterm/app.png differ
diff --git a/apps/espruinoterm/interface.html b/apps/espruinoterm/interface.html
new file mode 100644
index 000000000..660b3a86c
--- /dev/null
+++ b/apps/espruinoterm/interface.html
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+ Enter the menu items you'd like to see appear in the app below. When finished, click `Save to Bangle.js` to save the JavaScript back.
+
+
+
+
+ Title
+ Command
+
+
+
+
+
+
+
+ Save to Bangle.js
+
+
+
+
+
+
diff --git a/apps/espruinoterm/metadata.json b/apps/espruinoterm/metadata.json
new file mode 100644
index 000000000..25e6183e1
--- /dev/null
+++ b/apps/espruinoterm/metadata.json
@@ -0,0 +1,20 @@
+{
+ "id": "espruinoterm",
+ "name": "Espruino Terminal",
+ "shortName": "Espruino Term",
+ "version": "0.01",
+ "description": "Send commands to other Espruino devices via the Bluetooth UART interface, and see the result on a VT100 terminal. Customisable commands!",
+ "icon": "app.png",
+ "screenshots": [{"url":"screenshot.png"}],
+ "tags": "tool,bluetooth",
+ "supports": ["BANGLEJS","BANGLEJS2"],
+ "readme": "README.md",
+ "interface": "interface.html",
+ "dependencies": {"textinput":"type"},
+ "storage": [
+ {"name":"espruinoterm.app.js","url":"app.js"},
+ {"name":"espruinoterm.img","url":"app-icon.js","evaluate":true}
+ ],"data": [
+ {"name":"espruinoterm.json","url":"app.json"}
+ ]
+}
diff --git a/apps/espruinoterm/screenshot.png b/apps/espruinoterm/screenshot.png
new file mode 100644
index 000000000..cce881a37
Binary files /dev/null and b/apps/espruinoterm/screenshot.png differ
diff --git a/apps/f9lander/ChangeLog b/apps/f9lander/ChangeLog
new file mode 100644
index 000000000..5560f00bc
--- /dev/null
+++ b/apps/f9lander/ChangeLog
@@ -0,0 +1 @@
+0.01: New App!
diff --git a/apps/f9lander/README.md b/apps/f9lander/README.md
new file mode 100644
index 000000000..16202f166
--- /dev/null
+++ b/apps/f9lander/README.md
@@ -0,0 +1,33 @@
+# F9 Lander
+
+Land a Falcon 9 booster on a drone ship.
+
+## Game play
+
+Attempt to land your Falcon 9 booster on a drone ship before running out of fuel.
+A successful landing requires:
+ * setting down on the ship
+ * the booster has to be mostly vertical
+ * the landing speed cannot be too high
+
+## Controls
+
+The angle of the booster is controlled by tilting the watch side-to-side. The
+throttle level is controlled by tilting the watch forward and back:
+ * screen horizontal (face up) means no throttle
+ * screen vertical corresponds to full throttle
+
+The fuel burn rate is proportional to the throttle level.
+
+## Creators
+Liam Kl. B.
+
+Marko Kl. B.
+
+## Screenshots
+
+
+
+
+
+
diff --git a/apps/f9lander/app-icon.js b/apps/f9lander/app-icon.js
new file mode 100644
index 000000000..572768a28
--- /dev/null
+++ b/apps/f9lander/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwcA/4AD/P8yVJkgCCye27dt2wRE//kCIuSuwRIBwgCCpwRQpIRRnYRQkmdCIvPCJICBEZ4RG/IRP/15CJ/z5IRPz4RM/gQB/n+BxICCn/z/P/BxQCDz7mIAX4Cq31/CJ+ebpiYE/IR/CNP/5IROnn//4jP5DFQ5sJCKAjPk3oCMMk4QRQAX4Ckn7jBAA/5CK8nCJPJNHA"))
diff --git a/apps/f9lander/app.js b/apps/f9lander/app.js
new file mode 100644
index 000000000..7e52104c0
--- /dev/null
+++ b/apps/f9lander/app.js
@@ -0,0 +1,150 @@
+const falcon9 = Graphics.createImage(`
+ xxxxx
+ xxxxx xxxxx
+ x x
+ x x
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxxxxxx
+ xx xxxxx xx
+xx xx`);
+
+const droneShip = Graphics.createImage(`
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+`);
+
+const droneX = Math.floor(Math.random()*(g.getWidth()-droneShip.width-40) + 20)
+const cloudOffs = Math.floor(Math.random()*g.getWidth()/2);
+
+const oceanHeight = g.getHeight()*0.1;
+
+const targetY = g.getHeight()-oceanHeight-falcon9.height/2;
+
+var booster = { x : g.getWidth()/4 + Math.random()*g.getWidth()/2,
+ y : 20,
+ vx : 0,
+ vy : 0,
+ mass : 100,
+ fuel : 100 };
+
+var exploded = false;
+var nExplosions = 0;
+var landed = false;
+
+const gravity = 4;
+const dt = 0.1;
+const fuelBurnRate = 20*(176/g.getHeight());
+const maxV = 12;
+
+function flameImageGen (throttle) {
+ var str = " xxx \n xxx \n";
+ str += "xxxxx\n".repeat(throttle);
+ str += " xxx \n x \n";
+ return Graphics.createImage(str);
+}
+
+function drawFalcon(x, y, throttle, angle) {
+ g.setColor(1, 1, 1).drawImage(falcon9, x, y, {rotate:angle});
+ if (throttle>0) {
+ var flameImg = flameImageGen(throttle);
+ var r = falcon9.height/2 + flameImg.height/2-1;
+ var xoffs = -Math.sin(angle)*r;
+ var yoffs = Math.cos(angle)*r;
+ if (Math.random()>0.7) g.setColor(1, 0.5, 0);
+ else g.setColor(1, 1, 0);
+ g.drawImage(flameImg, x+xoffs, y+yoffs, {rotate:angle});
+ }
+}
+
+function drawBG() {
+ g.setBgColor(0.2, 0.2, 1).clear();
+ g.setColor(0, 0, 1).fillRect(0, g.getHeight()-oceanHeight, g.getWidth()-1, g.getHeight()-1);
+ g.setColor(0.5, 0.5, 1).fillCircle(cloudOffs+34, 30, 15).fillCircle(cloudOffs+60, 35, 20).fillCircle(cloudOffs+75, 20, 10);
+ g.setColor(1, 1, 0).fillCircle(g.getWidth(), 0, 20);
+ g.setColor(1, 1, 1).drawImage(droneShip, droneX, g.getHeight()-oceanHeight-1);
+}
+
+function showFuel() {
+ g.setColor(0, 0, 0).setFont("4x6:2").setFontAlign(-1, -1, 0).drawString("Fuel: "+Math.abs(booster.fuel).toFixed(0), 4, 4);
+}
+
+function renderScreen(input) {
+ drawBG();
+ showFuel();
+ drawFalcon(booster.x, booster.y, Math.floor(input.throttle*12), input.angle);
+}
+
+function getInputs() {
+ var accel = Bangle.getAccel();
+ var a = Math.PI/2 + Math.atan2(accel.y, accel.x);
+ var t = (1+accel.z);
+ if (t > 1) t = 1;
+ if (t < 0) t = 0;
+ if (booster.fuel<=0) t = 0;
+ return {throttle: t, angle: a};
+}
+
+function epilogue(str) {
+ g.setFont("Vector", 24).setFontAlign(0, 0, 0).setColor(0, 0, 0).drawString(str, g.getWidth()/2, g.getHeight()/2).flip();
+ g.setFont("Vector", 16).drawString("<= again exit =>", g.getWidth()/2, g.getHeight()/2+20);
+ clearInterval(stepInterval);
+ Bangle.on("swipe", (d) => { if (d>0) load(); else load('f9lander.app.js'); });
+}
+
+function gameStep() {
+ if (exploded) {
+ if (nExplosions++ < 15) {
+ var r = Math.random()*25;
+ var x = Math.random()*30 - 15;
+ var y = Math.random()*30 - 15;
+ g.setColor(1, Math.random()*0.5+0.5, 0).fillCircle(booster.x+x, booster.y+y, r);
+ if (nExplosions==1) Bangle.buzz(600);
+ }
+ else epilogue("You crashed!");
+ }
+ else {
+ var input = getInputs();
+ if (booster.y >= targetY) {
+// console.log(booster.x + " " + booster.y + " " + booster.vy + " " + droneX + " " + input.angle);
+ if (Math.abs(booster.x-droneX-droneShip.width/2) {
+ stepInterval = setInterval(gameStep, Math.floor(1000*dt));
+ Bangle.removeListener("swipe");
+});
diff --git a/apps/f9lander/f9lander.png b/apps/f9lander/f9lander.png
new file mode 100644
index 000000000..f03cc1645
Binary files /dev/null and b/apps/f9lander/f9lander.png differ
diff --git a/apps/f9lander/f9lander_screenshot1.png b/apps/f9lander/f9lander_screenshot1.png
new file mode 100644
index 000000000..ea7d8a834
Binary files /dev/null and b/apps/f9lander/f9lander_screenshot1.png differ
diff --git a/apps/f9lander/f9lander_screenshot2.png b/apps/f9lander/f9lander_screenshot2.png
new file mode 100644
index 000000000..a2f13d6c7
Binary files /dev/null and b/apps/f9lander/f9lander_screenshot2.png differ
diff --git a/apps/f9lander/f9lander_screenshot3.png b/apps/f9lander/f9lander_screenshot3.png
new file mode 100644
index 000000000..61b8be82f
Binary files /dev/null and b/apps/f9lander/f9lander_screenshot3.png differ
diff --git a/apps/f9lander/metadata.json b/apps/f9lander/metadata.json
new file mode 100644
index 000000000..75c6a0164
--- /dev/null
+++ b/apps/f9lander/metadata.json
@@ -0,0 +1,15 @@
+{ "id": "f9lander",
+ "name": "Falcon9 Lander",
+ "shortName":"F9lander",
+ "version":"0.01",
+ "description": "Land a rocket booster",
+ "icon": "f9lander.png",
+ "screenshots" : [ { "url":"f9lander_screenshot1.png" }, { "url":"f9lander_screenshot2.png" }, { "url":"f9lander_screenshot3.png" }],
+ "readme": "README.md",
+ "tags": "game",
+ "supports" : ["BANGLEJS", "BANGLEJS2"],
+ "storage": [
+ {"name":"f9lander.app.js","url":"app.js"},
+ {"name":"f9lander.img","url":"app-icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/fclock/ChangeLog b/apps/fclock/ChangeLog
index 30e049f69..7e7307c59 100644
--- a/apps/fclock/ChangeLog
+++ b/apps/fclock/ChangeLog
@@ -1,2 +1,3 @@
0.01: First published version of app
0.02: Move to Bangle.setUI to launcher support
+0.03: Tell clock widgets to hide.
diff --git a/apps/fclock/fclock.app.js b/apps/fclock/fclock.app.js
index afa0c5e2d..838a5578d 100644
--- a/apps/fclock/fclock.app.js
+++ b/apps/fclock/fclock.app.js
@@ -173,6 +173,9 @@ const drawHR = function () {
}
};
+// Show launcher when button pressed
+Bangle.setUI("clock");
+
// clean app screen
g.clear();
Bangle.loadWidgets();
@@ -198,6 +201,3 @@ Bangle.on('HRM', function (d) {
// draw now
drawClock();
-
-// Show launcher when button pressed
-Bangle.setUI("clock");
diff --git a/apps/fclock/metadata.json b/apps/fclock/metadata.json
index da553e110..dffb197a2 100644
--- a/apps/fclock/metadata.json
+++ b/apps/fclock/metadata.json
@@ -2,7 +2,7 @@
"id": "fclock",
"name": "fclock",
"shortName": "F Clock",
- "version": "0.02",
+ "version": "0.03",
"description": "Simple design of a digital clock",
"icon": "app.png",
"type": "clock",
diff --git a/apps/ffcniftya/ChangeLog b/apps/ffcniftya/ChangeLog
index 420c553f5..6d2f50119 100644
--- a/apps/ffcniftya/ChangeLog
+++ b/apps/ffcniftya/ChangeLog
@@ -1,2 +1,6 @@
0.01: New Clock Nifty A
-0.02: Shows the current week number (ISO8601), can be disabled via settings ""
+0.02: Shows the current week number (ISO8601), can be disabled via settings
+0.03: Call setUI before loading widgets
+ Improve settings page
+0.04: Use ClockFace library
+
diff --git a/apps/ffcniftya/README.md b/apps/ffcniftya/README.md
index 86f1f5c2d..80005fd3c 100644
--- a/apps/ffcniftya/README.md
+++ b/apps/ffcniftya/README.md
@@ -1,13 +1,12 @@
# Nifty-A Clock
-Colors are black/white - photos have non correct camera color "blue"
+Colors are black/white - photos have non correct camera color "blue".
-## This is the clock
+This is the clock:

-## The week number (ISO8601) can be turned of in settings
-(default is **"On"**)
+The week number (ISO8601) can be turned off in settings (default is `On`)

diff --git a/apps/ffcniftya/app.js b/apps/ffcniftya/app.js
index 5da1ec48e..2c1a54f6e 100644
--- a/apps/ffcniftya/app.js
+++ b/apps/ffcniftya/app.js
@@ -1,111 +1,59 @@
-const locale = require("locale");
-const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
-const CFG = require('Storage').readJSON("ffcniftya.json", 1) || {showWeekNum: true};
-
-/* Clock *********************************************/
-const scale = g.getWidth() / 176;
-
-const widget = 24;
-
-const viewport = {
- width: g.getWidth(),
- height: g.getHeight(),
-}
-
-const center = {
- x: viewport.width / 2,
- y: Math.round(((viewport.height - widget) / 2) + widget),
-}
-
-function ISO8601_week_no(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
- var tdt = new Date(date.valueOf());
- var dayn = (date.getDay() + 6) % 7;
- tdt.setDate(tdt.getDate() - dayn + 3);
- var firstThursday = tdt.valueOf();
- tdt.setMonth(0, 1);
- if (tdt.getDay() !== 4) {
- tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
- }
- return 1 + Math.ceil((firstThursday - tdt) / 604800000);
-}
-
-function d02(value) {
- return ('0' + value).substr(-2);
-}
-
-function draw() {
- g.reset();
- g.clearRect(0, widget, viewport.width, viewport.height);
- const now = new Date();
-
- const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0));
- const minutes = d02(now.getMinutes());
- const day = d02(now.getDate());
- const month = d02(now.getMonth() + 1);
- const year = now.getFullYear(now);
- const weekNum = d02(ISO8601_week_no(now));
- const monthName = locale.month(now, 3);
- const dayName = locale.dow(now, 3);
-
- const centerTimeScaleX = center.x + 32 * scale;
- g.setFontAlign(1, 0).setFont("Vector", 90 * scale);
- g.drawString(hour, centerTimeScaleX, center.y - 31 * scale);
- g.drawString(minutes, centerTimeScaleX, center.y + 46 * scale);
-
- g.fillRect(center.x + 30 * scale, center.y - 72 * scale, center.x + 32 * scale, center.y + 74 * scale);
-
- const centerDatesScaleX = center.x + 40 * scale;
- g.setFontAlign(-1, 0).setFont("Vector", 16 * scale);
- g.drawString(year, centerDatesScaleX, center.y - 62 * scale);
- g.drawString(month, centerDatesScaleX, center.y - 44 * scale);
- g.drawString(day, centerDatesScaleX, center.y - 26 * scale);
- if (CFG.showWeekNum) g.drawString(d02(ISO8601_week_no(now)), centerDatesScaleX, center.y + 15 * scale);
- g.drawString(monthName, centerDatesScaleX, center.y + 48 * scale);
- g.drawString(dayName, centerDatesScaleX, center.y + 66 * scale);
-}
-
-
-/* Minute Ticker *************************************/
-
-let tickTimer;
-
-function clearTickTimer() {
- if (tickTimer) {
- clearTimeout(tickTimer);
- tickTimer = undefined;
+// copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
+function ISO8601_week_no(date) {
+ var tdt = new Date(date.valueOf());
+ var dayn = (date.getDay() + 6) % 7;
+ tdt.setDate(tdt.getDate() - dayn + 3);
+ var firstThursday = tdt.valueOf();
+ tdt.setMonth(0, 1);
+ if (tdt.getDay() !== 4) {
+ tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
}
+ return 1 + Math.ceil((firstThursday - tdt) / 604800000);
}
-function queueNextTick() {
- clearTickTimer();
- tickTimer = setTimeout(tick, 60000 - (Date.now() % 60000));
- // tickTimer = setTimeout(tick, 3000);
+function format(value) {
+ return ("0" + value).substr(-2);
}
-function tick() {
- draw();
- queueNextTick();
-}
+const ClockFace = require("ClockFace");
+const clock = new ClockFace({
+ init: function () {
+ const appRect = Bangle.appRect;
-/* Init **********************************************/
+ this.viewport = {
+ width: appRect.w,
+ height: appRect.h
+ };
-// Clear the screen once, at startup
-g.clear();
-// Start ticking
-tick();
+ this.center = {
+ x: this.viewport.width / 2,
+ y: Math.round((this.viewport.height / 2) + appRect.y)
+ };
-// Stop updates when LCD is off, restart when on
-Bangle.on('lcdPower', (on) => {
- if (on) {
- tick(); // Start ticking
- } else {
- clearTickTimer(); // stop ticking
- }
+ this.scale = g.getWidth() / this.viewport.width;
+ this.centerTimeScaleX = this.center.x + 32 * this.scale;
+ this.centerDatesScaleX = this.center.x + 40 * this.scale;
+ },
+ draw: function (date) {
+ const hour = date.getHours() - (this.is12Hour && date.getHours() > 12 ? 12 : 0);
+ const month = date.getMonth() + 1;
+ const monthName = require("date_utils").month(month, 1);
+ const dayName = require("date_utils").dow(date.getDay(), 1);
+
+ g.setFontAlign(1, 0).setFont("Vector", 90 * this.scale);
+ g.drawString(format(hour), this.centerTimeScaleX, this.center.y - 31 * this.scale);
+ g.drawString(format(date.getMinutes()), this.centerTimeScaleX, this.center.y + 46 * this.scale);
+
+ g.fillRect(this.center.x + 30 * this.scale, this.center.y - 72 * this.scale, this.center.x + 32 * this.scale, this.center.y + 74 * this.scale);
+
+ g.setFontAlign(-1, 0).setFont("Vector", 16 * this.scale);
+ g.drawString(date.getFullYear(date), this.centerDatesScaleX, this.center.y - 62 * this.scale);
+ g.drawString(format(month), this.centerDatesScaleX, this.center.y - 44 * this.scale);
+ g.drawString(format(date.getDate()), this.centerDatesScaleX, this.center.y - 26 * this.scale);
+ if (this.showWeekNum) g.drawString(format(ISO8601_week_no(date)), this.centerDatesScaleX, this.center.y + 15 * this.scale);
+ g.drawString(monthName, this.centerDatesScaleX, this.center.y + 48 * this.scale);
+ g.drawString(dayName, this.centerDatesScaleX, this.center.y + 66 * this.scale);
+ },
+ settingsFile: "ffcniftya.json"
});
-
-// Load widgets
-Bangle.loadWidgets();
-Bangle.drawWidgets();
-
-// Show launcher when middle button pressed
-Bangle.setUI("clock");
\ No newline at end of file
+clock.start();
\ No newline at end of file
diff --git a/apps/ffcniftya/metadata.json b/apps/ffcniftya/metadata.json
index ce91cc225..015c56119 100644
--- a/apps/ffcniftya/metadata.json
+++ b/apps/ffcniftya/metadata.json
@@ -1,7 +1,7 @@
{
"id": "ffcniftya",
"name": "Nifty-A Clock",
- "version": "0.02",
+ "version": "0.04",
"description": "A nifty clock with time and date",
"icon": "app.png",
"screenshots": [{"url":"screenshot_nifty.png"}],
diff --git a/apps/ffcniftya/settings.js b/apps/ffcniftya/settings.js
index 46e4ef5aa..aec1d680a 100644
--- a/apps/ffcniftya/settings.js
+++ b/apps/ffcniftya/settings.js
@@ -1,23 +1,15 @@
-(function(back) {
- var FILE = "ffcniftya.json";
- // Load settings
- var cfg = require('Storage').readJSON(FILE, 1) || { showWeekNum: true };
+(function (back) {
+ const settings = Object.assign({ showWeekNum: true }, require("Storage").readJSON("ffcniftya.json", true));
- function writeSettings() {
- require('Storage').writeJSON(FILE, cfg);
- }
-
- // Show the menu
E.showMenu({
- "" : { "title" : "Nifty-A Clock" },
- "< Back" : () => back(),
- 'week number?': {
- value: cfg.showWeekNum,
- format: v => v?"On":"Off",
+ "": { "title": "Nifty-A Clock" },
+ "< Back": () => back(),
+ /*LANG*/"Show Week Number": {
+ value: settings.showWeekNum,
onchange: v => {
- cfg.showWeekNum = v;
- writeSettings();
+ settings.showWeekNum = v;
+ require("Storage").writeJSON("ffcniftya.json", settings);
}
}
});
-})
\ No newline at end of file
+})
diff --git a/apps/ffcniftyb/ChangeLog b/apps/ffcniftyb/ChangeLog
index dedd31452..83b11eb78 100644
--- a/apps/ffcniftyb/ChangeLog
+++ b/apps/ffcniftyb/ChangeLog
@@ -1,2 +1,6 @@
0.01: New Clock Nifty B
-0.02: Added configuration
\ No newline at end of file
+0.02: Added configuration
+0.03: Call setUI before loading widgets
+ Fix bug with black being unselectable
+ Improve settings page
+0.04: Use ClockFace library
diff --git a/apps/ffcniftyb/README.md b/apps/ffcniftyb/README.md
index e04243a0b..072f71cce 100644
--- a/apps/ffcniftyb/README.md
+++ b/apps/ffcniftyb/README.md
@@ -1,9 +1,6 @@
# Nifty Series B Clock
- Display Time and Date
-- Color Configuration
-
-##
+- Colour Configuration

-
diff --git a/apps/ffcniftyb/app.js b/apps/ffcniftyb/app.js
index 75d217ab4..540924fa5 100644
--- a/apps/ffcniftyb/app.js
+++ b/apps/ffcniftyb/app.js
@@ -1,118 +1,80 @@
-const locale = require("locale");
-const storage = require('Storage');
+var scale;
+var screen;
+var center;
+var buf;
+var img;
-const is12Hour = (storage.readJSON("setting.json", 1) || {})["12hour"];
-const color = (storage.readJSON("ffcniftyb.json", 1) || {})["color"] || 63488 /* red */;
-
-
-/* Clock *********************************************/
-const scale = g.getWidth() / 176;
-
-const screen = {
- width: g.getWidth(),
- height: g.getHeight() - 24,
-};
-
-const center = {
- x: screen.width / 2,
- y: screen.height / 2,
-};
-
-function d02(value) {
- return ('0' + value).substr(-2);
+function format(value) {
+ return ("0" + value).substr(-2);
}
function renderEllipse(g) {
g.fillEllipse(center.x - 5 * scale, center.y - 70 * scale, center.x + 160 * scale, center.y + 90 * scale);
}
-function renderText(g) {
- const now = new Date();
+function renderText(g, date) {
+ const hour = date.getHours() - (this.is12Hour && date.getHours() > 12 ? 12 : 0);
+ const month = date.getMonth() + 1;
- const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0));
- const minutes = d02(now.getMinutes());
- const day = d02(now.getDate());
- const month = d02(now.getMonth() + 1);
- const year = now.getFullYear();
-
- const month2 = locale.month(now, 3);
- const day2 = locale.dow(now, 3);
+ const monthName = require("date_utils").month(month, 1);
+ const dayName = require("date_utils").dow(date.getDay(), 1);
g.setFontAlign(1, 0).setFont("Vector", 90 * scale);
- g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale);
- g.drawString(minutes, center.x + 32 * scale, center.y + 46 * scale);
+ g.drawString(format(hour), center.x + 32 * scale, center.y - 31 * scale);
+ g.drawString(format(date.getMinutes()), center.x + 32 * scale, center.y + 46 * scale);
g.setFontAlign(1, 0).setFont("Vector", 16 * scale);
- g.drawString(year, center.x + 80 * scale, center.y - 42 * scale);
- g.drawString(month, center.x + 80 * scale, center.y - 26 * scale);
- g.drawString(day, center.x + 80 * scale, center.y - 10 * scale);
- g.drawString(month2, center.x + 80 * scale, center.y + 44 * scale);
- g.drawString(day2, center.x + 80 * scale, center.y + 60 * scale);
+ g.drawString(date.getFullYear(), center.x + 80 * scale, center.y - 42 * scale);
+ g.drawString(format(month), center.x + 80 * scale, center.y - 26 * scale);
+ g.drawString(format(date.getDate()), center.x + 80 * scale, center.y - 10 * scale);
+ g.drawString(monthName, center.x + 80 * scale, center.y + 44 * scale);
+ g.drawString(dayName, center.x + 80 * scale, center.y + 60 * scale);
}
-const buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, {
- msb: true
+const ClockFace = require("ClockFace");
+const clock = new ClockFace({
+ init: function () {
+ const appRect = Bangle.appRect;
+
+ screen = {
+ width: appRect.w,
+ height: appRect.h
+ };
+
+ center = {
+ x: screen.width / 2,
+ y: screen.height / 2
+ };
+
+ buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, { msb: true });
+
+ scale = g.getWidth() / screen.width;
+
+ img = {
+ width: screen.width,
+ height: screen.height,
+ transparent: 0,
+ bpp: 1,
+ buffer: buf.buffer
+ };
+
+ // default to RED (see settings.js)
+ // don't use || to default because 0 is a valid color
+ this.color = this.color === undefined ? 63488 : this.color;
+ },
+ draw: function (date) {
+ // render outside text with ellipse
+ buf.clear();
+ renderText(buf.setColor(1), date);
+ renderEllipse(buf.setColor(0));
+ g.setColor(this.color).drawImage(img, 0, 24);
+
+ // render ellipse with inside text
+ buf.clear();
+ renderEllipse(buf.setColor(1));
+ renderText(buf.setColor(0), date);
+ g.setColor(this.color).drawImage(img, 0, 24);
+ },
+ settingsFile: "ffcniftyb.json"
});
-
-function draw() {
-
- const img = {
- width: screen.width,
- height: screen.height,
- transparent: 0,
- bpp: 1,
- buffer: buf.buffer
- };
-
- // cleat screen area
- g.clearRect(0, 24, g.getWidth(), g.getHeight());
-
- // render outside text with ellipse
- buf.clear();
- renderText(buf.setColor(1));
- renderEllipse(buf.setColor(0));
- g.setColor(color).drawImage(img, 0, 24);
-
- // render ellipse with inside text
- buf.clear();
- renderEllipse(buf.setColor(1));
- renderText(buf.setColor(0));
- g.setColor(color).drawImage(img, 0, 24);
-}
-
-
-/* Minute Ticker *************************************/
-
-let ticker;
-
-function stopTick() {
- if (ticker) {
- clearTimeout(ticker);
- ticker = undefined;
- }
-}
-
-function startTick(run) {
- stopTick();
- run();
- ticker = setTimeout(() => startTick(run), 60000 - (Date.now() % 60000));
- // ticker = setTimeout(() => startTick(run), 3000);
-}
-
-/* Init **********************************************/
-
-g.clear();
-startTick(draw);
-
-Bangle.on('lcdPower', (on) => {
- if (on) {
- startTick(draw);
- } else {
- stopTick();
- }
-});
-
-Bangle.loadWidgets();
-Bangle.drawWidgets();
-
-Bangle.setUI("clock");
+clock.start();
\ No newline at end of file
diff --git a/apps/ffcniftyb/metadata.json b/apps/ffcniftyb/metadata.json
index 73f93ed36..019ae6eb3 100644
--- a/apps/ffcniftyb/metadata.json
+++ b/apps/ffcniftyb/metadata.json
@@ -1,8 +1,8 @@
{
"id": "ffcniftyb",
"name": "Nifty-B Clock",
- "version": "0.02",
- "description": "A nifty clock (series B) with time, date and color configuration",
+ "version": "0.04",
+ "description": "A nifty clock (series B) with time, date and colour configuration",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
diff --git a/apps/ffcniftyb/settings.js b/apps/ffcniftyb/settings.js
index 00abf80b5..da350edd8 100644
--- a/apps/ffcniftyb/settings.js
+++ b/apps/ffcniftyb/settings.js
@@ -1,49 +1,31 @@
(function (back) {
- const storage = require('Storage');
- const SETTINGS_FILE = "ffcniftyb.json";
+ const settings = Object.assign({ color: 63488 }, require("Storage").readJSON("ffcniftyb.json", true));
const colors = {
- 65535: 'White',
- 63488: 'Red',
- 65504: 'Yellow',
- 2047: 'Cyan',
- 2016: 'Green',
- 31: 'Blue',
- 0: 'Black',
+ 65535: /*LANG*/"White",
+ 63488: /*LANG*/"Red",
+ 65504: /*LANG*/"Yellow",
+ 2047: /*LANG*/"Cyan",
+ 2016: /*LANG*/"Green",
+ 31: /*LANG*/"Blue",
+ 0: /*LANG*/"Black"
}
- function load(settings) {
- return Object.assign(settings, storage.readJSON(SETTINGS_FILE, 1) || {});
- }
+ const menu = {};
+ menu[""] = { title: "Nifty-B Clock" };
+ menu["< Back"] = back;
- function save(settings) {
- storage.write(SETTINGS_FILE, settings)
- }
-
- const settings = load({
- color: 63488 /* red */,
+ Object.keys(colors).forEach(color => {
+ var label = colors[color];
+ menu[label] = {
+ value: settings.color == color,
+ onchange: () => {
+ settings.color = color;
+ require("Storage").write("ffcniftyb.json", settings);
+ setTimeout(load, 10);
+ }
+ };
});
- const saveColor = (color) => () => {
- settings.color = color;
- save(settings);
- back();
- };
-
- function showMenu(items, opt) {
- items[''] = opt || {};
- items['< Back'] = back;
- E.showMenu(items);
- }
-
- showMenu(
- Object.keys(colors).reduce((menu, color) => {
- menu[colors[color]] = saveColor(color);
- return menu;
- }, {}),
- {
- title: 'Color',
- selected: Object.keys(colors).indexOf(settings.color)
- }
- );
+ E.showMenu(menu);
});
diff --git a/apps/files/files.js b/apps/files/files.js
index e7b42c101..e81e9589f 100644
--- a/apps/files/files.js
+++ b/apps/files/files.js
@@ -1,7 +1,5 @@
const store = require('Storage');
-const boolFormat = (v) => v ? "On" : "Off";
-
function showMainMenu() {
const mainmenu = {
'': {
diff --git a/apps/flappy/ChangeLog b/apps/flappy/ChangeLog
index 349cb9d07..d660f85aa 100644
--- a/apps/flappy/ChangeLog
+++ b/apps/flappy/ChangeLog
@@ -2,3 +2,4 @@
0.03: A few tweaks to improve rendering speed
0.04: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast
0.05: Don't use Bangle.setLCDMode, just use offscreen buffer (allows widgets)
+0.06: Bangle.js 2 enhancements - remove offscreen buffer and render direct
diff --git a/apps/flappy/app.js b/apps/flappy/app.js
index e9ca31fa5..70553fe97 100644
--- a/apps/flappy/app.js
+++ b/apps/flappy/app.js
@@ -1,19 +1,20 @@
-b = Graphics.createArrayBuffer(120,120,8);
-var gimg = {
- width:120,
- height:104,
- bpp:8,
- buffer:b.buffer
- };
-
+var Y;
if (process.env.HWVERSION==2) {
- b.flip = function() {
- g.drawImage(gimg,28,50);
- };
+ // we have offscreen graphics, so just go direct
+ b = g;
+ Y = 24; // widgets
} else {
+ b = Graphics.createArrayBuffer(120,120,8);
+ var gimg = {
+ width:120,
+ height:104,
+ bpp:8,
+ buffer:b.buffer
+ };
b.flip = function() {
g.drawImage(gimg,0,24,{scale:2});
};
+ Y = 0; // we offset for widgets anyway
}
var BIRDIMG = E.toArrayBuffer(atob("EQyI/v7+/v7+/gAAAAAAAP7+/v7+/v7+/gYG0tLS0gDXAP7+/v7+/v4A0tLS0tIA19fXAP7+/v4AAAAA0tLS0gDX1wDXAP7+ANfX19cA0tLSANfXANcA/v4A19fX19cA0tLSANfX1wD+/gDS19fX0gDS0tLSAAAAAAD+/gDS0tIA0tLS0gDAwMDAwAD+/gAAAM3Nzc0AwAAAAAAA/v7+/v4Azc3Nzc0AwMDAwAD+/v7+/v4AAM3Nzc0AAAAAAP7+/v7+/v7+AAAAAP7+/v7+/g=="))
@@ -30,14 +31,14 @@ function newBarrier(x) {
barriers.push({
x1 : x-7,
x2 : x+7,
- y : 20+Math.random()*38,
+ y : Y+20+Math.random()*38,
gap : 12+Math.random()*15
});
}
function gameStart() {
running = true;
- birdy = 48/2;
+ birdy = Y + 48/2;
birdvy = 0;
barriers = [];
for (var i=38;ibbot))
gameStop();
});
diff --git a/apps/flappy/metadata.json b/apps/flappy/metadata.json
index 910797066..cb50f0094 100644
--- a/apps/flappy/metadata.json
+++ b/apps/flappy/metadata.json
@@ -1,7 +1,7 @@
{
"id": "flappy",
"name": "Flappy Bird",
- "version": "0.05",
+ "version": "0.06",
"description": "A Flappy Bird game clone",
"icon": "app.png",
"screenshots": [{"url":"screenshot1_flappy.png"},{"url":"screenshot2_flappy.png"}],
diff --git a/apps/football/ChangeLog b/apps/football/ChangeLog
new file mode 100644
index 000000000..66b9882cc
--- /dev/null
+++ b/apps/football/ChangeLog
@@ -0,0 +1,2 @@
+1.00: Initial implementation
+1.01: Bug fixes and performance and visual improvements
diff --git a/apps/football/README.md b/apps/football/README.md
new file mode 100644
index 000000000..f751b927e
--- /dev/null
+++ b/apps/football/README.md
@@ -0,0 +1,3 @@
+# Classic Football Chronometer Game
+
+Context: https://www.anaitgames.com/analisis/analisis-casio-football-14
diff --git a/apps/football/app-icon.js b/apps/football/app-icon.js
new file mode 100644
index 000000000..7eec578c6
--- /dev/null
+++ b/apps/football/app-icon.js
@@ -0,0 +1 @@
+atob("MDABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAA/wAAAAAA/wAAAAAA/wAAAAAA/wAAAAAA/wAAAAAA/wAAAAAA/wAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAA//AAAAAA//AAAAAA//AAAAAA//AAAAAADw8AD/AADw8AD/AADw8AD/AADw8AD/AP/wAAD/AP/wAAD/AP/wAAD/AP/wAAD/AA8PAAAAAA8PAAAAAA8PAAAAAA8PAAAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
diff --git a/apps/football/app.js b/apps/football/app.js
new file mode 100644
index 000000000..d12f07e2b
--- /dev/null
+++ b/apps/football/app.js
@@ -0,0 +1,474 @@
+// globals. TODO: move into an object
+const digit = [];
+let part = 0;
+let endInc = 0;
+var endGame = false;
+let goalFrame = 0;
+var stopped = true;
+let score0 = 0;
+let score1 = 0;
+let inc = 0;
+let msinc = 0;
+let seq0 = 0;
+let seq1 = 0;
+let goaler = -1;
+const w = g.getWidth();
+let owner = -1;
+
+const dash = {
+ width: 75,
+ height: 128,
+ bpp: 1,
+ buffer: require('heatshrink').decompress(atob('AH4A/AH4A/AH4A/AH4A/AB0D/4AB+AJEBAX/BAk/CQ8PCQ4kDCQoIDCQgkDCQgkECQgIE4ASHFxH8JRgSEEgYSEPJAkEAH4A/AH4A/AH4A/AH4A/ACg='))
+};
+
+function loadDigits () {
+ digit[0] = {
+ width: 80,
+ height: 128,
+ bpp: 1,
+ buffer: require('heatshrink').decompress(atob('AH4AGn//AAngBIMfBIvABIMPBIuABIMHBIoIBg0DBAn+gYSBgIJE/kHBIOABIn4h4JB4F/BIfwj4JB8BQEAoIJBBoJOEv4JBEIJOEIwMHGoIJDIIIJBJIJOEBIQOCJwYJDOIR9DBISFCSIYJCTISlDBIXwBIZoBBP4J/BP4J/BNX+gED//gBIc/BIMB//ABIcf/gDB/+ABIcP/AhCBAYuBFoU+BIkDFoUcBIkBFoUIBIkAFogA/AAZPJMZJ3JRZKfIWZLHJbZL5bBP4J/BP4J/BKPgBIc/BIfABIcfBIeA/4AB/EPBIcBBIX8AwIJB/0DBIQECBIIOCAAQYBBIIiCAAQsBBIPwGwIAC4F/BIPgJQIACAoIJBBoIJDDIIJBJwZQDBIJODKAcAgxODKAZxBJwgABPYROEKASFDAAiRCJwhQCTYYAkA'))
+ };
+
+ digit[1] = {
+ width: 80,
+ height: 128,
+ bpp: 1,
+ buffer: require('heatshrink').decompress(atob('AH4A2wAIHgIJIgYJIg4JIh4JIj4JIn4JIv4JI/4JHgIJIgYJIg4JIh4JIj4JIn4J/BP4J/BP4Jqj//BA0Ah/+BI8H/gJHgf4BI8B+AJHgHgBJFABJAA/J55jIO5KLJT5KzJY5L5nBP4J/BP4J/AAcfBJEPBJEHBJEDBJEBBJEABJN/BJE/BJEfBJEPBJEHBJEDBJEBBJEABJIA/AAwA='))
+ };
+
+ digit[2] = {
+ width: 80,
+ height: 128,
+ bpp: 1,
+ buffer: require('heatshrink').decompress(atob('AH4AGj//AAnwBIMPBIvgBIMHBIvABIMDBIuABIMBBIsBGQQIE/0DBIV/BIf8g4JCn4JD/EPKA/wj4JCKAngn4JCKAnAv4JCKAmA/4JCKAgEBQYZOEBIgADFgIJHIAKlJBI5oBBP4J/BP4J/BOcfBJEP/wJHg/8Aof/AAP+gf4BAUBBIX/gPwBIUDBIeA8AiDBIfAoA2DBIYSDJQQACEwZeCAAQ6DgF/BJATJE5I7IghPFBIUOMYomDO4g6EwCLDJwgiDAAhyFTohKEToheEBP4J/BP4J/BOHwBJHgBJHAv////8BImABAP//wJEAIIACBIf+BImABIX8g4JD4AJC/EPBIZACgfwj4JDKgUD8E/BIZoCgZODKAkDJwZQEgcBBIhQCgROEKAhOEKAhOEKAhOEKAgAm'))
+ };
+
+ digit[3] = {
+ width: 80,
+ height: 128,
+ bpp: 1,
+ buffer: require('heatshrink').decompress(atob('AH4AGj//AAnwBIMPBIvgBIMHBIvABIMDBIuABIMBBIsBGQQIE/0DBIV/BIf8g4JCn4JD/EPKA/wj4JCKAngn4JCKAnAv4JCKAmA/4JCKAgEBQYZOEBIgADFgIJHIAKlJBI5oBBI58BBP4J/BP4J/BL8/BJEf/wJHh/8BI8H/AFD/4AB/0D+AICgIJDgPgBIUDBIQ5B4AiDBIeAwA2DBIYSDJQIJDEwZeCAAQ6DOQQACJwgTJE5I7JJ5JjEgIUDO4kDFAgJC/kDIwipNj4JIn7HIbZL5TBP4J/BP4J/BJs/BJEfBJEPBIgjB//8g4JDgIIB//+gYJDAgIACBwIJCDAIACwAJDFgIAC4F/IAgAC8E/KggAC+EfIgoAB/EPBIQIDKAROFKAZOGKAROGKAQJI4BOGKAQJI+CfHAEAA=='))
+ };
+ digit[4] = {
+ width: 80,
+ height: 128,
+ bpp: 1,
+ buffer: require('heatshrink').decompress(atob('AH4AswEBBJOABAoHBgPABIsDBIPgBIsHBIPwBIsPBIP4BIsfBIP8BIs/BIP+BIt/BIP/BIv/BIRQEAwQCBKAkDBIZQEg4JDKAkPBIZQEj4JIn4J/BP4J/BP4JjgAJFj//AYN/8AJDh/+C4QJEg/8C4XAv////+gYjCh+ABAIABgPwC4Q9BAAWAEYUCgYJD4FAFgYJDIAoJDEwRUDAARoGAAROCCZYnJHZJPJMZAABO46hCRYwAFT4YAFWYYAFY4YAFbYYJ/BP4J/BP4Jnj4JIh4JIg4JIgYJIgIJIgAJJv4JIn4JIj4JIh4JIg4JIgYJIgIJIgAJJAH4AGA='))
+ };
+
+ digit[5] = {
+ width: 80,
+ height: 128,
+ bpp: 1,
+ buffer: require('heatshrink').decompress(atob('AGUP/4AE/gJBg4JF/AJBgYJF+AJBgIJF8AJBwAJF4AJB4F/BImABIPgn4JEIoXwj4ID/wJC/BQEJwUA/hQEJwUA/xQEJwUA/5QEJwQJBKAhOCBIJQEJwQJBKAiVDFggAEIAgJFKgYJFNAYJ/BP4J/BP4Jmv/8BI8//AJHj/wBI8P8E//4ABBIcH4F/BIWABIUDwAIC//ABIUBgIJD8AeDgYJDGwkHBIZKEh4JDLwkfBIZyECZInJHZJPFkChEMYdwUIh3DFAiLDgKvIgbDIJQKvIUIgJFUIZ8FBP4J/BP4J/BL8PBJEHBJEDBJEBBIl//4ABwAJEBAQHBv4JCDAIAC8E/BIQsBAAXwj4JCIAIAC/EPBIRUBAAX8g4JCNAIAC/0DBI//gIJCJwZQCBIQIEKANAJwpQCJwxQCJwxQCJwxQCBJYAwA='))
+ };
+
+ digit[6] = {
+ width: 80,
+ height: 128,
+ bpp: 1,
+ buffer: require('heatshrink').decompress(atob('AGU//4AE8AJBj4JF4AJBh4JFwAJBg4JFBAMGgYIE/wSCgIJE/gJCwAJE/AJC4F/BIfwBIXgKAhOCg/wKAhOCg/4KAhOD/hQEOwUH/xQDJwRiCKAZOCBIRQDJwQJCGwQAEBIJKCBIxeCBP4J/BP4J/BOED//gBI0B//ABI0A/+ABI9/CoIAB/gJDnwpBAAP+BIccHoIACBIcIh4JDFgkfBIZAEBIhUEv4JDNAgJE/ATNn4nIHZBPGKARjFgIUCO4sDFASLFg48COQsPKARyGUILHGn6hBBIJyGco4J/BP4J/BP4Jm8AJDn4JD4AJDj4JDwAJDh4JDgP/AAP8AwIJB/0DBIQECBIIOCAAQYBBIP4EQIACwAJC+A2BAAXAv4JB8BKBAAQFBBIINBBIYZBBIIhBAAYtBBIJODKAcAgxODKAZnBJwhQCOIYAEPoROEKASZDAAilCJwhQCTYYAuA=='))
+ };
+
+ digit[7] = {
+ width: 80,
+ height: 128,
+ bpp: 4,
+ buffer : require("heatshrink").decompress(atob("AH4A/AEtVADdQE5Nf/4AayAnJgoma/J4LKDR2KKDZODUMadChKhiJwefE5RQXJwbxLKCxOEE5hQVJwgnNKCZOFE5pQTJwonOKCJOGE5xQRD4Q8EE5xQPJw4nPgFZzIAMqCdFE6IARJwgnhJwonhJwonhe5In/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4nlr4mE/NQE78FE4n1Ez5QGE0JQEJ0RQETsBQFJ0gABrJOkAH4A/AH4A/ADNZqAmkgv/yAnkr///JQjJwIABypOkAAP5J0oABUMJODKAShgEwhQiE/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/AA+fE80JE8xQGE8JQFE8JQFE8RQEE8RQEE8ZQDE8ZQDE8hQCE8hQCE8pQBE8pQBE80JE80AE84A/AH4A/AH4A/AAQA=="))
+ };
+
+ digit[8] = {
+ width: 80,
+ height: 128,
+ bpp: 1,
+ buffer: require('heatshrink').decompress(atob('AGUf/4AE+AJBh4JF8AJBg4JF4AJBgYJFwAIBgIJFgOAgeABAn+A4MD4F/BIf8g4JB8E/BIf4h4JB+BQEAoIJBBoJOEn4JBEIJOEv4JBGoJOEAIIHBKAgEBBIRQDDAQJCKAYsCBISFCSIYJCTISlDBIX4BIZoBBP4J/BP4J/BNkB//wBIcf/4DB//gBIcP/wDBv/ABIcH/ghCwH/AAP+gYtCj4pBAAUBFoUOHoIACwAtCgkHBIfAoA2DBIZAEJQIACKgheBAARoGBKInJHZBPJMZJ3JRZKfJWZDHJfM4J/BP4J/BP4JP+AJDj4JD8AJDh4JD4AJDg4JDwH/AAP+AwQCBgIJCAgQJBBwQACDAIJB/giBAAXAv4JB/A2BAAXgn4JB+BKBAAQFBBIINBBIYZBBIIhBBIYtBBIJODKAYJBJwhQCwECJwhQCwBxCAAh9CJwhQCTIYAEUoROEKASbDAFwA='))
+ };
+
+ digit[9] = {
+ width: 80,
+ height: 128,
+ bpp: 1,
+ buffer: require('heatshrink').decompress(atob('AGUP/4AE/gJBg4JF/AJBgYJF+AJBgIJF8AJBwAJF4FAgHAv4JEwHAgHgn4JEgIJB+EfBAf+gYJB/BQE/kHBIIDBJwkPBIIXBJwkfBIIrBJwk/BIRQEJYIJCKAgOBBIXgIwYiBBIR7CQ4YJCR4SbDBISjCV4YJC/wJDFYIJ/BP4J/BP4Jjv/8BIcP/+AgE//AJDg//C4XwBIcDEYUP8E//4ABgIjCg/Av4JCwAjCgeABAQ5BuAJBgMBBIfgkAsDBIY2EIAIACJQhUBAAReENAIACOQgTJE5I7JJ5KhBMYwABO44ABRY4AFT4YAFWYYAFY4YAFbYYJ/BP4J/BP4Jnh4JIg4JIgYJIgIJEv//AAOABIgICA4N/BIQYBAAXgn4JCFgIAC+EfBIRABAAX4h4JCKgIAC/kHBIRoBAAX+gYJCn4JD/8BBIRODKAQJCBAhQBoBOFKAROGKAROGKAROGKAQJLAGA='))
+ };
+}
+
+// sprites
+
+const left0 = {
+ width: 8,
+ height: 10,
+ bpp: 1,
+ transparent: 0,
+ buffer: atob('ADAwEDgUcCgEAA==')
+};
+
+const left1 = {
+ width: 8,
+ height: 10,
+ bpp: 1,
+ transparent: 0,
+ buffer: atob('ADAwEDh0ECAoAA==')
+};
+
+const left2 = {
+ width: 8,
+ height: 10,
+ bpp: 1,
+ transparent: 0,
+ buffer: atob('ADAwEBg4EBg0AA==')
+};
+
+const left4 = {
+ width: 8,
+ height: 10,
+ bpp: 1,
+ transparent: 0,
+ buffer: atob('ABgYVDgQEChEAA==')
+};
+
+const right0 = {
+ width: 8,
+ height: 10,
+ bpp: 1,
+ transparent: 0,
+ buffer: atob('AAwMCBwoDhQgAA==')
+};
+
+const right1 = {
+ width: 8,
+ height: 10,
+ bpp: 1,
+ transparent: 0,
+ buffer: atob('AAwMCBwuCAQUAA==')
+};
+
+const right2 = {
+ width: 8,
+ height: 10,
+ bpp: 1,
+ transparent: 0,
+ buffer: atob('AAwMCBgcCBgsAA==')
+};
+
+const right4 = left4;
+
+const ball0 = {
+ width: 8,
+ height: 10,
+ bpp: 1,
+ transparent: 0,
+ buffer: atob('AAAAAAAwMAAAAA==')
+};
+
+const ball1 = {
+ width: 8,
+ height: 10,
+ bpp: 1,
+ transparent: 0,
+ buffer: atob('AAAAAAAAGBgAAA==')
+};
+const gol01 = {
+ width: 8,
+ height: 10,
+ bpp: 1,
+ transparent: 0,
+ buffer: atob('ABhkhIS0sIAAAA==')
+};
+
+const gol11 = {
+ width: 8,
+ height: 10,
+ bpp: 1,
+ buffer: require('heatshrink').decompress(atob('gEYk0hkMthsBBAI='))
+
+};
+
+loadDigits();
+
+
+function printNumber (n, x, y, options) {
+ if (n > 9 || n < -1) {
+ console.log(n);
+ return; // error
+ }
+ if (digit.length === 0) {
+ g.setColor(1, 1, 1);
+ if (options.scale == 0.2) {
+ g.setFont12x20(1);
+ } else {
+ g.setFont12x20(2.5);
+ }
+ g.drawString('' + n, x, y);
+ return;
+ }
+ const img = (n == -1) ? dash : digit[n];
+ if (img) {
+ // g.setColor(0,0,0);
+ // g.fillRect(x,y,x+32*options.scale,64*options.scale);
+ g.setColor(1, 1, 1);
+ g.drawImage(img, x, y, options);
+ }
+}
+
+g.setBgColor(0, 0, 0);
+g.clear();
+g.setColor(1, 1, 1);
+function onStop () {
+ if (goalFrame > 0) {
+ return;
+ }
+ stopped = !stopped;
+ if (stopped) {
+ // Bangle.beep();
+ if (msinc == 0) {
+ // GOOL
+ if (owner == 0) {
+ score0++;
+ goaler = 0;
+ } else if (owner == 1) {
+ score1++;
+ goaler = 1;
+ }
+ goalFrame = 5;
+ }
+ let newOwner = 0;
+ if (msinc % 2) {
+ newOwner = 1;
+ } else {
+ newOwner = 0;
+ }
+ if (newOwner) {
+ seq0--;
+ seq1++;
+ } else {
+ seq0++;
+ seq1--;
+ }
+ if (seq0 < 0) seq0 = 0;
+ if (seq0 > 3) seq0 = 3;
+ if (seq1 < 0) seq1 = 0;
+ if (seq1 > 3) seq1 = 3;
+ owner = newOwner;
+ }
+ refresh();
+ refresh_ms();
+}
+
+function onButtonPress() {
+ console.log('on.tap');
+ setWatch(() => {
+ onButtonPress();
+}, BTN1);
+ Bangle.beep();
+ if (endGame) {
+ score0 = 0;
+ score1 = 0;
+ seq0 = 0;
+ seq1 = 0;
+ part = 0;
+ inc = 0;
+ msinc = 0;
+ stopped = true;
+ endGame = false;
+ } else {
+ if (inc == 0) {
+ // autogame();
+ stopped = false;
+ } else {
+ onStop();
+ }
+ }
+}
+
+setWatch(() => {
+ onButtonPress();
+}, BTN1);
+/*Bangle.on('tap', function () {
+ onButtonPress();
+});
+*/
+g.setFont12x20(3);
+
+function refresh () {
+ g.clear();
+ if (inc > 59) {
+ inc = 0;
+ part++;
+ }
+ if (part >= 2 && inc > 30) {
+ part = 2;
+ Bangle.buzz();
+ endGame = true;
+ endInc = inc;
+ inc = 0;
+ }
+ if (inc > 44) {
+ inc = 0;
+ if (part < 2) {
+ part++;
+ }
+ if (part >= 2) {
+ if (score0 != score1) {
+ Bangle.buzz();
+ endGame = true;
+ endInc = inc;
+ inc = 0;
+ }
+ }
+ // end of 1st or 2nd part of the game?
+ }
+ let two = (inc < 10) ? '0' + inc : '' + inc;
+ if (endGame) {
+ g.setColor(0, 0, 0);
+ g.fillRect(0, 64, w, h);
+ if (inc % 2) {
+ two = (endInc < 10) ? '0' + endInc : '' + endInc;
+ printNumber(-1, 2, 64 + 16, { scale: 0.4 });
+ printNumber(part, 34, 64 + 16, { scale: 0.4 });
+ printNumber(two[0], 74, 64 + 16, { scale: 0.4 });
+ printNumber(two[1], 74 + 32, 64 + 16, { scale: 0.4 });
+ }
+ } else {
+ // seconds
+ printNumber(0, 2, 64 + 16, { scale: 0.4 });
+ printNumber(part, 34, 64 + 16, { scale: 0.4 });
+ printNumber(two[0], 74, 64 + 16, { scale: 0.4 });
+ printNumber(two[1], 74 + 32, 64 + 16, { scale: 0.4 });
+ }
+ refresh_ms();
+ refresh_score();
+ refresh_pixels();
+}
+
+function refresh_pixels () {
+ let frame4 = inc % 2;
+ if (goalFrame > 0) {
+ frame4 = goalFrame % 2;
+ if (goaler == 0) {
+ g.drawImage(frame4 ? right4 : right0, 20, 10, { scale: 5 });
+ g.setColor(1, 1, 1);
+ g.drawImage(gol11, w - 50, 10, { scale: 5 });
+ } else if (goaler == 1) {
+ g.drawImage(frame4 ? left0 : left4, w - 50, 10, { scale: 5 });
+ g.setColor(1, 1, 1);
+ g.drawImage(gol01, 30, 10, { scale: 5 });
+ }
+ return;
+ }
+ if (endGame) {
+ if (score0 > score1) {
+ g.drawImage(frame4 ? right1 : right0, 5 + (seq0 * 10), 10, { scale: 5 });
+ } else if (score0 < score1) {
+ g.drawImage(frame4 ? left0 : left1, w - 30 - (seq1 * 10), 10, { scale: 5 });
+ }
+ return;
+ }
+ g.drawImage(frame4 ? right1 : right0, 5 + (seq0 * 10), 10, { scale: 5 });
+ g.drawImage(frame4 ? left0 : left1, w - 30 - (seq1 * 10), 10, { scale: 5 });
+ let bx = (owner == 0) ? w / 3 : w / 2;
+ bx += 2;
+ g.drawImage(frame4 ? ball0 : ball1, bx, 10, { scale: 5 });
+ const liney = 60;
+ if (owner) {
+ g.drawLine(w-8, liney, 2*(w/3), liney);
+ } else {
+ g.drawLine(8, liney, w/3, liney);
+ }
+}
+let dots = 0;
+function refresh_dots () {
+ if (endGame) {
+ dots = 0;
+ } else {
+ dots++;
+ }
+ if (dots % 2) {
+ g.setColor(1, 1, 1);
+ } else {
+ g.setColor(0, 0, 0);
+ }
+ const x = 67;
+ let y = 100;
+ g.fillRect(x, y, x + 4, y + 4);
+ y += 16;
+ g.fillRect(x, y, x + 4, y + 4);
+}
+
+const h = g.getHeight();
+
+function refresh_ms () {
+ if (endGame) {
+ if (inc % 2) {
+ printNumber(-1, 80 + 64 - 4, 64 + 16 + 8 + 16, { scale: 0.2 });
+ printNumber(-1, 80 + 64 + 16 - 4, 64 + 32 + 8, { scale: 0.2 });
+ }
+ return;
+ }
+ // nanoseconds
+ if (msinc > 59) {
+ msinc = 0;
+ }
+ g.setColor(0, 0, 0);
+ g.fillRect(80 + 64, h / 2, 80 + 64 + 32, g.getHeight());
+ const two = (msinc < 10) ? '0' + msinc : '' + msinc;
+ printNumber(two[0], 80 + 64 - 4, 64 + 16 + 8 + 16, { scale: 0.2 });
+ printNumber(two[1], 80 + 64 + 16 - 4, 64 + 32 + 8, { scale: 0.2 });
+}
+
+function refresh_score () {
+ g.setColor(0, 0, 0);
+ g.fillRect(0, h - 32, w, h);
+ let two = (score0 < 10) ? '0' + score0 : '' + score0;
+ printNumber(two[0], 64 - 16, 32 + 64 + 16 + 8 + 16, { scale: 0.2 });
+ printNumber(two[1], 64, 32 + 64 + 32 + 8, { scale: 0.2 });
+ two = (score1 < 10) ? '0' + score1 : '' + score1;
+ printNumber(two[0], 32 + 64, 32 + 64 + 16 + 8 + 16, { scale: 0.2 });
+ printNumber(two[1], 32 + 64 + 16, 32 + 64 + 32 + 8, { scale: 0.2 });
+}
+refresh();
+
+setInterval(function () {
+ if (!stopped || endGame) {
+ inc++;
+ }
+ if (goalFrame > 0) {
+ goalFrame--;
+ }
+ refresh();
+}, 1000);
+
+setInterval(function () {
+ refresh_dots();
+}, 500);
+
+setInterval(function () {
+ if (!stopped) {
+ msinc++;
+ if (msinc > 59) {
+ msinc = 0;
+ }
+ }
+}, 10);
+
+setInterval(function () {
+ if (!stopped) {
+ refresh_ms();
+ }
+}, 250);
+
+function autogame () {
+ if (endGame) {
+ return;
+ }
+ onStop();
+ if (stopped) {
+ setTimeout(autogame, 500);
+ } else {
+ setTimeout(autogame, 315 + 10 * (Math.random() * 5));
+ }
+}
+
+Bangle.setOptions({ lockTimeout: 0, backlightTimeout: 0 });
+// autogame();
+
diff --git a/apps/football/app.png b/apps/football/app.png
new file mode 100644
index 000000000..80d7cea15
Binary files /dev/null and b/apps/football/app.png differ
diff --git a/apps/football/media/ball0.png b/apps/football/media/ball0.png
new file mode 100644
index 000000000..5b890c180
Binary files /dev/null and b/apps/football/media/ball0.png differ
diff --git a/apps/football/media/ball1.png b/apps/football/media/ball1.png
new file mode 100644
index 000000000..c72e56189
Binary files /dev/null and b/apps/football/media/ball1.png differ
diff --git a/apps/football/media/dash.png b/apps/football/media/dash.png
new file mode 100644
index 000000000..6a9b0c4ac
Binary files /dev/null and b/apps/football/media/dash.png differ
diff --git a/apps/football/media/digit0.png b/apps/football/media/digit0.png
new file mode 100644
index 000000000..33856cc5e
Binary files /dev/null and b/apps/football/media/digit0.png differ
diff --git a/apps/football/media/digit1.png b/apps/football/media/digit1.png
new file mode 100644
index 000000000..53b914ded
Binary files /dev/null and b/apps/football/media/digit1.png differ
diff --git a/apps/football/media/digit2.png b/apps/football/media/digit2.png
new file mode 100644
index 000000000..7a7787b05
Binary files /dev/null and b/apps/football/media/digit2.png differ
diff --git a/apps/football/media/digit3.png b/apps/football/media/digit3.png
new file mode 100644
index 000000000..a197d5993
Binary files /dev/null and b/apps/football/media/digit3.png differ
diff --git a/apps/football/media/digit4.png b/apps/football/media/digit4.png
new file mode 100644
index 000000000..f2810a0b6
Binary files /dev/null and b/apps/football/media/digit4.png differ
diff --git a/apps/football/media/digit5.png b/apps/football/media/digit5.png
new file mode 100644
index 000000000..d8027c362
Binary files /dev/null and b/apps/football/media/digit5.png differ
diff --git a/apps/football/media/digit6.png b/apps/football/media/digit6.png
new file mode 100644
index 000000000..bd7980045
Binary files /dev/null and b/apps/football/media/digit6.png differ
diff --git a/apps/football/media/digit7.png b/apps/football/media/digit7.png
new file mode 100644
index 000000000..9ef0df11a
Binary files /dev/null and b/apps/football/media/digit7.png differ
diff --git a/apps/football/media/digit8.png b/apps/football/media/digit8.png
new file mode 100644
index 000000000..6916a301a
Binary files /dev/null and b/apps/football/media/digit8.png differ
diff --git a/apps/football/media/digit9.png b/apps/football/media/digit9.png
new file mode 100644
index 000000000..d8d327523
Binary files /dev/null and b/apps/football/media/digit9.png differ
diff --git a/apps/football/media/digits.png b/apps/football/media/digits.png
new file mode 100644
index 000000000..68ace56af
Binary files /dev/null and b/apps/football/media/digits.png differ
diff --git a/apps/football/media/digits128.png b/apps/football/media/digits128.png
new file mode 100644
index 000000000..f363a7e8e
Binary files /dev/null and b/apps/football/media/digits128.png differ
diff --git a/apps/football/media/digits64.png b/apps/football/media/digits64.png
new file mode 100644
index 000000000..445c14dfa
Binary files /dev/null and b/apps/football/media/digits64.png differ
diff --git a/apps/football/media/gol00.png b/apps/football/media/gol00.png
new file mode 100644
index 000000000..3b16aa967
Binary files /dev/null and b/apps/football/media/gol00.png differ
diff --git a/apps/football/media/gol01.png b/apps/football/media/gol01.png
new file mode 100644
index 000000000..3b16aa967
Binary files /dev/null and b/apps/football/media/gol01.png differ
diff --git a/apps/football/media/gol10.png b/apps/football/media/gol10.png
new file mode 100644
index 000000000..178b6fe3d
Binary files /dev/null and b/apps/football/media/gol10.png differ
diff --git a/apps/football/media/gol11.png b/apps/football/media/gol11.png
new file mode 100644
index 000000000..732fa815d
Binary files /dev/null and b/apps/football/media/gol11.png differ
diff --git a/apps/football/media/left0.png b/apps/football/media/left0.png
new file mode 100644
index 000000000..20599cbb7
Binary files /dev/null and b/apps/football/media/left0.png differ
diff --git a/apps/football/media/left1.png b/apps/football/media/left1.png
new file mode 100644
index 000000000..b6ffc22f9
Binary files /dev/null and b/apps/football/media/left1.png differ
diff --git a/apps/football/media/left2.png b/apps/football/media/left2.png
new file mode 100644
index 000000000..11ff8885f
Binary files /dev/null and b/apps/football/media/left2.png differ
diff --git a/apps/football/media/left4.png b/apps/football/media/left4.png
new file mode 100644
index 000000000..a7301a4f8
Binary files /dev/null and b/apps/football/media/left4.png differ
diff --git a/apps/football/media/right0.png b/apps/football/media/right0.png
new file mode 100644
index 000000000..ac418ad7b
Binary files /dev/null and b/apps/football/media/right0.png differ
diff --git a/apps/football/media/right1.png b/apps/football/media/right1.png
new file mode 100644
index 000000000..31554cbc3
Binary files /dev/null and b/apps/football/media/right1.png differ
diff --git a/apps/football/media/right2.png b/apps/football/media/right2.png
new file mode 100644
index 000000000..8c8d0ece4
Binary files /dev/null and b/apps/football/media/right2.png differ
diff --git a/apps/football/media/right4.png b/apps/football/media/right4.png
new file mode 100644
index 000000000..83a78b52c
Binary files /dev/null and b/apps/football/media/right4.png differ
diff --git a/apps/football/metadata.json b/apps/football/metadata.json
new file mode 100644
index 000000000..43e7ac1bf
--- /dev/null
+++ b/apps/football/metadata.json
@@ -0,0 +1,31 @@
+{
+ "id": "football",
+ "name": "football",
+ "shortName": "football",
+ "version": "1.01",
+ "type": "app",
+ "description": "Classic football game of the CASIO chronometer",
+ "icon": "app.png",
+ "allow_emulator": true,
+ "tags": "games",
+ "supports": [
+ "BANGLEJS2"
+ ],
+ "readme": "README.md",
+ "storage": [
+ {
+ "name": "football.app.js",
+ "url": "app.js"
+ },
+ {
+ "name": "football.img",
+ "url": "app-icon.js",
+ "evaluate": true
+ }
+ ],
+ "screenshots": [
+ {
+ "url": "screenshot.png"
+ }
+ ]
+}
diff --git a/apps/football/screenshot.png b/apps/football/screenshot.png
new file mode 100644
index 000000000..5742fe9e1
Binary files /dev/null and b/apps/football/screenshot.png differ
diff --git a/apps/ftclock/ChangeLog b/apps/ftclock/ChangeLog
index 83ec21ee6..c30dae69f 100644
--- a/apps/ftclock/ChangeLog
+++ b/apps/ftclock/ChangeLog
@@ -1,3 +1,4 @@
0.01: first release
0.02: RAM efficient version of `fourTwentyTz.js` (as suggested by @gfwilliams).
0.03: `mkFourTwentyTz.js` now handles new timezonedb.com CSV format
+0.04: Tell clock widgets to hide.
diff --git a/apps/ftclock/app.js b/apps/ftclock/app.js
index b12db10f1..4f2cef895 100644
--- a/apps/ftclock/app.js
+++ b/apps/ftclock/app.js
@@ -33,6 +33,8 @@ function draw() {
// Clear the screen once, at startup
g.clear();
+// Show launcher when middle button pressed
+Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
@@ -47,5 +49,4 @@ Bangle.on('lcdPower',on=>{
drawTimeout = undefined;
}
});
-// Show launcher when middle button pressed
-Bangle.setUI("clock");
+
diff --git a/apps/ftclock/metadata.json b/apps/ftclock/metadata.json
index 876feb1bb..96a4f84b9 100644
--- a/apps/ftclock/metadata.json
+++ b/apps/ftclock/metadata.json
@@ -1,7 +1,7 @@
{
"id": "ftclock",
"name": "Four Twenty Clock",
- "version": "0.03",
+ "version": "0.04",
"description": "A clock that tells when and where it's going to be 4:20 next",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot1.png"}],
diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html
index b3cd7e12d..8ef117933 100644
--- a/apps/fwupdate/custom.html
+++ b/apps/fwupdate/custom.html
@@ -81,6 +81,9 @@ function onInit(device) {
if (crc==3435933210) version = "2v11.52";
if (crc==46757280) version = "2v11.58";
if (crc==3508163280 || crc==1418074094) version = "2v12";
+ if (crc==4056371285) version = "2v13";
+ if (crc==1038322422) version = "2v14";
+ if (crc==2560806221) version = "2v15";
if (!ok) {
version += `(⚠ update required)`;
}
diff --git a/apps/gallifr/ChangeLog b/apps/gallifr/ChangeLog
index 0e1f45042..32c1057b0 100644
--- a/apps/gallifr/ChangeLog
+++ b/apps/gallifr/ChangeLog
@@ -1,2 +1,3 @@
0.01: First released version
0.02: Changed setWatch to Bangle.setUI
+0.03: Tell clock widgets to hide.
diff --git a/apps/gallifr/app.js b/apps/gallifr/app.js
index d327bcdc1..8468eee48 100644
--- a/apps/gallifr/app.js
+++ b/apps/gallifr/app.js
@@ -238,10 +238,12 @@ Bangle.on('lcdPower', (on) => {
}
});
+// Show launcher when button pressed
+Bangle.setUI("clock");
+
g.clear();
startTimers();
Bangle.loadWidgets();
drawAll();
-// Show launcher when button pressed
-Bangle.setUI("clock");
+
diff --git a/apps/gallifr/metadata.json b/apps/gallifr/metadata.json
index 9ce7d7f97..96fe243ed 100644
--- a/apps/gallifr/metadata.json
+++ b/apps/gallifr/metadata.json
@@ -2,7 +2,7 @@
"id": "gallifr",
"name": "Time Traveller's Chronometer",
"shortName": "Time Travel Clock",
- "version": "0.02",
+ "version": "0.03",
"description": "A clock for time travellers. The light pie segment shows the minutes, the black circle, the hour. The dial itself reads 'time' just in case you forget.",
"icon": "gallifr.png",
"screenshots": [{"url":"screenshot_time.png"}],
diff --git a/apps/game1024/ChangeLog b/apps/game1024/ChangeLog
index 29838413e..df36b6456 100644
--- a/apps/game1024/ChangeLog
+++ b/apps/game1024/ChangeLog
@@ -6,4 +6,6 @@
0.06: Fixed issue 1609 added a message popup state handler to control unwanted screen redraw
0.07: Optimized the mover algorithm for efficiency (work in progress)
0.08: Bug fix at end of the game with victorious splash and glorious orchestra
-0.09: Added settings menu, removed symbol selection button (*), added highscore reset
\ No newline at end of file
+0.09: Added settings menu, removed symbol selection button (*), added highscore reset
+0.10: fixed clockmode in settings
+0.11: Use default Bangle formatter for booleans
diff --git a/apps/game1024/metadata.json b/apps/game1024/metadata.json
index e2c4bdb3e..f3b72aad3 100644
--- a/apps/game1024/metadata.json
+++ b/apps/game1024/metadata.json
@@ -1,7 +1,7 @@
{ "id": "game1024",
"name": "1024 Game",
"shortName" : "1024 Game",
- "version": "0.09",
+ "version": "0.11",
"icon": "game1024.png",
"screenshots": [ {"url":"screenshot.png" } ],
"readme":"README.md",
diff --git a/apps/game1024/settings.js b/apps/game1024/settings.js
index c8e393663..b52e060b1 100644
--- a/apps/game1024/settings.js
+++ b/apps/game1024/settings.js
@@ -32,16 +32,15 @@
}
},
"Exit press:": {
- value: !settings.debugMode, // ! converts undefined to true
+ value: !settings.clockMode,
format: v => v?"short":"long",
onchange: v => {
- settings.debugMode = v;
+ settings.clockMode = v;
writeSettings();
},
},
"Debug mode:": {
- value: !!settings.debugMode, // !! converts undefined to false
- format: v => v?"On":"Off",
+ value: !!settings.debugMode,
onchange: v => {
settings.debugMode = v;
writeSettings();
@@ -67,4 +66,4 @@
}
// Show the menu
E.showMenu(settingsMenu);
- })
\ No newline at end of file
+ })
diff --git a/apps/gbmusic/ChangeLog b/apps/gbmusic/ChangeLog
index e2ee53ede..d8379b317 100644
--- a/apps/gbmusic/ChangeLog
+++ b/apps/gbmusic/ChangeLog
@@ -9,3 +9,4 @@
0.09: Move event listener from widget to boot code, stops music from showing up in messages
0.10: Simplify touch events
Remove date+time
+0.11: Use default Bangle formatter for booleans
diff --git a/apps/gbmusic/metadata.json b/apps/gbmusic/metadata.json
index 0ded80452..bbe2a158d 100644
--- a/apps/gbmusic/metadata.json
+++ b/apps/gbmusic/metadata.json
@@ -2,7 +2,7 @@
"id": "gbmusic",
"name": "Gadgetbridge Music Controls",
"shortName": "Music Controls",
- "version": "0.10",
+ "version": "0.11",
"description": "Control the music on your Gadgetbridge-connected phone",
"icon": "icon.png",
"screenshots": [{"url":"screenshot_v1_d.png"},{"url":"screenshot_v1_l.png"},
diff --git a/apps/gbmusic/settings.js b/apps/gbmusic/settings.js
index ae013fda5..6619eab1c 100644
--- a/apps/gbmusic/settings.js
+++ b/apps/gbmusic/settings.js
@@ -25,19 +25,16 @@
}
}
- const yesNo = (v) => translate(v ? "Yes" : "No");
let menu = {
"": {"title": "Music Control"},
};
menu[translate("< Back")] = back;
menu[translate("Auto start")] = {
value: !!s.autoStart,
- format: yesNo,
onchange: save("autoStart"),
};
menu[translate("Simple button")] = {
value: !!s.simpleButton,
- format: yesNo,
onchange: save("simpleButton"),
};
diff --git a/apps/gbridge/ChangeLog b/apps/gbridge/ChangeLog
index 059767ece..f707ffb94 100644
--- a/apps/gbridge/ChangeLog
+++ b/apps/gbridge/ChangeLog
@@ -27,3 +27,4 @@
0.25: workaround call notification
Fix inflated step number
0.26: Include charging status in battery updates to phone
+0.27: Use default Bangle formatter for booleans
diff --git a/apps/gbridge/metadata.json b/apps/gbridge/metadata.json
index db7119758..e6130b06b 100644
--- a/apps/gbridge/metadata.json
+++ b/apps/gbridge/metadata.json
@@ -1,7 +1,7 @@
{
"id": "gbridge",
"name": "Gadgetbridge",
- "version": "0.26",
+ "version": "0.27",
"description": "(NOT RECOMMENDED) Displays Gadgetbridge notifications from Android. Please use the 'Android Integration' Bangle.js app instead.",
"icon": "app.png",
"type": "widget",
diff --git a/apps/gbridge/settings.js b/apps/gbridge/settings.js
index f9c7cde90..cf6c84c73 100644
--- a/apps/gbridge/settings.js
+++ b/apps/gbridge/settings.js
@@ -27,13 +27,11 @@
"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
"Show Icon" : {
value: settings().showIcon,
- format: v => v?"Yes":"No",
onchange: setIcon
},
"Find Phone" : function() { E.showMenu(findPhone); },
"Record HRM" : {
value: !!settings().hrm,
- format: v => v?"Yes":"No",
onchange: v => updateSetting('hrm', v)
}
};
diff --git a/apps/geissclk/ChangeLog b/apps/geissclk/ChangeLog
index 7458fadee..cd46173f7 100644
--- a/apps/geissclk/ChangeLog
+++ b/apps/geissclk/ChangeLog
@@ -1,3 +1,4 @@
0.01: New App!
0.02: BTN2->launcher, use smaller text to allow "20:00" to fit on screen
0.03: Changed setWatch to Bangle.setUI
+0.04: Tell clock widgets to hide.
diff --git a/apps/geissclk/clock.js b/apps/geissclk/clock.js
index f14ea5f39..5401fb142 100644
--- a/apps/geissclk/clock.js
+++ b/apps/geissclk/clock.js
@@ -142,11 +142,13 @@ Bangle.on('lcdPower',function(on) {
animInterval = setInterval(iterate, 50);
}
});
-g.clear();
+
+// Show launcher when button pressed
+Bangle.setUI("clock");g.clear();
+
Bangle.loadWidgets();
Bangle.drawWidgets();
iterate();
animInterval = setInterval(iterate, 50);
-// Show launcher when button pressed
-Bangle.setUI("clock");
+
diff --git a/apps/geissclk/metadata.json b/apps/geissclk/metadata.json
index 456854dbd..68bd2a970 100644
--- a/apps/geissclk/metadata.json
+++ b/apps/geissclk/metadata.json
@@ -1,7 +1,7 @@
{
"id": "geissclk",
"name": "Geiss Clock",
- "version": "0.03",
+ "version": "0.04",
"description": "7 segment clock with animated background in the style of Ryan Geiss' music visualisation. NOTE: The first run will take ~1 minute to do some precalculation",
"icon": "clock.png",
"type": "clock",
diff --git a/apps/glbasic/ChangeLog b/apps/glbasic/ChangeLog
index 89aee01f8..d97fd44d5 100644
--- a/apps/glbasic/ChangeLog
+++ b/apps/glbasic/ChangeLog
@@ -1,2 +1,3 @@
0.20: New App!
+0.21: Tell clock widgets to hide.
diff --git a/apps/glbasic/glbasic.app.js b/apps/glbasic/glbasic.app.js
index ff7837ada..c1f82f74c 100644
--- a/apps/glbasic/glbasic.app.js
+++ b/apps/glbasic/glbasic.app.js
@@ -178,6 +178,8 @@ function draw() {
////////////////////////////////////////////////////
// Bangle.setBarometerPower(true);
+Bangle.setUI("clock");
+
Bangle.loadWidgets();
draw();
@@ -197,6 +199,5 @@ Bangle.on('lcdPower', on => {
}
});
-Bangle.setUI("clock");
Bangle.drawWidgets();
diff --git a/apps/glbasic/metadata.json b/apps/glbasic/metadata.json
index 7c79097da..6d4c562a3 100644
--- a/apps/glbasic/metadata.json
+++ b/apps/glbasic/metadata.json
@@ -2,7 +2,7 @@
"id": "glbasic",
"name": "GLBasic Clock",
"shortName": "GLBasic",
- "version": "0.20",
+ "version": "0.21",
"description": "A clock with large numbers",
"dependencies": {"widpedom":"app"},
"icon": "icon48.png",
diff --git a/apps/golfview/ChangeLog b/apps/golfview/ChangeLog
index b243db101..909b8feca 100644
--- a/apps/golfview/ChangeLog
+++ b/apps/golfview/ChangeLog
@@ -1 +1,2 @@
0.01: New App! Very limited course support.
+0.02: Course search added to BangleApps page
\ No newline at end of file
diff --git a/apps/golfview/custom.html b/apps/golfview/custom.html
index 94bc551c0..f80e8dee5 100644
--- a/apps/golfview/custom.html
+++ b/apps/golfview/custom.html
@@ -7,30 +7,55 @@
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous">
+
+
-
-
-
Search
-
+
+
+
+
+
No course loaded, please search for a course then choose 'select'.
Upload to Device
Download Course
-
A course needs a few things to be parsed correctly by this tool.
-
- See official mapping guidelines here .
- All holes and features must be within the target course's area.
- Supported features are greens, fairways, tees, bunkers, water hazards and holes.
- All features for a given hole should have the "ref" tag with the hole number as value. Shared features should
- list ref values separated by ';'. example .
- There must be 18 holes and they must have the following tags: handicap, par, ref, dist
- For any mapping assistance or issues, please file in the official
- repo
-
-
Example Course
-
© OpenStreetMap contributors
+
+
A course needs a few things to be parsed correctly by this tool.
+
+ See official mapping guidelines here .
+ All holes and features must be within the target course's area.
+ Supported features are greens, fairways, tees, bunkers, water hazards and holes.
+ All features for a given hole should have the "ref" tag with the hole number as value. Shared features
+ should
+ list ref values separated by ';'. example .
+ There must be 18 holes and they must have the following tags: handicap, par, ref, dist
+ For any mapping assistance or issues, please file in the official
+ repo
+
+
Example Course
+
+
@@ -38,13 +63,47 @@
-
-
-
-