Merge remote-tracking branch 'upstream/master'
|
@ -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']
|
|
@ -1,4 +1,4 @@
|
||||||
name: Node CI
|
name: build
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
@ -6,29 +6,22 @@ jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [16.x]
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository and submodules
|
- name: Checkout repository and submodules
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js 16.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: 16.x
|
||||||
- name: install testing dependencies
|
- name: Install testing dependencies
|
||||||
run: npm i
|
run: npm ci
|
||||||
- name: test all apps and widgets
|
- name: Test all apps and widgets
|
||||||
run: npm run test
|
run: npm test
|
||||||
- name: install typescript dependencies
|
- name: Install typescript dependencies
|
||||||
working-directory: ./typescript
|
working-directory: ./typescript
|
||||||
run: npm ci
|
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
|
working-directory: ./typescript
|
||||||
run: npm run build
|
run: npm run build
|
|
@ -1,6 +1,5 @@
|
||||||
.htaccess
|
.htaccess
|
||||||
node_modules
|
node_modules
|
||||||
package-lock.json
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.js.bak
|
*.js.bak
|
||||||
appdates.csv
|
appdates.csv
|
||||||
|
@ -11,3 +10,7 @@ tests/Layout/bin/tmp.*
|
||||||
tests/Layout/testresult.bmp
|
tests/Layout/testresult.bmp
|
||||||
apps.local.json
|
apps.local.json
|
||||||
_site
|
_site
|
||||||
|
.jekyll-cache
|
||||||
|
.owncloudsync.log
|
||||||
|
Desktop.ini
|
||||||
|
.sync_*.db*
|
||||||
|
|
27
README.md
|
@ -1,7 +1,7 @@
|
||||||
Bangle.js App Loader (and Apps)
|
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 **release version** at [banglejs.com/apps](https://banglejs.com/apps)
|
||||||
* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/)
|
* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/)
|
||||||
|
@ -191,7 +191,7 @@ widget bar at the top of the screen they can add themselves to the global
|
||||||
|
|
||||||
```
|
```
|
||||||
WIDGETS["mywidget"]={
|
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
|
sortorder:0, // (Optional) determines order of widgets in the same corner
|
||||||
width: 24, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
|
width: 24, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
|
||||||
draw:draw // called to draw the widget
|
draw:draw // called to draw the widget
|
||||||
|
@ -226,10 +226,8 @@ and which gives information about the app for the Launcher.
|
||||||
"name":"Short Name", // for Bangle.js menu
|
"name":"Short Name", // for Bangle.js menu
|
||||||
"icon":"*myappid", // for Bangle.js menu
|
"icon":"*myappid", // for Bangle.js menu
|
||||||
"src":"-myappid", // source file
|
"src":"-myappid", // source file
|
||||||
"type":"widget/clock/app/bootloader", // optional, default "app"
|
"type":"widget/clock/app/bootloader/...", // optional, default "app"
|
||||||
// if this is 'widget' then it's not displayed in the menu
|
// see 'type' in 'metadata.json format' below for more options/info
|
||||||
// if it's 'clock' then it'll be loaded by default at boot time
|
|
||||||
// if this is 'bootloader' then it's code that is run at boot time, but is not in a menu
|
|
||||||
"version":"1.23",
|
"version":"1.23",
|
||||||
// added by BangleApps loader on upload based on metadata.json
|
// added by BangleApps loader on upload based on metadata.json
|
||||||
"files:"file1,file2,file3",
|
"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
|
"version": "0v01", // the version of this app
|
||||||
"description": "...", // long description (can contain markdown)
|
"description": "...", // long description (can contain markdown)
|
||||||
"icon": "icon.png", // icon in apps/
|
"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) -
|
"type":"...", // optional(if app) -
|
||||||
// 'app' - an application
|
// 'app' - an application
|
||||||
// 'clock' - a clock - required for clocks to automatically start
|
// 'clock' - a clock - required for clocks to automatically start
|
||||||
// 'widget' - a widget
|
// 'widget' - a widget
|
||||||
// 'launch' - replacement launcher app
|
// 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js'
|
||||||
// 'bootloader' - code that runs at startup only
|
// '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
|
// '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
|
"tags": "", // comma separated tag list for searching
|
||||||
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
|
"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
|
"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'
|
// 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
|
"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.
|
* 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
|
* storage is used to identify the app files and how to handle them
|
||||||
* data is used to clean up files when the app is uninstalled
|
* data is used to clean up files when the app is uninstalled
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
theme: jekyll-theme-minimal
|
theme: jekyll-theme-slate
|
|
@ -0,0 +1,352 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=0.8,maximum-scale=0.8, minimum-scale=0.8, shrink-to-fit=no">
|
||||||
|
<link rel="stylesheet" href="css/spectre.min.css">
|
||||||
|
<link rel="stylesheet" href="css/spectre-exp.min.css">
|
||||||
|
<link rel="stylesheet" href="css/spectre-icons.min.css">
|
||||||
|
<link rel="stylesheet" href="css/pwa.css">
|
||||||
|
<link rel="stylesheet" href="css/main.css">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="img/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="site.webmanifest">
|
||||||
|
<link rel="mask-icon" href="img/safari-pinned-tab.svg" color="#5755d9">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="BangleApps">
|
||||||
|
<meta name="application-name" content="BangleApps">
|
||||||
|
<meta name="msapplication-TileColor" content="#5755d9">
|
||||||
|
<meta name="theme-color" content="#5755d9">
|
||||||
|
<title>Bangle.js App Loader</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!--<button id="test">Test</button>
|
||||||
|
<div id="status"></div>-->
|
||||||
|
|
||||||
|
<header class="navbar-primary navbar">
|
||||||
|
<section class="navbar-section" >
|
||||||
|
<a href="https://banglejs.com" target="_blank" class="navbar-brand mr-2" ><img src="img/banglejs-logo-sml.png" alt="Bangle.js">
|
||||||
|
<div>App Loader</div></a>
|
||||||
|
<!-- <a href="#" class="btn btn-link">...</a> -->
|
||||||
|
</section>
|
||||||
|
<section class="navbar-section">
|
||||||
|
<button class="btn" id="connectmydevice">Connect</button>
|
||||||
|
</section>
|
||||||
|
<!--<section class="navbar-section">
|
||||||
|
<div class="input-group input-inline">
|
||||||
|
<input class="form-input" type="text" placeholder="search">
|
||||||
|
<button class="btn btn-primary input-group-btn">Search</button>
|
||||||
|
</div>
|
||||||
|
</section>-->
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="container" style="padding-top:4px">
|
||||||
|
<p id="requireHTTPS" class="hidden">
|
||||||
|
<b>STOP!</b> This page <b>must</b> be served over HTTPS. Please <a>reload this page via HTTPS</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<ul class="tab tab-block" id="tab-navigate">
|
||||||
|
<li class="tab-item active" id="tab-librarycontainer">
|
||||||
|
<a href="javascript:showTab('librarycontainer')">Library</a>
|
||||||
|
</li>
|
||||||
|
<li class="tab-item" id="tab-myappscontainer">
|
||||||
|
<a href="javascript:showTab('myappscontainer')">My Apps</a>
|
||||||
|
</li>
|
||||||
|
<li class="tab-item" id="tab-morecontainer">
|
||||||
|
<a href="javascript:showTab('morecontainer')">More...</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="container" id="toastcontainer">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container apploader-tab" id="librarycontainer">
|
||||||
|
<div class="dropdown-container">
|
||||||
|
<div class="dropdown devicetype-nav">
|
||||||
|
<a href="#" class="btn btn-link dropdown-toggle" tabindex="0">
|
||||||
|
<span>All apps</span><i class="icon icon-caret"></i>
|
||||||
|
</a>
|
||||||
|
<!-- menu component -->
|
||||||
|
<ul class="menu">
|
||||||
|
<li class="menu-item"><a>All apps</a></li>
|
||||||
|
<li class="menu-item"><a dt="BANGLEJS">Bangle.js 1</a></li>
|
||||||
|
<li class="menu-item"><a dt="BANGLEJS2">Bangle.js 2</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="filter-nav">
|
||||||
|
<label class="chip active" filterid="">Default</label>
|
||||||
|
<label class="chip" filterid="clock">Clocks</label>
|
||||||
|
<label class="chip" filterid="game">Games</label>
|
||||||
|
<label class="chip" filterid="tool">Tools</label>
|
||||||
|
<label class="chip" filterid="widget">Widgets</label>
|
||||||
|
<label class="chip" filterid="bluetooth">Bluetooth</label>
|
||||||
|
<label class="chip" filterid="outdoors">Outdoors</label>
|
||||||
|
<label class="chip" filterid="favourites">Favourites</label>
|
||||||
|
</div>
|
||||||
|
<div class="sort-nav hidden">
|
||||||
|
<span>Sort by:</span>
|
||||||
|
<label class="chip active" sortid="">None</label>
|
||||||
|
<label class="chip" sortid="created">New</label>
|
||||||
|
<label class="chip" sortid="modified">Updated</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel" style="clear:both">
|
||||||
|
<div class="panel-header">
|
||||||
|
<div class="input-group" id="searchform">
|
||||||
|
<input class="form-input" type="text" placeholder="Keywords...">
|
||||||
|
<button class="btn btn-primary input-group-btn">Search</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body columns"><!-- apps go here --></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container apploader-tab" id="myappscontainer" style="display:none">
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-header" style="text-align:right">
|
||||||
|
<button class="btn refresh">Refresh...</button>
|
||||||
|
<button class="btn btn-primary updateapps hidden">Update X apps</button>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body columns"><!-- apps go here --></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container apploader-tab" id="morecontainer" style="display:none">
|
||||||
|
<div class="hero bg-gray">
|
||||||
|
<div class="hero-body">
|
||||||
|
<a href="https://banglejs.com" target="_blank"><img src="img/banglejs-logo-mid.png" alt="Bangle.js"></a>
|
||||||
|
<h2>App Loader</h2>
|
||||||
|
<p>A tool for uploading and removing apps from <a href="https://banglejs.com" target="_blank">Bangle.js Smart Watches</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container" style="padding-top: 8px;">
|
||||||
|
<p><b>Can't connect?</b> Check out the <a href="https://www.espruino.com/Troubleshooting+Bangle.js" target="_blank">Bangle.js Troubleshooting page</a>
|
||||||
|
<p id="apploaderlinks"></p>
|
||||||
|
<p>Check out <a href="https://github.com/espruino/BangleApps" target="_blank">the Source on GitHub</a>, or
|
||||||
|
find out <a href="https://www.espruino.com/Bangle.js+App+Loader" target="_blank">how to add your own app</a></p>
|
||||||
|
<p>Using <a href="https://espruino.com/" target="_blank">Espruino</a>, Icons from <a href="https://icons8.com/" target="_blank">icons8.com</a></p>
|
||||||
|
|
||||||
|
<h3>Utilities</h3>
|
||||||
|
<p><button class="btn" id="settime">Set Bangle.js Time</button>
|
||||||
|
<button class="btn" id="removeall" data-tooltip="Delete everything from your Bangle, leaving it blank">Remove all Apps</button>
|
||||||
|
<button class="btn" id="reinstallall" data-tooltip="Remove and re-install every app, leaving all other data intact">Reinstall apps</button>
|
||||||
|
<button class="btn" id="installdefault">Install default apps</button>
|
||||||
|
<button class="btn" id="installfavourite" data-tooltip="Delete everything, install apps you've marked as favourites">Install favourite apps</button></p>
|
||||||
|
<p><button class="btn tooltip tooltip-right" id="downloadallapps" data-tooltip="Download all Bangle.js files to a ZIP file">Backup</button>
|
||||||
|
<button class="btn tooltip tooltip-right" id="uploadallapps" data-tooltip="Restore Bangle.js from a ZIP file">Restore</button></p>
|
||||||
|
<h3>Settings</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-switch">
|
||||||
|
<input type="checkbox" id="settings-pretokenise">
|
||||||
|
<i class="form-icon"></i> Pretokenise apps before upload (smaller, faster apps)
|
||||||
|
</label>
|
||||||
|
<label class="form-switch">
|
||||||
|
<input type="checkbox" id="settings-settime">
|
||||||
|
<i class="form-icon"></i> Always update time when we connect
|
||||||
|
</label>
|
||||||
|
<div class="form-group">
|
||||||
|
<select class="form-select form-inline" id="settings-lang" style="width: 10em">
|
||||||
|
<option value="">None (English)</option>
|
||||||
|
</select> <span>Translations (<a href="https://github.com/espruino/BangleApps/issues/1311" target="_blank">BETA - more info</a>). Any apps that are uploaded to Bangle.js after changing this will have any text automatically translated.</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn" id="defaultsettings">Default settings</button>
|
||||||
|
</div>
|
||||||
|
<div id="more-deviceinfo" style="display:none">
|
||||||
|
<h3>Device info</h3>
|
||||||
|
<div id="more-deviceinfo-content"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="floating hidden">
|
||||||
|
<!-- Install button, hidden by default -->
|
||||||
|
<div id="installContainer" class="hidden">
|
||||||
|
<button id="butInstall" type="button">
|
||||||
|
Install
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://www.puck-js.com/puck.js"></script>
|
||||||
|
<script src="core/lib/marked.min.js"></script>
|
||||||
|
<script src="core/lib/espruinotools.js"></script>
|
||||||
|
<script src="core/lib/heatshrink.js"></script>
|
||||||
|
<script src="core/js/utils.js"></script>
|
||||||
|
<script src="loader.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js"></script> <!-- for backup.js -->
|
||||||
|
<script src="backup.js"></script>
|
||||||
|
<script src="core/js/ui.js"></script>
|
||||||
|
<script src="core/js/comms.js"></script>
|
||||||
|
<script src="core/js/appinfo.js"></script>
|
||||||
|
<script src="core/js/index.js"></script>
|
||||||
|
<script src="core/js/pwa.js" defer></script>
|
||||||
|
<script>
|
||||||
|
/*Android = {
|
||||||
|
bangleTx : function(data) {
|
||||||
|
console.log("TX : "+JSON.stringify(data));
|
||||||
|
}
|
||||||
|
};*/
|
||||||
|
|
||||||
|
/*document.getElementById("test").addEventListener("click", function() {
|
||||||
|
console.log("Pressed");
|
||||||
|
Android.bangleTx("LED1.toggle();\n");
|
||||||
|
});*/
|
||||||
|
|
||||||
|
if (typeof Android!=="undefined") {
|
||||||
|
console.log("Running in Android, overwrite Puck library");
|
||||||
|
|
||||||
|
var isBusy = false;
|
||||||
|
var queue = [];
|
||||||
|
var connection = {
|
||||||
|
cb : function(data) {},
|
||||||
|
write : function(data, writecb) {
|
||||||
|
Android.bangleTx(data);
|
||||||
|
Puck.writeProgress(data.length, data.length);
|
||||||
|
if (writecb) setTimeout(writecb,10);
|
||||||
|
},
|
||||||
|
close : function() {},
|
||||||
|
received : "",
|
||||||
|
hadData : false
|
||||||
|
}
|
||||||
|
|
||||||
|
function bangleRx(data) {
|
||||||
|
// document.getElementById("status").innerText = "RX:"+data;
|
||||||
|
connection.received += data;
|
||||||
|
connection.hadData = true;
|
||||||
|
if (connection.cb) connection.cb(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(level, s) {
|
||||||
|
if (Puck.log) Puck.log(level, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleQueue() {
|
||||||
|
if (!queue.length) return;
|
||||||
|
var q = queue.shift();
|
||||||
|
log(3,"Executing "+JSON.stringify(q)+" from queue");
|
||||||
|
if (q.type == "write") Puck.write(q.data, q.callback, q.callbackNewline);
|
||||||
|
else log(1,"Unknown queue item "+JSON.stringify(q));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* convenience function... Write data, call the callback with data:
|
||||||
|
callbackNewline = false => if no new data received for ~0.2 sec
|
||||||
|
callbackNewline = true => after a newline */
|
||||||
|
function write(data, callback, callbackNewline) {
|
||||||
|
let result;
|
||||||
|
/// If there wasn't a callback function, then promisify
|
||||||
|
if (typeof callback !== 'function') {
|
||||||
|
callbackNewline = callback;
|
||||||
|
|
||||||
|
result = new Promise((resolve, reject) => callback = (value, err) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
else resolve(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBusy) {
|
||||||
|
log(3, "Busy - adding Puck.write to queue");
|
||||||
|
queue.push({type:"write", data:data, callback:callback, callbackNewline:callbackNewline});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cbTimeout;
|
||||||
|
function onWritten() {
|
||||||
|
if (callbackNewline) {
|
||||||
|
connection.cb = function(d) {
|
||||||
|
var newLineIdx = connection.received.indexOf("\n");
|
||||||
|
if (newLineIdx>=0) {
|
||||||
|
var l = connection.received.substr(0,newLineIdx);
|
||||||
|
connection.received = connection.received.substr(newLineIdx+1);
|
||||||
|
connection.cb = undefined;
|
||||||
|
if (cbTimeout) clearTimeout(cbTimeout);
|
||||||
|
cbTimeout = undefined;
|
||||||
|
if (callback)
|
||||||
|
callback(l);
|
||||||
|
isBusy = false;
|
||||||
|
handleQueue();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// wait for any received data if we have a callback...
|
||||||
|
var maxTime = 300; // 30 sec - Max time we wait in total, even if getting data
|
||||||
|
var dataWaitTime = callbackNewline ? 100/*10 sec if waiting for newline*/ : 3/*300ms*/;
|
||||||
|
var maxDataTime = dataWaitTime; // max time we wait after having received data
|
||||||
|
cbTimeout = setTimeout(function timeout() {
|
||||||
|
cbTimeout = undefined;
|
||||||
|
if (maxTime) maxTime--;
|
||||||
|
if (maxDataTime) maxDataTime--;
|
||||||
|
if (connection.hadData) maxDataTime=dataWaitTime;
|
||||||
|
if (maxDataTime && maxTime) {
|
||||||
|
cbTimeout = setTimeout(timeout, 100);
|
||||||
|
} else {
|
||||||
|
connection.cb = undefined;
|
||||||
|
if (callback)
|
||||||
|
callback(connection.received);
|
||||||
|
isBusy = false;
|
||||||
|
handleQueue();
|
||||||
|
connection.received = "";
|
||||||
|
}
|
||||||
|
connection.hadData = false;
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connection.txInProgress) connection.received = "";
|
||||||
|
isBusy = true;
|
||||||
|
connection.write(data, onWritten);
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
|
||||||
|
Puck = {
|
||||||
|
/// Are we writing debug information? 0 is no, 1 is some, 2 is more, 3 is all.
|
||||||
|
debug : Puck.debug,
|
||||||
|
/// Should we use flow control? Default is true
|
||||||
|
flowControl : true,
|
||||||
|
/// Used internally to write log information - you can replace this with your own function
|
||||||
|
log : function(level, s) { if (level <= this.debug) console.log("<BLE> "+s)},
|
||||||
|
/// Called with the current send progress or undefined when done - you can replace this with your own function
|
||||||
|
writeProgress : Puck.writeProgress,
|
||||||
|
connect : function(callback) {
|
||||||
|
setTimeout(callback, 10);
|
||||||
|
},
|
||||||
|
write : write,
|
||||||
|
eval : function(expr, cb) {
|
||||||
|
const response = write('\x10Bluetooth.println(JSON.stringify(' + expr + '))\n', true)
|
||||||
|
.then(function (d) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(d);
|
||||||
|
} catch (e) {
|
||||||
|
log(1, "Unable to decode " + JSON.stringify(d) + ", got " + e.toString());
|
||||||
|
return Promise.reject(d);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (cb) {
|
||||||
|
return void response.then(cb, (err) => cb(null, err));
|
||||||
|
} else {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isConnected : function() { return true; },
|
||||||
|
getConnection : function() { return connection; },
|
||||||
|
close : function() {
|
||||||
|
if (connection)
|
||||||
|
connection.close();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// no need for header
|
||||||
|
document.getElementsByTagName("header")[0].style="display:none";
|
||||||
|
// force connection attempt automatically
|
||||||
|
setTimeout(function() {
|
||||||
|
getInstalledApps(true).catch(err => {
|
||||||
|
showToast("Device connection failed, "+err,"error");
|
||||||
|
if ("object"==typeof err) console.log(err.stack);
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
|
@ -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)
|
|
@ -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"))
|
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 5.8 KiB |
|
@ -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}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 7.1 KiB |
|
@ -269,7 +269,7 @@ function actions(v){
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get Messages status
|
// Get Messages status
|
||||||
var messages = require("Storage").readJSON("messages.json",1)||[];
|
var messages_installed = require("Storage").read("messages") !== undefined;
|
||||||
|
|
||||||
//var BTconnected = NRF.getSecurityStatus().connected;
|
//var BTconnected = NRF.getSecurityStatus().connected;
|
||||||
//NRF.on('connect',BTconnected = NRF.getSecurityStatus().connected);
|
//NRF.on('connect',BTconnected = NRF.getSecurityStatus().connected);
|
||||||
|
@ -318,7 +318,7 @@ function drawWidgeds() {
|
||||||
var x2M = x1M + 25;
|
var x2M = x1M + 25;
|
||||||
var y2M = y2B;
|
var y2M = y2B;
|
||||||
|
|
||||||
if (messages.some(m=>m.new)) {
|
if (messages_installed && require("messages").status() == "new") {
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
g.fillRect(x1M,y1M,x2M,y2M);
|
g.fillRect(x1M,y1M,x2M,y2M);
|
||||||
g.setColor(g.theme.bg);
|
g.setColor(g.theme.bg);
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: Initial version for upload
|
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
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "7x7dotsclock",
|
{ "id": "7x7dotsclock",
|
||||||
"name": "7x7 Dots Clock",
|
"name": "7x7 Dots Clock",
|
||||||
"shortName":"7x7 Dots Clock",
|
"shortName":"7x7 Dots Clock",
|
||||||
"version":"0.02",
|
"version":"0.03",
|
||||||
"description": "A clock with a big 7x7 dots Font",
|
"description": "A clock with a big 7x7 dots Font",
|
||||||
"icon": "dotsfontclock.png",
|
"icon": "dotsfontclock.png",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Fullscreen settings.
|
0.02: Fullscreen settings.
|
||||||
|
0.03: Tell clock widgets to hide.
|
||||||
|
|
|
@ -115,6 +115,9 @@ function draw() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show launcher when middle button pressed
|
||||||
|
Bangle.setUI("clock");
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
|
|
||||||
// Clear the screen once, at startup
|
// Clear the screen once, at startup
|
||||||
|
@ -140,5 +143,3 @@ Bangle.on('lock', function(isLocked) {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Show launcher when middle button pressed
|
|
||||||
Bangle.setUI("clock");
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "90sclk",
|
"id": "90sclk",
|
||||||
"name": "90s Clock",
|
"name": "90s Clock",
|
||||||
"version": "0.02",
|
"version": "0.03",
|
||||||
"description": "A 90s style watch-face",
|
"description": "A 90s style watch-face",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
|
|
@ -9,7 +9,7 @@ currently-running apps */
|
||||||
|
|
||||||
// add your widget
|
// add your widget
|
||||||
WIDGETS["mywidget"]={
|
WIDGETS["mywidget"]={
|
||||||
area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
|
area:"tl", // tl (top left), tr (top right), 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
|
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
|
draw:draw // called to draw the widget
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,2 +1,9 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Fix the settings bug and some tweaking
|
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
|
|
@ -1,13 +1,15 @@
|
||||||
# Activity reminder
|
# Activity reminder
|
||||||
|
|
||||||
A reminder to take short walks for the ones with a sedentary lifestyle.
|
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:
|
Different settings can be personalized:
|
||||||
- Enable : Enable/Disable the app
|
- Enable : Enable/Disable the app
|
||||||
- Start hour: Hour to start the reminder
|
- Start hour: Hour to start the reminder
|
||||||
- End hour: Hour to end the reminder
|
- End hour: Hour to end the reminder
|
||||||
- Max inactivity: Maximum inactivity time to allow before the alert. From 15 to 60 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 15 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
|
- Min steps: Minimal amount of steps to count as an activity
|
||||||
|
- Temp Threshold: Temperature threshold to determine if the watch is worn
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
})();
|
|
@ -1,37 +1,54 @@
|
||||||
function drawAlert(){
|
(function () {
|
||||||
E.showPrompt("Inactivity detected",{
|
// load variable before defining functions cause it can trigger a ReferenceError
|
||||||
title:"Activity reminder",
|
const activityreminder = require("activityreminder");
|
||||||
buttons : {"Ok": true,"Dismiss": false}
|
let activityreminder_data = activityreminder.loadData();
|
||||||
}).then(function(v) {
|
let W = g.getWidth();
|
||||||
if(v == true){
|
// let H = g.getHeight();
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
||||||
Bangle.buzz(400);
|
function getHoursMins(date){
|
||||||
setTimeout(load, 20000);
|
var h = date.getHours();
|
||||||
}
|
var m = date.getMinutes();
|
||||||
|
return (""+h).substr(-2) + ":" + ("0"+m).substr(-2);
|
||||||
|
}
|
||||||
|
|
||||||
function run(){
|
function drawData(name, value, y){
|
||||||
if(stepsArray.length == activityreminder.maxInnactivityMin){
|
g.drawString(name, 10, y);
|
||||||
if (stepsArray[0] - stepsArray[stepsArray.length-1] < activityreminder.minSteps){
|
g.drawString(value, 100, y);
|
||||||
drawAlert();
|
}
|
||||||
}
|
|
||||||
}else{
|
|
||||||
eval(require("Storage").read("activityreminder.settings.js"))(()=>load());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
function drawInfo() {
|
||||||
|
var h=18, y = h;
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
g.setFont("Vector",h).setFontAlign(-1,-1);
|
||||||
|
|
||||||
g.clear();
|
// Header
|
||||||
Bangle.loadWidgets();
|
g.drawLine(0,25,W,25);
|
||||||
Bangle.drawWidgets();
|
g.drawLine(0,26,W,26);
|
||||||
activityreminder = require("activityreminder").loadSettings();
|
|
||||||
stepsArray = require("activityreminder").loadStepsArray();
|
g.drawString("Current Cycle", 10, y+=h);
|
||||||
run();
|
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);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentSteps(){
|
||||||
|
let health = Bangle.getHealthStatus("day");
|
||||||
|
return health.steps - activityreminder_data.stepsOnDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
g.clear();
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
drawInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
|
||||||
|
})();
|
|
@ -1,29 +1,81 @@
|
||||||
function run(){
|
(function () {
|
||||||
var now = new Date();
|
// load variable before defining functions cause it can trigger a ReferenceError
|
||||||
var h = now.getHours();
|
const activityreminder = require("activityreminder");
|
||||||
if(h >= activityreminder.startHour && h < activityreminder.endHour){
|
const activityreminder_settings = activityreminder.loadSettings();
|
||||||
var health = Bangle.getHealthStatus("day");
|
let activityreminder_data = activityreminder.loadData();
|
||||||
stepsArray.unshift(health.steps);
|
|
||||||
stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin);
|
if (activityreminder_data.firstLoad) {
|
||||||
require("activityreminder").saveStepsArray(stepsArray);
|
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_settings.enabled) {
|
||||||
if(activityreminder.enabled) {
|
|
||||||
stepsArray = require("activityreminder").loadStepsArray();
|
|
||||||
setInterval(run, 60000);
|
setInterval(run, 60000);
|
||||||
}
|
/* todo in a futur release
|
||||||
|
increase setInterval time to something that is still sensible (5 mins ?)
|
||||||
|
when we added a settimer
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
|
@ -1,22 +1,44 @@
|
||||||
exports.loadSettings = function() {
|
exports.loadSettings = function () {
|
||||||
return Object.assign({
|
return Object.assign({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
startHour: 9,
|
startHour: 9,
|
||||||
endHour: 20,
|
endHour: 20,
|
||||||
maxInnactivityMin: 30,
|
maxInnactivityMin: 30,
|
||||||
dismissDelayMin: 15,
|
dismissDelayMin: 15,
|
||||||
minSteps: 50
|
pauseDelayMin: 120,
|
||||||
}, require("Storage").readJSON("activityreminder.s.json", true) || {});
|
minSteps: 50,
|
||||||
|
tempThreshold: 27
|
||||||
|
}, require("Storage").readJSON("activityreminder.s.json", true) || {});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.writeSettings = function(settings){
|
exports.writeSettings = function (settings) {
|
||||||
require("Storage").writeJSON("activityreminder.s.json", settings);
|
require("Storage").writeJSON("activityreminder.s.json", settings);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.saveStepsArray = function(stepsArray) {
|
exports.saveData = function (data) {
|
||||||
require("Storage").writeJSON("activityreminder.sa.json", stepsArray);
|
require("Storage").writeJSON("activityreminder.data.json", data);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.loadStepsArray = function(){
|
exports.loadData = function () {
|
||||||
return require("Storage").readJSON("activityreminder.sa.json") || [];
|
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;
|
||||||
};
|
};
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "Activity Reminder",
|
"name": "Activity Reminder",
|
||||||
"shortName":"Activity Reminder",
|
"shortName":"Activity Reminder",
|
||||||
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
|
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
|
||||||
"version":"0.02",
|
"version":"0.09",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
"tags": "tool,activity",
|
"tags": "tool,activity",
|
||||||
|
@ -13,11 +13,12 @@
|
||||||
{"name": "activityreminder.app.js", "url":"app.js"},
|
{"name": "activityreminder.app.js", "url":"app.js"},
|
||||||
{"name": "activityreminder.boot.js", "url": "boot.js"},
|
{"name": "activityreminder.boot.js", "url": "boot.js"},
|
||||||
{"name": "activityreminder.settings.js", "url": "settings.js"},
|
{"name": "activityreminder.settings.js", "url": "settings.js"},
|
||||||
|
{"name": "activityreminder.alert.js", "url": "alert.js"},
|
||||||
{"name": "activityreminder", "url": "lib.js"},
|
{"name": "activityreminder", "url": "lib.js"},
|
||||||
{"name": "activityreminder.img", "url": "app-icon.js", "evaluate": true}
|
{"name": "activityreminder.img", "url": "app-icon.js", "evaluate": true}
|
||||||
],
|
],
|
||||||
"data": [
|
"data": [
|
||||||
{"name": "activityreminder.s.json"},
|
{"name": "activityreminder.s.json"},
|
||||||
{"name": "activityreminder.sa.json"}
|
{"name": "activityreminder.data.json", "storageFile": true}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
(function(back) {
|
(function (back) {
|
||||||
// Load settings
|
// Load settings
|
||||||
var settings = require("activityreminder").loadSettings();
|
const activityreminder = require("activityreminder");
|
||||||
|
let settings = activityreminder.loadSettings();
|
||||||
|
|
||||||
// Show the menu
|
function getMainMenu(){
|
||||||
E.showMenu({
|
var mainMenu = {
|
||||||
"" : { "title" : "Activity Reminder" },
|
"": { "title": "Activity Reminder" },
|
||||||
"< Back" : () => back(),
|
"< Back": () => back(),
|
||||||
'Enable': {
|
'Enable': {
|
||||||
value: settings.enabled,
|
value: settings.enabled,
|
||||||
format: v => v?"Yes":"No",
|
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.enabled = v;
|
settings.enabled = v;
|
||||||
require("activityreminder").writeSettings(settings);
|
activityreminder.writeSettings(settings);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Start hour': {
|
'Start hour': {
|
||||||
|
@ -19,46 +19,68 @@
|
||||||
min: 0, max: 24,
|
min: 0, max: 24,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.startHour = v;
|
settings.startHour = v;
|
||||||
require("activityreminder").writeSettings(settings);
|
activityreminder.writeSettings(settings);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'End hour': {
|
'End hour': {
|
||||||
value: settings.endHour,
|
value: settings.endHour,
|
||||||
min: 0, max: 24,
|
min: 0, max: 24,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.endHour = v;
|
settings.endHour = v;
|
||||||
require("activityreminder").writeSettings(settings);
|
activityreminder.writeSettings(settings);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Max inactivity': {
|
'Max inactivity': {
|
||||||
value: settings.maxInnactivityMin,
|
value: settings.maxInnactivityMin,
|
||||||
min: 15, max: 120,
|
min: 15, max: 120,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.maxInnactivityMin = v;
|
settings.maxInnactivityMin = v;
|
||||||
require("activityreminder").writeSettings(settings);
|
activityreminder.writeSettings(settings);
|
||||||
},
|
},
|
||||||
format: x => {
|
format: x => x + "m"
|
||||||
return x + " min";
|
},
|
||||||
}
|
'Dismiss delay': {
|
||||||
},
|
|
||||||
'Dismiss delay': {
|
|
||||||
value: settings.dismissDelayMin,
|
value: settings.dismissDelayMin,
|
||||||
min: 5, max: 15,
|
min: 5, max: 60,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.dismissDelayMin = 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 => {
|
format: x => {
|
||||||
return x + " min";
|
return x + "m";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Min steps': {
|
'Min steps': {
|
||||||
value: settings.minSteps,
|
value: settings.minSteps,
|
||||||
min: 10, max: 500,
|
min: 10, max: 500, step: 10,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.minSteps = 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());
|
||||||
})
|
})
|
||||||
|
|
|
@ -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.
|
|
@ -0,0 +1,62 @@
|
||||||
|
# Adv Casio Clock
|
||||||
|
|
||||||
|
<img src="https://user-images.githubusercontent.com/2981891/175355586-1dfc0d66-6555-4385-b124-1605fdb71a11.jpg" width="250" />
|
||||||
|
|
||||||
|
An over-engineered clock inspired by Casio watches.<br/>
|
||||||
|
It has a dedicated timer, a scratchpad and can display the weather condition 4 days ahead.<br/>
|
||||||
|
It uses a <a target="_blank" href="https://dotgreg.github.io/advCasioBangleClock/">custom web app</a> to update its content.<br/>
|
||||||
|
Forked from the awesome Cassio Watch.<br/>
|
||||||
|
|
||||||
|
## 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:<br/>
|
||||||
|
<img src="https://user-images.githubusercontent.com/2981891/175519126-049caf93-73d0-4472-9650-33b28f80843c.jpg" width="250" />
|
||||||
|
<img src="https://user-images.githubusercontent.com/2981891/175519128-96926e32-2165-4c61-9364-843440304bb9.jpg" width="250" />
|
||||||
|
<img src="https://user-images.githubusercontent.com/2981891/175519130-4921073c-48fc-4c29-932d-d42acc3b395c.jpg" width="250" />
|
||||||
|
|
||||||
|
Web interface to update weather & scratchpad <br/>
|
||||||
|
<a target="_blank" ref="https://dotgreg.github.io/advCasioBangleClock/">https://dotgreg.github.io/advCasioBangleClock</a>
|
||||||
|
|
||||||
|
<img src="https://user-images.githubusercontent.com/2981891/175519121-851bb209-7192-40db-a014-490c344f7597.jpg" width="250" />
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
### How to update the tasks list / weather
|
||||||
|
- you will need a <a target="_blank" href="https://openweathermap.org/price#weather">free openweathermap.org api key</a>.
|
||||||
|
- 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 <a target="_blank" href="https://github.com/dotgreg/advCasioBangleClock/tree/master/web-app">here</a>
|
||||||
|
- 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
|
||||||
|
<a target="_blank" href="https://github.com/dotgreg/advCasioBangleClock/issues">https://github.com/dotgreg/advCasioBangleClock/issues</a>
|
||||||
|
|
||||||
|
### Code repository (bangle app and web app)
|
||||||
|
<a target="_blank" href="https://github.com/dotgreg/advCasioBangleClock">https://github.com/dotgreg/advCasioBangleClock</a>
|
||||||
|
|
||||||
|
### Creator
|
||||||
|
<a target="_blank" href="https://github.com/dotgreg">https://github.com/dotgreg</a>
|
|
@ -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"))
|
|
@ -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<arr.length;i++) {
|
||||||
|
if (arr[i][2] > 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();
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1 @@
|
||||||
|
{"tasks":"", "weather":[]};
|
|
@ -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 }
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 87 KiB |
After Width: | Height: | Size: 104 KiB |
After Width: | Height: | Size: 230 KiB |
|
@ -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
|
|
@ -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.
|
|
@ -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"))
|
|
@ -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<titleCnt ? g.theme.bg2 : g.theme.bg).
|
||||||
|
setColor(idx<titleCnt ? g.theme.fg2 : g.theme.fg).
|
||||||
|
clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
|
||||||
|
g.setFont(bodyFont).drawString(lines[idx], r.x, r.y);
|
||||||
|
}, select : function(idx) {
|
||||||
|
if (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();
|
After Width: | Height: | Size: 3.0 KiB |
|
@ -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"}]
|
||||||
|
}
|
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.7 KiB |
|
@ -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);
|
||||||
|
})
|
|
@ -0,0 +1,2 @@
|
||||||
|
0.01: First, proof of concept
|
||||||
|
0.02: Load AGPS data on app start and automatically in background
|
|
@ -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)
|
|
@ -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==")
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -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();
|
|
@ -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);
|
||||||
|
}
|
||||||
|
})();
|
|
@ -0,0 +1 @@
|
||||||
|
{"enabled":true,"refresh":1440,"gnsstype":1}
|
|
@ -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<bin.length;i+=chunkSize) {
|
||||||
|
var chunk = bin.substr(i,chunkSize);
|
||||||
|
js += `Serial1.write(atob("${btoa(chunk)}"))\n`;
|
||||||
|
}
|
||||||
|
return js;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CASIC_CHECKSUM(cmd) {
|
||||||
|
var cs = 0;
|
||||||
|
for (var i=1;i<cmd.length;i++)
|
||||||
|
cs = cs ^ cmd.charCodeAt(i);
|
||||||
|
return cmd+"*"+cs.toString(16).toUpperCase().padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLastUpdate() {
|
||||||
|
const file = "agpsdata.json";
|
||||||
|
let data = require("Storage").readJSON(file, 1) || {};
|
||||||
|
data.lastUpdate = Math.round(Date.now());
|
||||||
|
require("Storage").writeJSON(file, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.pull = function(successCallback, failureCallback) {
|
||||||
|
let uri = "https://www.espruino.com/agps/casic.base64";
|
||||||
|
if (Bangle.http){
|
||||||
|
Bangle.http(uri, {timeout:10000}).then(event => {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
};
|
|
@ -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"}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.6 KiB |
|
@ -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());
|
||||||
|
});
|
|
@ -24,3 +24,14 @@
|
||||||
0.23: Fix regression with Days of Week (#1735)
|
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
|
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
|
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
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,31 @@
|
||||||
Alarms & Timers
|
# Alarms & Timers
|
||||||
===============
|
|
||||||
|
|
||||||
This app allows you to add/modify any alarms and 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)
|
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.
|
||||||
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).
|
||||||
|
|
|
@ -1,240 +1,386 @@
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
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)
|
// An array of alarm objects (see sched/README.md)
|
||||||
let alarms = require("sched").getAlarms();
|
var alarms = require("sched").getAlarms();
|
||||||
|
|
||||||
function getCurrentTime() {
|
function handleFirstDayOfWeek(dow) {
|
||||||
let time = new Date();
|
if (firstDayOfWeek == 1) {
|
||||||
return (
|
if ((dow & 1) == 1) {
|
||||||
time.getHours() * 3600000 +
|
// In the scheduler API Sunday is 1.
|
||||||
time.getMinutes() * 60000 +
|
// Here the week starts on Monday and Sunday is ON so
|
||||||
time.getSeconds() * 1000
|
// 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() {
|
// Check the first day of week and update the dow field accordingly (alarms only!)
|
||||||
require("sched").setAlarms(alarms);
|
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
|
||||||
require("sched").reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showMainMenu() {
|
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 = {
|
const menu = {
|
||||||
'': { 'title': /*LANG*/'Alarms&Timers' },
|
"": { "title": /*LANG*/"Alarms & Timers" },
|
||||||
/*LANG*/'< Back' : ()=>{load();},
|
"< Back": () => load(),
|
||||||
/*LANG*/'New Alarm': ()=>editAlarm(-1),
|
/*LANG*/"New...": () => showNewMenu()
|
||||||
/*LANG*/'New Timer': ()=>editTimer(-1)
|
|
||||||
};
|
};
|
||||||
alarms.forEach((alarm,idx)=>{
|
|
||||||
var type,txt; // a leading space is currently required (JS error in Espruino 2v12)
|
alarms.forEach((e, index) => {
|
||||||
if (alarm.timer) {
|
var label = e.timer
|
||||||
type = /*LANG*/"Timer";
|
? require("time_utils").formatDuration(e.timer)
|
||||||
txt = " "+require("sched").formatTime(alarm.timer);
|
: require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : "");
|
||||||
} else {
|
menu[label] = {
|
||||||
type = /*LANG*/"Alarm";
|
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
|
||||||
txt = " "+require("sched").formatTime(alarm.t);
|
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
if (alarms.some(e => !e.on)) {
|
menu[/*LANG*/"Advanced"] = () => showAdvancedMenu();
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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<<i)),
|
|
||||||
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
|
|
||||||
onchange: v => v ? dow |= 1<<i : dow &= ~(1<<i),
|
|
||||||
};
|
|
||||||
})(i);
|
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
function editAlarm(alarmIndex, alarm) {
|
function showNewMenu() {
|
||||||
let newAlarm = alarmIndex < 0;
|
E.showMenu({
|
||||||
let a = require("sched").newDefaultAlarm();
|
"": { "title": /*LANG*/"New..." },
|
||||||
if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
|
"< Back": () => showMainMenu(),
|
||||||
if (alarm) Object.assign(a,alarm);
|
/*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined),
|
||||||
let t = require("sched").decodeTime(a.t);
|
/*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 = {
|
const menu = {
|
||||||
'': { 'title': /*LANG*/'Alarm' },
|
"": { "title": isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm" },
|
||||||
/*LANG*/'< Back': () => {
|
"< Back": () => {
|
||||||
saveAlarm(newAlarm, alarmIndex, a, t);
|
prepareAlarmForSave(alarm, alarmIndex, time);
|
||||||
|
saveAndReload();
|
||||||
showMainMenu();
|
showMainMenu();
|
||||||
},
|
},
|
||||||
/*LANG*/'Hours': {
|
/*LANG*/"Hour": {
|
||||||
value: t.hrs, min : 0, max : 23, wrap : true,
|
value: time.h,
|
||||||
onchange: v => t.hrs=v
|
format: v => ("0" + v).substr(-2),
|
||||||
|
min: 0,
|
||||||
|
max: 23,
|
||||||
|
wrap: true,
|
||||||
|
onchange: v => time.h = v
|
||||||
},
|
},
|
||||||
/*LANG*/'Minutes': {
|
/*LANG*/"Minute": {
|
||||||
value: t.mins, min : 0, max : 59, wrap : true,
|
value: time.m,
|
||||||
onchange: v => t.mins=v
|
format: v => ("0" + v).substr(-2),
|
||||||
|
min: 0,
|
||||||
|
max: 59,
|
||||||
|
wrap: true,
|
||||||
|
onchange: v => time.m = v
|
||||||
},
|
},
|
||||||
/*LANG*/'Enabled': {
|
/*LANG*/"Enabled": {
|
||||||
value: a.on,
|
value: alarm.on,
|
||||||
format: v => v ? /*LANG*/"On" : /*LANG*/"Off",
|
onchange: v => alarm.on = v
|
||||||
onchange: v=>a.on=v
|
|
||||||
},
|
},
|
||||||
/*LANG*/'Repeat': {
|
/*LANG*/"Repeat": {
|
||||||
value: a.rp,
|
value: decodeDOW(alarm),
|
||||||
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
|
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => {
|
||||||
onchange: v => a.rp = v
|
alarm.rp = repeat;
|
||||||
},
|
alarm.dow = dow;
|
||||||
/*LANG*/'Days': {
|
alarm.t = require("time_utils").encodeTime(time);
|
||||||
value: "SMTWTFS".split("").map((d,n)=>a.dow&(1<<n)?d:".").join(""),
|
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
|
||||||
onchange: () => editDOW(a.dow, d => {
|
|
||||||
a.dow = d;
|
|
||||||
a.t = require("sched").encodeTime(t);
|
|
||||||
editAlarm(alarmIndex, a);
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
|
/*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v),
|
||||||
/*LANG*/'Auto Snooze': {
|
/*LANG*/"Auto Snooze": {
|
||||||
value: a.as,
|
value: alarm.as,
|
||||||
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
|
onchange: v => alarm.as = v
|
||||||
onchange: v => a.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();
|
E.showMenu(menu);
|
||||||
|
|
||||||
if (!newAlarm) {
|
|
||||||
menu[/*LANG*/"Delete"] = function () {
|
|
||||||
alarms.splice(alarmIndex, 1);
|
|
||||||
saveAndReload();
|
|
||||||
showMainMenu();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.showMenu(menu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveAlarm(newAlarm, alarmIndex, a, t) {
|
function showCustomDaysMenu(dow, dowChangeCallback, originalRepeat, originalDow) {
|
||||||
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);
|
|
||||||
|
|
||||||
const menu = {
|
const menu = {
|
||||||
'': { 'title': /*LANG*/'Timer' },
|
"": { "title": /*LANG*/"Custom Days" },
|
||||||
/*LANG*/'< Back': () => {
|
"< Back": () => {
|
||||||
saveTimer(newAlarm, alarmIndex, a, t);
|
// If the user unchecks all the days then we assume repeat = once
|
||||||
showMainMenu();
|
// and we force the dow to every day.
|
||||||
},
|
var repeat = dow > 0;
|
||||||
/*LANG*/'Hours': {
|
dowChangeCallback(repeat, repeat ? dow : EVERY_DAY)
|
||||||
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 ),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
menu[/*LANG*/"Cancel"] = () => showMainMenu();
|
require("date_utils").dows(firstDayOfWeek).forEach((day, i) => {
|
||||||
|
menu[day] = {
|
||||||
if (!newAlarm) {
|
value: !!(dow & (1 << (i + firstDayOfWeek))),
|
||||||
menu[/*LANG*/"Delete"] = function() {
|
onchange: v => v ? (dow |= 1 << (i + firstDayOfWeek)) : (dow &= ~(1 << (i + firstDayOfWeek)))
|
||||||
alarms.splice(alarmIndex,1);
|
|
||||||
saveAndReload();
|
|
||||||
showMainMenu();
|
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
return E.showMenu(menu);
|
|
||||||
|
menu[/*LANG*/"Cancel"] = () => setTimeout(showEditRepeatMenu, 10, originalRepeat, originalDow, dowChangeCallback)
|
||||||
|
|
||||||
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveTimer(newAlarm, alarmIndex, a, t) {
|
function showEditTimerMenu(selectedTimer, timerIndex) {
|
||||||
a.timer = require("sched").encodeTime(t);
|
var isNew = timerIndex === undefined;
|
||||||
a.t = getCurrentTime() + a.timer;
|
|
||||||
a.last = 0;
|
|
||||||
|
|
||||||
if (newAlarm) {
|
var timer = require("sched").newDefaultTimer();
|
||||||
alarms.push(a);
|
|
||||||
} else {
|
if (selectedTimer) {
|
||||||
alarms[alarmIndex] = a;
|
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) {
|
function enableAll(on) {
|
||||||
E.showPrompt(/*LANG*/"Are you sure?", {
|
if (alarms.filter(e => e.on == !on).length == 0) {
|
||||||
title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All"
|
E.showAlert(
|
||||||
}).then((confirm) => {
|
on ? /*LANG*/"Nothing to Enable" : /*LANG*/"Nothing to Disable",
|
||||||
if (confirm) {
|
on ? /*LANG*/"Enable All" : /*LANG*/"Disable All"
|
||||||
alarms.forEach(alarm => alarm.on = on);
|
).then(() => showAdvancedMenu());
|
||||||
saveAndReload();
|
} else {
|
||||||
}
|
E.showPrompt(/*LANG*/"Are you sure?", { title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All" }).then((confirm) => {
|
||||||
|
if (confirm) {
|
||||||
showMainMenu();
|
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() {
|
function deleteAll() {
|
||||||
E.showPrompt(/*LANG*/"Are you sure?", {
|
if (alarms.length == 0) {
|
||||||
title: /*LANG*/"Delete All"
|
E.showAlert(/*LANG*/"Nothing to delete", /*LANG*/"Delete All").then(() => showAdvancedMenu());
|
||||||
}).then((confirm) => {
|
} else {
|
||||||
if (confirm) {
|
E.showPrompt(/*LANG*/"Are you sure?", {
|
||||||
alarms = [];
|
title: /*LANG*/"Delete All"
|
||||||
saveAndReload();
|
}).then((confirm) => {
|
||||||
}
|
if (confirm) {
|
||||||
|
alarms = [];
|
||||||
showMainMenu();
|
saveAndReload();
|
||||||
});
|
showMainMenu();
|
||||||
|
} else {
|
||||||
|
showAdvancedMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showMainMenu();
|
showMainMenu();
|
||||||
|
|
|
@ -2,16 +2,29 @@
|
||||||
"id": "alarm",
|
"id": "alarm",
|
||||||
"name": "Alarms & Timers",
|
"name": "Alarms & Timers",
|
||||||
"shortName": "Alarms",
|
"shortName": "Alarms",
|
||||||
"version": "0.24",
|
"version": "0.33",
|
||||||
"description": "Set alarms and timers on your Bangle",
|
"description": "Set alarms and timers on your Bangle",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,alarm,widget",
|
"tags": "tool,alarm,widget",
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": [ "BANGLEJS", "BANGLEJS2" ],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"dependencies": {"scheduler":"type"},
|
"dependencies": { "scheduler":"type" },
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"alarm.app.js","url":"app.js"},
|
{ "name": "alarm.app.js", "url": "app.js" },
|
||||||
{"name":"alarm.img","url":"app-icon.js","evaluate":true},
|
{ "name": "alarm.img", "url": "app-icon.js", "evaluate": true },
|
||||||
{"name":"alarm.wid.js","url":"widget.js"}
|
{ "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" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 2.2 KiB |
|
@ -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);
|
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() {
|
},reload:function() {
|
||||||
// don't include library here as we're trying to use as little RAM as possible
|
// 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();
|
WIDGETS["alarm"].reload();
|
||||||
|
|
|
@ -7,3 +7,9 @@
|
||||||
0.06: Option to keep messages after a disconnect (default false) (fix #1186)
|
0.06: Option to keep messages after a disconnect (default false) (fix #1186)
|
||||||
0.07: Include charging state in battery updates to phone
|
0.07: Include charging state in battery updates to phone
|
||||||
0.08: Handling of alarms
|
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)
|
||||||
|
|
|
@ -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 Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js
|
||||||
keep any messages it has received, or should it delete them?
|
keep any messages it has received, or should it delete them?
|
||||||
* `Messages` - launches the messages app, showing a list of messages
|
* `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
|
## 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
|
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
|
## Testing
|
||||||
|
|
||||||
Bangle.js can only hold one connection open at a time, so it's hard to see
|
Bangle.js can only hold one connection open at a time, so it's hard to see
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
Bluetooth.println("");
|
Bluetooth.println("");
|
||||||
Bluetooth.println(JSON.stringify(message));
|
Bluetooth.println(JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
var lastMsg;
|
||||||
|
|
||||||
var settings = require("Storage").readJSON("android.settings.json",1)||{};
|
var settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||||
//default alarm settings
|
//default alarm settings
|
||||||
|
@ -18,7 +19,17 @@
|
||||||
/* TODO: Call handling, fitness */
|
/* TODO: Call handling, fitness */
|
||||||
var HANDLERS = {
|
var HANDLERS = {
|
||||||
// {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
|
// {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
|
// {t:"notify~",id:int, title:string} // modified
|
||||||
"notify~" : function() { event.t="modify";require("messages").pushMessage(event); },
|
"notify~" : function() { event.t="modify";require("messages").pushMessage(event); },
|
||||||
// {t:"notify-",id:int} // remove
|
// {t:"notify-",id:int} // remove
|
||||||
|
@ -67,26 +78,93 @@
|
||||||
var dow = event.d[j].rep;
|
var dow = event.d[j].rep;
|
||||||
if (!dow) dow = 127; //if no DOW selected, set alarm to all DOW
|
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 last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
|
||||||
var a = {
|
var a = require("sched").newDefaultAlarm();
|
||||||
id : "gb"+j,
|
a.id = "gb"+j;
|
||||||
appid : "gbalarms",
|
a.appid = "gbalarms";
|
||||||
on : true,
|
a.on = true;
|
||||||
t : event.d[j].h * 3600000 + event.d[j].m * 60000,
|
a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
|
||||||
dow : ((dow&63)<<1) | (dow>>6), // Gadgetbridge sends DOW in a different format
|
a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
|
||||||
last : last,
|
a.last = last;
|
||||||
rp : settings.rp,
|
|
||||||
as : settings.as,
|
|
||||||
vibrate : settings.vibrate
|
|
||||||
};
|
|
||||||
alarms.push(a);
|
alarms.push(a);
|
||||||
}
|
}
|
||||||
sched.setAlarms(alarms);
|
sched.setAlarms(alarms);
|
||||||
sched.reload();
|
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];
|
var h = HANDLERS[event.t];
|
||||||
if (h) h(); else console.log("GB Unknown",event);
|
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
|
// Battery monitor
|
||||||
function sendBattery() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
|
function sendBattery() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "android",
|
"id": "android",
|
||||||
"name": "Android Integration",
|
"name": "Android Integration",
|
||||||
"shortName": "Android",
|
"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.",
|
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||||
|
@ -15,6 +15,6 @@
|
||||||
{"name":"android.img","url":"app-icon.js","evaluate":true},
|
{"name":"android.img","url":"app-icon.js","evaluate":true},
|
||||||
{"name":"android.boot.js","url":"boot.js"}
|
{"name":"android.boot.js","url":"boot.js"}
|
||||||
],
|
],
|
||||||
"data": [{"name":"android.settings.json"}],
|
"data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}],
|
||||||
"sortorder": -8
|
"sortorder": -8
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,34 +18,12 @@
|
||||||
}),
|
}),
|
||||||
/*LANG*/"Keep Msgs" : {
|
/*LANG*/"Keep Msgs" : {
|
||||||
value : !!settings.keep,
|
value : !!settings.keep,
|
||||||
format : v=>v?/*LANG*/"Yes":/*LANG*/"No",
|
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.keep = v;
|
settings.keep = v;
|
||||||
updateSettings();
|
updateSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/*LANG*/"Messages" : ()=>load("messages.app.js"),
|
/*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);
|
E.showMenu(mainmenu);
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Fix bug if image clock wasn't installed
|
0.02: Fix bug if image clock wasn't installed
|
||||||
0.03: Update to use setUI
|
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.
|
||||||
|
|
|
@ -87,7 +87,6 @@ if (g.drawImages) {
|
||||||
draw();
|
draw();
|
||||||
var secondInterval = setInterval(draw,100);
|
var secondInterval = setInterval(draw,100);
|
||||||
// load widgets
|
// load widgets
|
||||||
Bangle.loadWidgets();
|
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
// Stop when LCD goes off
|
// Stop when LCD goes off
|
||||||
Bangle.on('lcdPower',on=>{
|
Bangle.on('lcdPower',on=>{
|
||||||
|
@ -104,3 +103,5 @@ if (g.drawImages) {
|
||||||
}
|
}
|
||||||
// Show launcher when button pressed
|
// Show launcher when button pressed
|
||||||
Bangle.setUI("clock");
|
Bangle.setUI("clock");
|
||||||
|
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "animclk",
|
"id": "animclk",
|
||||||
"name": "Animated Clock",
|
"name": "Animated Clock",
|
||||||
"shortName": "Anim 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",
|
"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",
|
"icon": "app.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
|
|
@ -10,3 +10,4 @@
|
||||||
week is buffered until date or timezone changes
|
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.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
|
0.08: fixed calendar weeknumber not shortened to two digits
|
||||||
|
0.09: Use default Bangle formatter for booleans
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "antonclk",
|
"id": "antonclk",
|
||||||
"name": "Anton Clock",
|
"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.",
|
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
|
||||||
"readme":"README.md",
|
"readme":"README.md",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
(function(back) {
|
(function(back) {
|
||||||
var FILE = "antonclk.json";
|
var FILE = "antonclk.json";
|
||||||
// Load settings
|
|
||||||
var settings = Object.assign({
|
var settings = Object.assign({
|
||||||
secondsOnUnlock: false,
|
secondsOnUnlock: false,
|
||||||
}, require('Storage').readJSON(FILE, true) || {});
|
}, require('Storage').readJSON(FILE, true) || {});
|
||||||
|
@ -41,7 +40,6 @@
|
||||||
"Date": stringInSettings("dateOnMain", ["Long", "Short", "ISO8601"]),
|
"Date": stringInSettings("dateOnMain", ["Long", "Short", "ISO8601"]),
|
||||||
"Show Weekday": {
|
"Show Weekday": {
|
||||||
value: (settings.weekDay !== undefined ? settings.weekDay : true),
|
value: (settings.weekDay !== undefined ? settings.weekDay : true),
|
||||||
format: v => v ? "On" : "Off",
|
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.weekDay = v;
|
settings.weekDay = v;
|
||||||
writeSettings();
|
writeSettings();
|
||||||
|
@ -49,7 +47,6 @@
|
||||||
},
|
},
|
||||||
"Show CalWeek": {
|
"Show CalWeek": {
|
||||||
value: (settings.calWeek !== undefined ? settings.calWeek : false),
|
value: (settings.calWeek !== undefined ? settings.calWeek : false),
|
||||||
format: v => v ? "On" : "Off",
|
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.calWeek = v;
|
settings.calWeek = v;
|
||||||
writeSettings();
|
writeSettings();
|
||||||
|
@ -57,7 +54,6 @@
|
||||||
},
|
},
|
||||||
"Uppercase": {
|
"Uppercase": {
|
||||||
value: (settings.upperCase !== undefined ? settings.upperCase : true),
|
value: (settings.upperCase !== undefined ? settings.upperCase : true),
|
||||||
format: v => v ? "On" : "Off",
|
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.upperCase = v;
|
settings.upperCase = v;
|
||||||
writeSettings();
|
writeSettings();
|
||||||
|
@ -65,7 +61,6 @@
|
||||||
},
|
},
|
||||||
"Vector font": {
|
"Vector font": {
|
||||||
value: (settings.vectorFont !== undefined ? settings.vectorFont : false),
|
value: (settings.vectorFont !== undefined ? settings.vectorFont : false),
|
||||||
format: v => v ? "On" : "Off",
|
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.vectorFont = v;
|
settings.vectorFont = v;
|
||||||
writeSettings();
|
writeSettings();
|
||||||
|
@ -82,7 +77,6 @@
|
||||||
"Show": stringInSettings("secondsMode", ["Never", "Unlocked", "Always"]),
|
"Show": stringInSettings("secondsMode", ["Never", "Unlocked", "Always"]),
|
||||||
"With \":\"": {
|
"With \":\"": {
|
||||||
value: (settings.secondsWithColon !== undefined ? settings.secondsWithColon : true),
|
value: (settings.secondsWithColon !== undefined ? settings.secondsWithColon : true),
|
||||||
format: v => v ? "On" : "Off",
|
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.secondsWithColon = v;
|
settings.secondsWithColon = v;
|
||||||
writeSettings();
|
writeSettings();
|
||||||
|
@ -90,7 +84,6 @@
|
||||||
},
|
},
|
||||||
"Color": {
|
"Color": {
|
||||||
value: (settings.secondsColoured !== undefined ? settings.secondsColoured : true),
|
value: (settings.secondsColoured !== undefined ? settings.secondsColoured : true),
|
||||||
format: v => v ? "On" : "Off",
|
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.secondsColoured = v;
|
settings.secondsColoured = v;
|
||||||
writeSettings();
|
writeSettings();
|
||||||
|
@ -99,9 +92,6 @@
|
||||||
"Date": stringInSettings("dateOnSecs", ["Year", "Weekday", "No"])
|
"Date": stringInSettings("dateOnSecs", ["Year", "Weekday", "No"])
|
||||||
};
|
};
|
||||||
|
|
||||||
// Actually display the menu
|
|
||||||
E.showMenu(mainmenu);
|
E.showMenu(mainmenu);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// end of file
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: Create astral clock app
|
0.01: Create astral clock app
|
||||||
0.02: Fixed Whirlpool galaxy RA/DA, larger compass display, fixed moonphase overlapping battery widget
|
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.03: Update to use Bangle.setUI instead of setWatch
|
||||||
|
0.04: Tell clock widgets to hide.
|
||||||
|
|
|
@ -767,6 +767,24 @@ function draw() {
|
||||||
g.clear();
|
g.clear();
|
||||||
current_moonphase = getMoonPhase();
|
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
|
// Load widgets
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
@ -799,23 +817,6 @@ Bangle.setGPSPower(1);
|
||||||
// Show launcher when button pressed
|
// Show launcher when button pressed
|
||||||
Bangle.setClockMode();
|
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 () {
|
setWatch(function () {
|
||||||
if (!astral_settings.astral_default) {
|
if (!astral_settings.astral_default) {
|
||||||
colours_switched = true;
|
colours_switched = true;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "astral",
|
"id": "astral",
|
||||||
"name": "Astral Clock",
|
"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.",
|
"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",
|
"icon": "app-icon.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
|
|
@ -7,3 +7,9 @@
|
||||||
0.07: Update to use Bangle.setUI instead of setWatch
|
0.07: Update to use Bangle.setUI instead of setWatch
|
||||||
0.08: Use theme colors, Layout library
|
0.08: Use theme colors, Layout library
|
||||||
0.09: Fix time/date disappearing after fullscreen notification
|
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
|
|
@ -4,3 +4,8 @@ A simple digital clock showing seconds as a horizontal bar.
|
||||||
| 24hr style | 12hr style |
|
| 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
|
|
@ -3,7 +3,6 @@
|
||||||
* A simple digital clock showing seconds as a bar
|
* A simple digital clock showing seconds as a bar
|
||||||
**/
|
**/
|
||||||
// Check settings for what type our clock should be
|
// Check settings for what type our clock should be
|
||||||
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
|
||||||
let locale = require("locale");
|
let locale = require("locale");
|
||||||
{ // add some more info to locale
|
{ // add some more info to locale
|
||||||
let date = new Date();
|
let date = new Date();
|
||||||
|
@ -11,51 +10,25 @@ let locale = require("locale");
|
||||||
date.setMonth(1, 3); // februari: months are zero-indexed
|
date.setMonth(1, 3); // februari: months are zero-indexed
|
||||||
const localized = locale.date(date, true);
|
const localized = locale.date(date, true);
|
||||||
locale.dayFirst = /3.*2/.test(localized);
|
locale.dayFirst = /3.*2/.test(localized);
|
||||||
|
locale.hasMeridian = (locale.meridian(date)!=="");
|
||||||
locale.hasMeridian = false;
|
|
||||||
if (typeof locale.meridian==="function") { // function does not exist if languages app is not installed
|
|
||||||
locale.hasMeridian = (locale.meridian(date)!=="");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Bangle.loadWidgets();
|
|
||||||
|
let barW = 0, prevX = 0;
|
||||||
function renderBar(l) {
|
function renderBar(l) {
|
||||||
if (!this.fraction) {
|
"ram";
|
||||||
// zero-size fillRect stills draws one line of pixels, we don't want that
|
if (l) prevX = 0; // called from Layout: drawing area was cleared
|
||||||
return;
|
else l = clock.layout.bar;
|
||||||
}
|
let x2 = l.x+barW;
|
||||||
const width = this.fraction*l.w;
|
if (clock.powerSave && Bangle.isLocked()) x2 = 0; // hide bar
|
||||||
g.fillRect(l.x, l.y, l.x+width-1, l.y+l.height-1);
|
if (x2===prevX) return; // nothing to do
|
||||||
|
if (x2===0) x2--; // don't leave 1px line
|
||||||
|
if (x2<Math.max(0, prevX)) g.setBgColor(l.bgCol || g.theme.bg).clearRect(x2+1, l.y, prevX, l.y2);
|
||||||
|
else g.setColor(l.col || g.theme.fg).fillRect(prevX+1, l.y, x2, l.y2);
|
||||||
|
prevX = x2;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Layout = require("Layout");
|
|
||||||
const layout = new Layout({
|
|
||||||
type: "v", c: [
|
|
||||||
{
|
|
||||||
type: "h", c: [
|
|
||||||
{id: "time", label: "88:88", type: "txt", font: "6x8:5", bgCol: g.theme.bg}, // size updated below
|
|
||||||
{id: "ampm", label: " ", type: "txt", font: "6x8:2", bgCol: g.theme.bg},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{id: "bar", type: "custom", fraction: 0, fillx: 1, height: 6, col: g.theme.fg2, render: renderBar},
|
|
||||||
{height: 40},
|
|
||||||
{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 (is12Hour) {
|
|
||||||
// Maximum font size = (<screen width> - <ampm: 2chars * (2*6)px>) / (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) {
|
function timeText(date) {
|
||||||
if (!is12Hour) {
|
if (!clock.is12Hour) {
|
||||||
return locale.time(date, true);
|
return locale.time(date, true);
|
||||||
}
|
}
|
||||||
const date12 = new Date(date.getTime());
|
const date12 = new Date(date.getTime());
|
||||||
|
@ -68,7 +41,7 @@ function timeText(date) {
|
||||||
return locale.time(date12, true);
|
return locale.time(date12, true);
|
||||||
}
|
}
|
||||||
function ampmText(date) {
|
function ampmText(date) {
|
||||||
return (is12Hour && locale.hasMeridian)? locale.meridian(date) : "";
|
return (clock.is12Hour && locale.hasMeridian) ? locale.meridian(date) : "";
|
||||||
}
|
}
|
||||||
function dateText(date) {
|
function dateText(date) {
|
||||||
const dayName = locale.dow(date, true),
|
const dayName = locale.dow(date, true),
|
||||||
|
@ -78,31 +51,74 @@ function dateText(date) {
|
||||||
return `${dayName} ${dayMonth}`;
|
return `${dayName} ${dayMonth}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
draw = function draw(force) {
|
const ClockFace = require("ClockFace"),
|
||||||
if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled
|
clock = new ClockFace({
|
||||||
const date = new Date();
|
precision: 1,
|
||||||
layout.time.label = timeText(date);
|
settingsFile: "barclock.settings.json",
|
||||||
layout.ampm.label = ampmText(date);
|
init: function() {
|
||||||
layout.date.label = dateText(date);
|
const Layout = require("Layout");
|
||||||
const SECONDS_PER_MINUTE = 60;
|
this.layout = new Layout({
|
||||||
layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE;
|
type: "v", c: [
|
||||||
if (force) {
|
{
|
||||||
Bangle.drawWidgets();
|
type: "h", c: [
|
||||||
layout.forgetLazyState();
|
{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},
|
||||||
layout.render();
|
],
|
||||||
// schedule update at start of next second
|
},
|
||||||
const millis = date.getMilliseconds();
|
{id: "bar", type: "custom", fillx: 1, height: 6, col: g.theme.fg2, render: renderBar},
|
||||||
setTimeout(draw, 1000-millis);
|
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 = (<screen width> - <ampm: 2chars * (2*6)px>) / (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
|
// power saving: only update once a minute while locked, hide bar
|
||||||
Bangle.setUI("clock");
|
if (clock.powerSave) {
|
||||||
Bangle.on("lcdPower", function(on) {
|
Bangle.on("lock", lock => {
|
||||||
if (on) {
|
clock.precision = lock ? 60 : 1;
|
||||||
draw(true);
|
clock.tick();
|
||||||
}
|
renderBar(); // hide/redraw bar right away
|
||||||
});
|
});
|
||||||
g.reset().clear();
|
}
|
||||||
Bangle.drawWidgets();
|
|
||||||
draw();
|
clock.start();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "barclock",
|
"id": "barclock",
|
||||||
"name": "Bar Clock",
|
"name": "Bar Clock",
|
||||||
"version": "0.09",
|
"version": "0.15",
|
||||||
"description": "A simple digital clock showing seconds as a bar",
|
"description": "A simple digital clock showing seconds as a bar",
|
||||||
"icon": "clock-bar.png",
|
"icon": "clock-bar.png",
|
||||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],
|
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],
|
||||||
|
@ -12,6 +12,10 @@
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"barclock.app.js","url":"clock-bar.js"},
|
{"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}
|
{"name":"barclock.img","url":"clock-bar-icon.js","evaluate":true}
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{"name":"barclock.settings.json"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
|
@ -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.
|
|
@ -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.
|