Merge branch 'espruino:master' into master
|
|
@ -1,2 +1,4 @@
|
|||
apps/animclk/V29.LBM.js
|
||||
apps/banglerun/rollup.config.js
|
||||
apps/schoolCalendar/fullcalendar/main.js
|
||||
apps/authentiwatch/qr_packed.js
|
||||
|
|
|
|||
23
README.md
|
|
@ -377,40 +377,37 @@ that handles configuring the app.
|
|||
When the app settings are opened, this function is called with one
|
||||
argument, `back`: a callback to return to the settings menu.
|
||||
|
||||
Usually it will save any information in `app.json` where `app` is the name
|
||||
Usually it will save any information in `myappid.json` where `myappid` is the name
|
||||
of your app - so you should change the example accordingly.
|
||||
|
||||
Example `settings.js`
|
||||
```js
|
||||
// make sure to enclose the function in parentheses
|
||||
(function(back) {
|
||||
let settings = require('Storage').readJSON('app.json',1)||{};
|
||||
function save(key, value) {
|
||||
settings[key] = value;
|
||||
require('Storage').write('app.json',settings);
|
||||
}
|
||||
function get(key, def) { return require('Settings').get('myappid', key, def); }
|
||||
function set(key, value) { require('Settings').set('myappid', key, value); }
|
||||
const appMenu = {
|
||||
'': {'title': 'App Settings'},
|
||||
'< Back': back,
|
||||
'Monkeys': {
|
||||
value: settings.monkeys||12,
|
||||
onchange: (m) => {save('monkeys', m)}
|
||||
value: get('monkeys', 12),
|
||||
onchange: (m) => set('monkeys', m)
|
||||
}
|
||||
};
|
||||
E.showMenu(appMenu)
|
||||
})
|
||||
```
|
||||
In this example the app needs to add `app.settings.js` to `storage` in `apps.json`.
|
||||
It should also add `app.json` to `data`, to make sure it is cleaned up when the app is uninstalled.
|
||||
In this example the app needs to add `myappid.settings.js` to `storage` in `apps.json`.
|
||||
It should also add `myappid.json` to `data`, to make sure it is cleaned up when the app is uninstalled.
|
||||
```json
|
||||
{ "id": "app",
|
||||
{ "id": "myappid",
|
||||
...
|
||||
"storage": [
|
||||
...
|
||||
{"name":"app.settings.js","url":"settings.js"},
|
||||
{"name":"myappid.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"app.json"}
|
||||
{"name":"myappid.json"}
|
||||
]
|
||||
},
|
||||
```
|
||||
|
|
|
|||
147
apps.json
|
|
@ -32,7 +32,7 @@
|
|||
{
|
||||
"id": "messages",
|
||||
"name": "Messages",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "App to display notifications from iOS and Gadgetbridge",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
{
|
||||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "(BETA) App to display notifications from Gadgetbridge on Android. This will eventually replace the Gadgetbridge widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,messages,notifications",
|
||||
|
|
@ -61,12 +61,12 @@
|
|||
{"name":"android.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"android.boot.js","url":"boot.js"}
|
||||
],
|
||||
"sortorder": -9
|
||||
"sortorder": -8
|
||||
},
|
||||
{
|
||||
"id": "ios",
|
||||
"name": "iOS Integration",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "(BETA) App to display notifications from iOS devices",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,ios,apple,messages,notifications",
|
||||
|
|
@ -77,12 +77,12 @@
|
|||
{"name":"ios.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"ios.boot.js","url":"boot.js"}
|
||||
],
|
||||
"sortorder": -9
|
||||
"sortorder": -8
|
||||
},
|
||||
{
|
||||
"id": "health",
|
||||
"name": "Health Tracking",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,health",
|
||||
|
|
@ -543,11 +543,11 @@
|
|||
{
|
||||
"id": "cubescramble",
|
||||
"name": "Cube Scramble",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "A random scramble generator for the 3x3 Rubik's cube",
|
||||
"icon": "cube-scramble.png",
|
||||
"tags": "",
|
||||
"supports" : ["BANGLEJS","BANGLEJS2"],
|
||||
"supports" : ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
|
@ -599,7 +599,7 @@
|
|||
{
|
||||
"id": "compass",
|
||||
"name": "Compass",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Simple compass that points North",
|
||||
"icon": "compass.png",
|
||||
"screenshots": [{"url":"screenshot_compass.png"}],
|
||||
|
|
@ -740,7 +740,7 @@
|
|||
"description": "Show currently installed apps, free space, and allow their deletion from the watch",
|
||||
"icon": "files.png",
|
||||
"tags": "tool,system,files",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"files.app.js","url":"files.js"},
|
||||
{"name":"files.img","url":"files-icon.js","evaluate":true}
|
||||
|
|
@ -749,12 +749,12 @@
|
|||
{
|
||||
"id": "weather",
|
||||
"name": "Weather",
|
||||
"version": "0.10",
|
||||
"version": "0.11",
|
||||
"description": "Show Gadgetbridge weather report",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"tags": "widget,outdoors",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "readme.md",
|
||||
"storage": [
|
||||
{"name":"weather.app.js","url":"app.js"},
|
||||
|
|
@ -768,11 +768,11 @@
|
|||
{
|
||||
"id": "chargeanim",
|
||||
"name": "Charge Animation",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "When charging, show a sideways charging animation and keep the screen on. When removed from the charger load the clock again.",
|
||||
"icon": "icon.png",
|
||||
"tags": "battery",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"chargeanim.app.js","url":"app.js"},
|
||||
|
|
@ -892,7 +892,7 @@
|
|||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"widchime.wid.js","url":"widget.js"},
|
||||
{"name":"widchime.settings.js","url":"settings.js"}
|
||||
|
|
@ -908,7 +908,7 @@
|
|||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"widram.wid.js","url":"widget.js"}
|
||||
]
|
||||
|
|
@ -2639,14 +2639,16 @@
|
|||
{
|
||||
"id": "magnav",
|
||||
"name": "Navigation Compass",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Compass with linear display as for GPSNAV. Has Tilt compensation and remembers calibration.",
|
||||
"screenshots": [{"url":"screenshot-b2.png"},{"url":"screenshot-light-b2.png"}],
|
||||
"icon": "magnav.png",
|
||||
"tags": "tool,outdoors",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"magnav.app.js","url":"magnav.min.js"},
|
||||
{"name":"magnav.app.js","url":"magnav_b1.js","supports":["BANGLEJS"]},
|
||||
{"name":"magnav.app.js","url":"magnav_b2.js","supports":["BANGLEJS2"]},
|
||||
{"name":"magnav.img","url":"magnav-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"magnav.json"}]
|
||||
|
|
@ -2730,8 +2732,9 @@
|
|||
{
|
||||
"id": "multiclock",
|
||||
"name": "Multi Clock",
|
||||
"version": "0.08",
|
||||
"description": "Clock with multiple faces. Switch between faces with BTN1 & BTN3 or swipe left-right. For best display set theme Background 2 to cyan or some other bright colour in settings.",
|
||||
"version": "0.09",
|
||||
"description": "Clock with multiple faces. Switch between faces with BTN1 & BTN3 (Bangle 2 touch top-right, bottom right). For best display set theme Background 2 to cyan or some other bright colour in settings.",
|
||||
"screenshots": [{"url":"screen-ana.png"},{"url":"screen-big.png"},{"url":"screen-td.png"},{"url":"screen-nifty.png"},{"url":"screen-word.png"},{"url":"screen-sec.png"}],
|
||||
"icon": "multiclock.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
|
|
@ -3127,15 +3130,17 @@
|
|||
{
|
||||
"id": "dtlaunch",
|
||||
"name": "Desktop Launcher",
|
||||
"version": "0.04",
|
||||
"description": "Desktop style App Launcher with six apps per page - fast access if you have lots of apps installed.",
|
||||
"version": "0.05",
|
||||
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
|
||||
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
||||
"icon": "icon.png",
|
||||
"type": "launch",
|
||||
"tags": "tool,system,launcher",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"dtlaunch.app.js","url":"app.js"},
|
||||
{"name":"dtlaunch.app.js","url":"app-b1.js", "supports": ["BANGLEJS"]},
|
||||
{"name":"dtlaunch.app.js","url":"app-b2.js", "supports": ["BANGLEJS2"]},
|
||||
{"name":"dtlaunch.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
|
|
@ -3644,15 +3649,15 @@
|
|||
"id": "gbmusic",
|
||||
"name": "Gadgetbridge Music Controls",
|
||||
"shortName": "Music Controls",
|
||||
"version": "0.05",
|
||||
"version": "0.07",
|
||||
"description": "Control the music on your Gadgetbridge-connected phone",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_2.png"}],
|
||||
"screenshots": [{"url":"screenshot_v1.png"},{"url":"screenshot_v2.png"}],
|
||||
"type": "app",
|
||||
"tags": "tools,bluetooth,gadgetbridge,music",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": false,
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"gbmusic.app.js","url":"app.js"},
|
||||
{"name":"gbmusic.settings.js","url":"settings.js"},
|
||||
|
|
@ -3718,12 +3723,12 @@
|
|||
"id": "qmsched",
|
||||
"name": "Quiet Mode Schedule and Widget",
|
||||
"shortName": "Quiet Mode",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Automatically turn Quiet Mode on or off at set times",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot_edit.png"},{"url":"screenshot_main.png"},{"url":"screenshot_widget_alarms.png"},{"url":"screenshot_widget_silent.png"}],
|
||||
"tags": "tool,widget",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"qmsched","url":"lib.js"},
|
||||
|
|
@ -4040,7 +4045,7 @@
|
|||
{
|
||||
"id": "antonclk",
|
||||
"name": "Anton Clock",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "A simple clock using the bold Anton font.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
|
@ -4201,7 +4206,7 @@
|
|||
{
|
||||
"id": "swiperclocklaunch",
|
||||
"name": "Swiper Clock Launch",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Navigate between clock and launcher with Swipe action",
|
||||
"icon": "swiperclocklaunch.png",
|
||||
"type": "bootloader",
|
||||
|
|
@ -4235,11 +4240,18 @@
|
|||
"id": "emojuino",
|
||||
"name": "Emojuino",
|
||||
"shortName": "Emojuino",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Emojis & Espruino: broadcast Unicode emojis via Bluetooth Low Energy.",
|
||||
"icon": "emojuino.png",
|
||||
"screenshots": [
|
||||
{ "url": "screenshot-tx.png" },
|
||||
{ "url": "screenshot-swipe.png" },
|
||||
{ "url": "screenshot-welcome.png" }
|
||||
],
|
||||
"type": "app",
|
||||
"tags": "emoji",
|
||||
"supports" : [ "BANGLEJS2" ],
|
||||
"allow_emulator": true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{ "name": "emojuino.app.js", "url": "emojuino.js" },
|
||||
|
|
@ -4250,8 +4262,8 @@
|
|||
"id": "cliclockJS2Enhanced",
|
||||
"name": "Commandline-Clock JS2 Enhanced",
|
||||
"shortName": "CLI-Clock JS2",
|
||||
"version": "0.1",
|
||||
"description": "Simple CLI-Styled Clock with enhancements. Modes that are hard to use and unneded are removed (BPM, battery info, memory ect) credit to hughbarney for the original code and design",
|
||||
"version": "0.02",
|
||||
"description": "Simple CLI-Styled Clock with enhancements. Modes that are hard to use and unneded are removed (BPM, battery info, memory ect) credit to hughbarney for the original code and design. Also added HID media controlls, just swipe on the clock face to controll the media! Gadgetbride support coming soon(hopefully) Thanks to t0m1o1 for media controls!",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screengrab.png"}],
|
||||
"type": "clock",
|
||||
|
|
@ -4264,30 +4276,31 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "a_battery_widget",
|
||||
"id": "wid_a_battery_widget",
|
||||
"name": "A Battery Widget (with percentage)",
|
||||
"shortName":"A Battery Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"1.0",
|
||||
"version":"1.01",
|
||||
"type": "widget",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"description": "Simple and slim battery widget with charge status and percentage",
|
||||
"tags": "widget,battery",
|
||||
"storage": [
|
||||
{"name":"a_battery_widget.wid.js","url":"widget.js"}
|
||||
{"name":"wid_a_battery_widget.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{
|
||||
{
|
||||
"id": "lcars",
|
||||
"name": "LCARS Clock",
|
||||
"shortName":"LCARS",
|
||||
"icon": "lcars.png",
|
||||
"version":"0.01",
|
||||
"version":"0.04",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"storage": [
|
||||
{"name":"lcars.app.js","url":"lcars.app.js"},
|
||||
{"name":"lcars.img","url":"lcars.icon.js","evaluate":true}
|
||||
|
|
@ -4311,5 +4324,57 @@
|
|||
{"name":"binwatch.Background240_center.img","url":"Background240_center.img"},
|
||||
{"name":"binwatch.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hidmsicswipe",
|
||||
"name": "Bluetooth Music Swipe Controls",
|
||||
"shortName": "Swipe Control",
|
||||
"version": "0.01",
|
||||
"description": "Based on the original Bluetooth Music Controls. Swipe up/down for volume, left/right for previous and next, tap for play/pause and btn1 to lock and unlock the controls. Enable HID in settings, pair with your phone, then use this app to control music from your watch!",
|
||||
"icon": "hidmsicswipe.png",
|
||||
"tags": "bluetooth",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"hidmsicswipe.app.js","url":"hidmsicswipe.js"},
|
||||
{"name":"hidmsicswipe.img","url":"hidmsicswipe-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "authentiwatch",
|
||||
"name": "2FA Authenticator",
|
||||
"shortName": "AuthWatch",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"version": "0.01",
|
||||
"description": "Google Authenticator compatible tool.",
|
||||
"tags": "tool",
|
||||
"interface": "interface.html",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"authentiwatch.app.js","url":"app.js"},
|
||||
{"name":"authentiwatch.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"authentiwatch.json"}]
|
||||
},
|
||||
{ "id": "schoolCalendar",
|
||||
"name": "School Calendar",
|
||||
"shortName":"SCalendar",
|
||||
"icon": "CalenderLogo.png",
|
||||
"version": "0.01",
|
||||
"description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)",
|
||||
"tags": "tool",
|
||||
"readme":"README.md",
|
||||
"custom":"custom.html",
|
||||
"supports": ["BANGLEJS"],
|
||||
"screenshots": [{"url":"screenshot_basic.png"},{"url":"screenshot_info.png"}],
|
||||
"storage": [
|
||||
{"name":"schoolCalendar.app.js"},
|
||||
{"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"app.json"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -152,6 +152,8 @@
|
|||
"no-prototype-builtins": "off",
|
||||
"no-redeclare": "off",
|
||||
"no-unreachable": "warn",
|
||||
"no-cond-assign": "warn",
|
||||
"no-useless-catch": "warn",
|
||||
// TODO: "no-undef": "warn",
|
||||
"no-undef": "off",
|
||||
"no-unused-vars": "off",
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
2021/11/18 | 1.0: Release for Bangle 2
|
||||
|
|
@ -1 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Remove messages on disconnect
|
||||
Fix music control
|
||||
|
|
|
|||
|
|
@ -33,7 +33,13 @@
|
|||
// {t:"musicinfo", artist,album,track,dur,c(track count),n(track num}
|
||||
"musicinfo" : function() {
|
||||
require("messages").pushMessage(Object.assign(event, {t:"modify",id:"music",title:"Music"}));
|
||||
}
|
||||
},
|
||||
// {"t":"call","cmd":"incoming/end","name":"Bob","number":"12421312"})
|
||||
"call" : function() {
|
||||
event.t=t.cmd=="incoming"?"add":"remove";
|
||||
event.id="call";
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
};
|
||||
var h = HANDLERS[event.t];
|
||||
if (h) h(); else console.log("GB Unknown",event);
|
||||
|
|
@ -42,6 +48,7 @@
|
|||
// Battery monitor
|
||||
function sendBattery() { gbSend({ t: "status", bat: E.getBattery() }); }
|
||||
NRF.on("connect", () => setTimeout(sendBattery, 2000));
|
||||
NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect
|
||||
setInterval(sendBattery, 10*60*1000);
|
||||
// Health tracking
|
||||
Bangle.on('health', health=>{
|
||||
|
|
@ -50,6 +57,6 @@
|
|||
// Music control
|
||||
Bangle.musicControl = cmd => {
|
||||
// play/pause/next/previous/volumeup/volumedown
|
||||
gbSend({ t: "music", m:cmd });
|
||||
gbSend({ t: "music", n:cmd });
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Load widgets after setUI so widclk knows when to hide
|
||||
0.03: Clock now shows day of week under date.
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ function draw() {
|
|||
var date = new Date();
|
||||
var timeStr = require("locale").time(date,1);
|
||||
var dateStr = require("locale").date(date).toUpperCase();
|
||||
var dowStr = require("locale").dow(date).toUpperCase();
|
||||
// draw time
|
||||
g.setFontAlign(0,0).setFont("Anton");
|
||||
g.clearRect(0,y-40,g.getWidth(),y+35); // clear the background
|
||||
|
|
@ -32,6 +33,10 @@ function draw() {
|
|||
g.setFontAlign(0,0).setFont("6x8",2);
|
||||
g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background
|
||||
g.drawString(dateStr,x,y);
|
||||
//draw day of week
|
||||
y += 16;
|
||||
g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background
|
||||
g.drawString(dowStr,x,y);
|
||||
// queue draw in one minute
|
||||
queueDraw();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: First release
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# Authentiwatch - 2FA Authenticator
|
||||
|
||||
## Supports
|
||||
|
||||
* Google Authenticator compatible 2-factor authentication
|
||||
* Hash calculations:
|
||||
* Bangle 1: SHA1 only (same as Google Authenticator)
|
||||
* Bangle 2: All (SHA1, SHA256, SHA512)
|
||||
* Timed (TOTP) and Counter (HOTP) modes
|
||||
* Custom periods
|
||||
* Between 6 and 10 digits
|
||||
* Phone/PC configuration web page:
|
||||
* Add/edit/delete/arrange tokens
|
||||
* Scan QR codes
|
||||
* Produce scannable QR codes
|
||||
|
||||
## Usage
|
||||
|
||||
* Use the Phone/PC web page interface to manage the tokens stored on the watch.
|
||||
* Tokens are stored *ONLY* on the watch.
|
||||
* Swipe right to exit to the app launcher.
|
||||
* Swipe left on selected counter token to advance the counter to the next value.
|
||||
|
||||
## Creator
|
||||
|
||||
Andrew Gregory (andrew.gregory at gmail)
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mUywkBiIADCxoTFAAcQGBwY/DDQIKDBiMDDCgGCBI4YMGAIDFDCAFEBQwYLFgIYEGQgYMApoYJGAJjFMogYMSQgCDDBwDCY4oMEDBZgHHQQYQf4oYVBgwYQBogYPPYZpFDBKMEDAbdDCxT9IDYIFFABqSEAogySQYoWNFgrFDJZoQBJggYRBwhLGDBwyFDCZGEDCYAEDGrIMbwhnGDEpLGAwxlLFQgQDJiYoFDDAZDDCpMDMpQOCNxQYNBo4KKBpwYYBYJ8NeJgYkLBQY8UYQXVGQIwN"))
|
||||
|
|
@ -0,0 +1,300 @@
|
|||
const tokenentryheight = 46;
|
||||
// Hash functions
|
||||
const crypto = require("crypto");
|
||||
const algos = {
|
||||
"SHA512":{sha:crypto.SHA512,retsz:64,blksz:128},
|
||||
"SHA256":{sha:crypto.SHA256,retsz:32,blksz:64 },
|
||||
"SHA1" :{sha:crypto.SHA1 ,retsz:20,blksz:64 },
|
||||
};
|
||||
|
||||
var tokens = require("Storage").readJSON("authentiwatch.json", true) || [];
|
||||
|
||||
// QR Code Text
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// otpauth://totp/${url}:AA_${algorithm}_${digits}dig_${period}s@${url}?algorithm=${algorithm}&digits=${digits}&issuer=${url}&period=${period}&secret=${secret}
|
||||
//
|
||||
// ${algorithm} : one of SHA1 / SHA256 / SHA512
|
||||
// ${digits} : one of 6 / 8
|
||||
// ${period} : one of 30 / 60
|
||||
// ${url} : a domain name "example.com"
|
||||
// ${secret} : the seed code
|
||||
|
||||
function b32decode(seedstr) {
|
||||
// RFC4648
|
||||
var i, buf = 0, bitcount = 0, retstr = "";
|
||||
for (i in seedstr) {
|
||||
var c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".indexOf(seedstr.charAt(i).toUpperCase(), 0);
|
||||
if (c != -1) {
|
||||
buf <<= 5;
|
||||
buf |= c;
|
||||
bitcount += 5;
|
||||
if (bitcount >= 8) {
|
||||
retstr += String.fromCharCode(buf >> (bitcount - 8));
|
||||
buf &= (0xFF >> (16 - bitcount));
|
||||
bitcount -= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bitcount > 0) {
|
||||
retstr += String.fromCharCode(buf << (8 - bitcount));
|
||||
}
|
||||
var retbuf = new Uint8Array(retstr.length);
|
||||
for (i in retstr) {
|
||||
retbuf[i] = retstr.charCodeAt(i);
|
||||
}
|
||||
return retbuf;
|
||||
}
|
||||
function do_hmac(key, message, algo) {
|
||||
var a = algos[algo];
|
||||
// RFC2104
|
||||
if (key.length > a.blksz) {
|
||||
key = a.sha(key);
|
||||
}
|
||||
var istr = new Uint8Array(a.blksz + message.length);
|
||||
var ostr = new Uint8Array(a.blksz + a.retsz);
|
||||
for (var i = 0; i < a.blksz; ++i) {
|
||||
var c = (i < key.length) ? key[i] : 0;
|
||||
istr[i] = c ^ 0x36;
|
||||
ostr[i] = c ^ 0x5C;
|
||||
}
|
||||
istr.set(message, a.blksz);
|
||||
ostr.set(a.sha(istr), a.blksz);
|
||||
var ret = a.sha(ostr);
|
||||
// RFC4226 dynamic truncation
|
||||
var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4);
|
||||
return v.getUint32(0) & 0x7FFFFFFF;
|
||||
}
|
||||
function hotp(token) {
|
||||
var tick;
|
||||
var d = new Date();
|
||||
if (token.period > 0) {
|
||||
// RFC6238 - timed
|
||||
var seconds = Math.floor(d.getTime() / 1000);
|
||||
tick = Math.floor(seconds / token.period);
|
||||
} else {
|
||||
// RFC4226 - counter
|
||||
tick = -token.period;
|
||||
}
|
||||
var msg = new Uint8Array(8);
|
||||
var v = new DataView(msg.buffer);
|
||||
v.setUint32(0, tick >> 16 >> 16);
|
||||
v.setUint32(4, tick & 0xFFFFFFFF);
|
||||
var ret = "";
|
||||
try {
|
||||
var hash = do_hmac(b32decode(token.secret), msg, token.algorithm.toUpperCase());
|
||||
ret = "" + hash % Math.pow(10, token.digits);
|
||||
while (ret.length < token.digits) {
|
||||
ret = "0" + ret;
|
||||
}
|
||||
} catch(err) {
|
||||
ret = "Not supported";
|
||||
}
|
||||
return {hotp:ret, next:((token.period > 0) ? ((tick + 1) * token.period * 1000) : d.getTime() + 30000)};
|
||||
}
|
||||
|
||||
var state = {
|
||||
listy: 0,
|
||||
prevcur:0,
|
||||
curtoken:-1,
|
||||
nextTime:0,
|
||||
otp:"",
|
||||
rem:0,
|
||||
hide:0
|
||||
};
|
||||
|
||||
function drawToken(id, r) {
|
||||
var x1 = r.x;
|
||||
var y1 = r.y;
|
||||
var x2 = r.x + r.w - 1;
|
||||
var y2 = r.y + r.h - 1;
|
||||
var adj;
|
||||
g.setClipRect(Math.max(x1, Bangle.appRect.x ), Math.max(y1, Bangle.appRect.y ),
|
||||
Math.min(x2, Bangle.appRect.x2), Math.min(y2, Bangle.appRect.y2));
|
||||
if (id == state.curtoken) {
|
||||
// current token
|
||||
g.setColor(g.theme.fgH);
|
||||
g.setBgColor(g.theme.bgH);
|
||||
g.setFont("Vector", 16);
|
||||
// center just below top line
|
||||
g.setFontAlign(0, -1, 0);
|
||||
adj = y1;
|
||||
} else {
|
||||
g.setColor(g.theme.fg);
|
||||
g.setBgColor(g.theme.bg);
|
||||
g.setFont("Vector", 30);
|
||||
// center in box
|
||||
g.setFontAlign(0, 0, 0);
|
||||
adj = (y1 + y2) / 2;
|
||||
}
|
||||
g.clearRect(x1, y1, x2, y2);
|
||||
g.drawString(tokens[id].label, (x1 + x2) / 2, adj, false);
|
||||
if (id == state.curtoken) {
|
||||
if (tokens[id].period > 0) {
|
||||
// timed - draw progress bar
|
||||
let xr = Math.floor(Bangle.appRect.w * state.rem / tokens[id].period);
|
||||
g.fillRect(x1, y2 - 4, xr, y2 - 1);
|
||||
adj = 0;
|
||||
} else {
|
||||
// counter - draw triangle as swipe hint
|
||||
let yc = (y1 + y2) / 2;
|
||||
g.fillPoly([0, yc, 10, yc - 10, 10, yc + 10, 0, yc]);
|
||||
adj = 5;
|
||||
}
|
||||
// digits just below label
|
||||
g.setFont("Vector", (state.otp.length > 8) ? 26 : 30);
|
||||
g.drawString(state.otp, (x1 + x2) / 2 + adj, y1 + 16, false);
|
||||
}
|
||||
// shaded lines top and bottom
|
||||
g.setColor(0.5, 0.5, 0.5);
|
||||
g.drawLine(x1, y1, x2, y1);
|
||||
g.drawLine(x1, y2, x2, y2);
|
||||
g.setClipRect(0, 0, g.getWidth(), g.getHeight());
|
||||
}
|
||||
|
||||
function draw() {
|
||||
var d = new Date();
|
||||
if (state.curtoken != -1) {
|
||||
var t = tokens[state.curtoken];
|
||||
if (d.getTime() > state.nextTime) {
|
||||
if (state.hide == 0) {
|
||||
// auto-hide the current token
|
||||
if (state.curtoken != -1) {
|
||||
state.prevcur = state.curtoken;
|
||||
state.curtoken = -1;
|
||||
}
|
||||
state.nextTime = 0;
|
||||
} else {
|
||||
// time to generate a new token
|
||||
var r = hotp(t);
|
||||
state.nextTime = r.next;
|
||||
state.otp = r.hotp;
|
||||
if (t.period <= 0) {
|
||||
state.hide = 1;
|
||||
}
|
||||
state.hide--;
|
||||
}
|
||||
}
|
||||
state.rem = Math.max(0, Math.floor((state.nextTime - d.getTime()) / 1000));
|
||||
}
|
||||
if (tokens.length > 0) {
|
||||
var drewcur = false;
|
||||
var id = Math.floor(state.listy / tokenentryheight);
|
||||
var y = id * tokenentryheight + Bangle.appRect.y - state.listy;
|
||||
while (id < tokens.length && y < Bangle.appRect.y2) {
|
||||
drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:tokenentryheight});
|
||||
if (id == state.curtoken && (tokens[id].period <= 0 || state.nextTime != 0)) {
|
||||
drewcur = true;
|
||||
}
|
||||
id += 1;
|
||||
y += tokenentryheight;
|
||||
}
|
||||
if (drewcur) {
|
||||
// the current token has been drawn - draw it again in 1sec
|
||||
if (state.drawtimer) {
|
||||
clearTimeout(state.drawtimer);
|
||||
}
|
||||
state.drawtimer = setTimeout(draw, (tokens[state.curtoken].period > 0) ? 1000 : state.nexttime - d.getTime());
|
||||
if (tokens[state.curtoken].period <= 0) {
|
||||
state.hide = 0;
|
||||
}
|
||||
} else {
|
||||
// de-select the current token if it is scrolled out of view
|
||||
if (state.curtoken != -1) {
|
||||
state.prevcur = state.curtoken;
|
||||
state.curtoken = -1;
|
||||
}
|
||||
state.nexttime = 0;
|
||||
}
|
||||
} else {
|
||||
g.setFont("Vector", 30);
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.drawString("No tokens", Bangle.appRect.x + Bangle.appRect.w / 2,Bangle.appRect.y + Bangle.appRect.h / 2, false);
|
||||
}
|
||||
}
|
||||
|
||||
function onTouch(zone, e) {
|
||||
if (e) {
|
||||
var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / tokenentryheight);
|
||||
if (id == state.curtoken || tokens.length == 0) {
|
||||
id = -1;
|
||||
}
|
||||
if (state.curtoken != id) {
|
||||
if (id != -1) {
|
||||
var y = id * tokenentryheight - state.listy;
|
||||
if (y < 0) {
|
||||
state.listy += y;
|
||||
y = 0;
|
||||
}
|
||||
y += tokenentryheight;
|
||||
if (y > Bangle.appRect.h) {
|
||||
state.listy += (y - Bangle.appRect.h);
|
||||
}
|
||||
}
|
||||
state.nextTime = 0;
|
||||
state.curtoken = id;
|
||||
state.hide = 2;
|
||||
draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onDrag(e) {
|
||||
if (e.x > g.getWidth() || e.y > g.getHeight()) return;
|
||||
if (e.dx == 0 && e.dy == 0) return;
|
||||
var newy = Math.min(state.listy - e.dy, tokens.length * tokenentryheight - Bangle.appRect.h);
|
||||
newy = Math.max(0, newy);
|
||||
if (newy != state.listy) {
|
||||
state.listy = newy;
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
function onSwipe(e) {
|
||||
if (e == 1) {
|
||||
Bangle.showLauncher();
|
||||
}
|
||||
if (e == -1 && state.curtoken != -1 && tokens[state.curtoken].period <= 0) {
|
||||
tokens[state.curtoken].period--;
|
||||
require("Storage").writeJSON("authentiwatch.json", tokens);
|
||||
state.nextTime = 0;
|
||||
state.hide = 2;
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
function bangle1Btn(e) {
|
||||
if (tokens.length > 0) {
|
||||
if (state.curtoken == -1) {
|
||||
state.curtoken = state.prevcur;
|
||||
} else {
|
||||
switch (e) {
|
||||
case -1: state.curtoken--; break;
|
||||
case 1: state.curtoken++; break;
|
||||
}
|
||||
}
|
||||
state.curtoken = Math.max(state.curtoken, 0);
|
||||
state.curtoken = Math.min(state.curtoken, tokens.length - 1);
|
||||
var fakee = {};
|
||||
fakee.y = state.curtoken * tokenentryheight - state.listy + Bangle.appRect.y;
|
||||
state.curtoken = -1;
|
||||
state.nextTime = 0;
|
||||
onTouch(0, fakee);
|
||||
}
|
||||
}
|
||||
|
||||
Bangle.on('touch', onTouch);
|
||||
Bangle.on('drag' , onDrag );
|
||||
Bangle.on('swipe', onSwipe);
|
||||
if (typeof BTN2 == 'number') {
|
||||
setWatch(function(){bangle1Btn(-1); }, BTN1, {edge:"rising", debounce:50, repeat:true});
|
||||
setWatch(function(){Bangle.showLauncher();}, BTN2, {edge:"rising", debounce:50, repeat:true});
|
||||
setWatch(function(){bangle1Btn( 1); }, BTN3, {edge:"rising", debounce:50, repeat:true});
|
||||
}
|
||||
Bangle.loadWidgets();
|
||||
|
||||
// Clear the screen once, at startup
|
||||
g.clear();
|
||||
draw();
|
||||
Bangle.drawWidgets();
|
||||
|
After Width: | Height: | Size: 964 B |
|
|
@ -0,0 +1,373 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"/>
|
||||
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
<style type="text/css">
|
||||
body{font-family:sans-serif}
|
||||
body div{display:none}
|
||||
body.select div#tokens,body.editing div#edit,body.scanning div#scan,body.showqr div#tokenqr{display:block}
|
||||
#tokens th,#tokens td{padding:5px}
|
||||
#tokens tr:nth-child(odd){background-color:#ccc}
|
||||
#tokens tr:nth-child(even){background-color:#eee}
|
||||
#qr-canvas{margin:auto;width:calc(100%-20px);max-width:400px}
|
||||
#advbtn,#scan,#tokenqr table{text-align:center}
|
||||
#edittoken tbody#adv{display:none}
|
||||
#edittoken.showadv tbody#adv{display:table-row-group}
|
||||
#advbtn button:before,#advbtn button:after{content:"\25bc"}
|
||||
#edittoken.showadv #advbtn button:before,#edittoken.showadv #advbtn button:after{content:"\25b2"}
|
||||
button{height:3em}
|
||||
table button{width:100%}
|
||||
form.totp tr.hotp,form.hotp tr.totp{display:none}
|
||||
</style>
|
||||
|
||||
<!-- https://rawgit.com/sitepoint-editors/jsqrcode/master/src/qr_packed.js -->
|
||||
<script src="qr_packed.js"></script>
|
||||
|
||||
<!-- https://davidshimjs.github.io/qrcodejs/ -->
|
||||
<script src="../../core/lib/qrcode.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
/* Start of all TOTP URLs */
|
||||
const otpAuthUrl = 'otpauth://';
|
||||
|
||||
const tokentypes = ['TOTP (Timed)', 'HOTP (Counter)'];
|
||||
|
||||
/* Array of TOTP tokens */
|
||||
var tokens=[];
|
||||
|
||||
/* Remove any non-base-32 characters from the given string and collapses
|
||||
* whitespace to a single space. Optionally removes all whitespace from
|
||||
* the string.
|
||||
*/
|
||||
function base32clean(val, nows) {
|
||||
var ret = val.replaceAll(/\s+/g, ' ');
|
||||
ret = ret.replaceAll(/[^A-Za-z2-7 ]/g, '');
|
||||
if (nows) {
|
||||
ret = ret.replaceAll(/\s+/g, '');
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Save changes to a token to the global tokens[] array.
|
||||
* id is the index into the global tokens[].
|
||||
* forget is a flag indicating if the token should be forgotten.
|
||||
*/
|
||||
function saveEdit(id, forget) {
|
||||
if (forget) {
|
||||
if (confirm('Forget token?')) {
|
||||
tokens.splice(id, 1);
|
||||
updateTokens();
|
||||
}
|
||||
} else {
|
||||
let fe = document.forms['edittoken'].elements;
|
||||
let d = parseInt(fe['digits'].value);
|
||||
let p = parseInt(fe['period'].value);
|
||||
let c = parseInt(fe['count'].value);
|
||||
switch (fe['type'].value) {
|
||||
case tokentypes[1]: p = (c > 0) ? -c : 0; break;
|
||||
default : p = (p > 0) ? p : 30; break;
|
||||
}
|
||||
tokens[id] = {
|
||||
'algorithm':fe['algorithm'].value,
|
||||
'digits':((d > 0) ? d : 6),
|
||||
'period':p,
|
||||
'issuer':fe['issuer'].value,
|
||||
'account':fe['account'].value,
|
||||
'secret':base32clean(fe['secret'].value, false),
|
||||
'label':fe['label'].value
|
||||
};
|
||||
updateTokens();
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate and display a QR-code representing the current token.
|
||||
*/
|
||||
function showQrCode() {
|
||||
var fe = document.forms['edittoken'].elements;
|
||||
var url = new String(otpAuthUrl);
|
||||
switch (fe['type'].value) {
|
||||
case tokentypes[1]: url += 'hotp/'; break;
|
||||
default : url += 'totp/'; break;
|
||||
}
|
||||
if (fe['account'].value.length > 0) {
|
||||
url += encodeURIComponent(fe['account'].value);
|
||||
} else {
|
||||
url += encodeURIComponent(fe['label'].value);
|
||||
}
|
||||
url += '?';
|
||||
if (fe['issuer'].value.length > 0) {
|
||||
url += 'issuer=' + encodeURIComponent(fe['issuer'].value);
|
||||
url += '&';
|
||||
}
|
||||
url += 'secret=' + base32clean(fe['secret'].value, true);
|
||||
switch (fe['type'].value) {
|
||||
case tokentypes[1]:
|
||||
if (parseInt(fe['count'].value) != 0) {
|
||||
url += '&counter=' + fe['count'].value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (parseInt(fe['period'].value) != 30) {
|
||||
url += '&period=' + fe['period'].value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (parseInt(fe['digits'].value) != 6) {
|
||||
url += '&digits=' + fe['digits'].value;
|
||||
}
|
||||
if (fe['algorithm'].value != 'SHA1') {
|
||||
url += '&algorithm=' + fe['algorithm'].value;
|
||||
}
|
||||
tokenqr.clear();
|
||||
tokenqr.makeCode(url);
|
||||
document.body.className = 'showqr';
|
||||
}
|
||||
|
||||
function onTypeChanged() {
|
||||
var f = document.forms['edittoken'];
|
||||
var fe = f.elements;
|
||||
if (fe['type'].value == tokentypes[0]) { f.classList.add('totp'); f.classList.remove('hotp'); }
|
||||
if (fe['type'].value == tokentypes[1]) { f.classList.add('hotp'); f.classList.remove('totp'); }
|
||||
}
|
||||
|
||||
/* Generate a form for editing the specified token.
|
||||
* id is the index into the global tokens[].
|
||||
*/
|
||||
function editToken(id) {
|
||||
var p;
|
||||
const selectMarkup = function(name, ary, cur, onchg) {
|
||||
var ret = '<select name="' + name + '"' + ((typeof onchg == 'string') ? ' onchange="' + onchg + '"' : '') + '>';
|
||||
for (let i = 0; i < ary.length; i++) {
|
||||
ret += '<option' + ((ary[i] == cur) ? ' selected=selected' : '') + '>' + ary[i] + '</option>';
|
||||
}
|
||||
return ret + '</select>';
|
||||
};
|
||||
scanning=false;
|
||||
var markup = '<form id="edittoken"><input type="hidden" name="tokenid" value="' + id + '"><table>';
|
||||
markup += '<tr><td>Name:</td><td><input name="label" type="text" value="' + tokens[id].label + '"></td></tr>';
|
||||
markup += '<tr><td>Secret:</td><td><input name="secret" type="text" value="' + tokens[id].secret + '"></td></tr>';
|
||||
markup += '<tbody id="adv">';
|
||||
markup += '<tr><td>Account:</td><td><input name="account" type="text" value="' + tokens[id].account + '"></td></tr>';
|
||||
markup += '<tr><td>Issuer:</td><td><input name="issuer" type="text" value="' + tokens[id].issuer + '"></td></tr>';
|
||||
p = parseInt(tokens[id].period);
|
||||
markup += '<tr><td>Type:</td><td>';
|
||||
markup += selectMarkup('type', tokentypes, (tokens[id].period > 0) ? tokentypes[0] : tokentypes[1], 'onTypeChanged()');
|
||||
markup += '</td></tr>';
|
||||
markup += '<tr class="totp"><td>Period:</td><td><input name="period" type="text" value="' + ((p > 0) ? p : 30) + '"></td></tr>';
|
||||
markup += '<tr class="hotp"><td>Count:</td><td><input name="count" type="text" value="' + ((p >= 0) ? 0 : -p) + '"></td></tr>';
|
||||
markup += '<tr><td>Digits:</td><td>';
|
||||
markup += selectMarkup('digits', ['6','7','8','9','10'], tokens[id].digits);
|
||||
markup += '</td></tr>';
|
||||
markup += '<tr><td>Hash:</td><td>';
|
||||
markup += selectMarkup('algorithm', ['SHA1','SHA256','SHA512'], tokens[id].algorithm);
|
||||
markup += '</td></tr>';
|
||||
markup += '</tbody><tr><td id="advbtn" colspan="2">';
|
||||
markup += '<button type="button" onclick="document.getElementById(\'edittoken\').classList.toggle(\'showadv\')">Advanced</button>';
|
||||
markup += '</td></tr></table></form>';
|
||||
markup += '<button type="button" onclick="updateTokens()">Cancel Edit</button>';
|
||||
markup += '<button type="button" onclick="saveEdit(' + id + ', false)">Save Changes</button>';
|
||||
if (tokens[id].isnew) {
|
||||
markup += '<button type="button" onclick="startScan()">Scan QR Code</button>';
|
||||
} else {
|
||||
markup += '<button type="button" onclick="showQrCode()">Show QR Code</button>';
|
||||
markup += '<button type="button" onclick="saveEdit(' + id + ', true)">Forget Token</button>';
|
||||
}
|
||||
document.getElementById('edit').innerHTML = markup;
|
||||
document.body.className = 'editing';
|
||||
onTypeChanged();
|
||||
}
|
||||
|
||||
/* Create a new blank token and open the editor for it.
|
||||
*/
|
||||
function addToken() {
|
||||
tokens[tokens.length] = {'algorithm':'SHA1','digits':6,'period':30,'issuer':'','account':'','secret':'','label':'','isnew':true};
|
||||
editToken(tokens.length - 1);
|
||||
}
|
||||
|
||||
/* Move the specified token up or down in the global tokens[].
|
||||
* id is the index in the global tokens[] of the token to move.
|
||||
* dir is the direction to move: -1=up, 1=down.
|
||||
*/
|
||||
function moveToken(id, dir) {
|
||||
tokens.splice(id + dir, 0, tokens.splice(id, 1)[0]);
|
||||
updateTokens();
|
||||
}
|
||||
|
||||
/* Update the display listing all the tokens.
|
||||
*/
|
||||
function updateTokens() {
|
||||
const tokenButton = function(fn, id, label, dir) {
|
||||
return '<button type="button" onclick="' + fn + '(' + id + (dir ? ',' + dir : '') + ')">' + label + '</button>';
|
||||
};
|
||||
var markup = '<table><tr><th>Token</th><th colspan="2">Order</th></tr>';
|
||||
/* any tokens marked new are cancelled new additions and must be removed */
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
if (tokens[i].isnew) {
|
||||
tokens.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
markup += '<tr><td>';
|
||||
markup += tokenButton('editToken', i, tokens[i].label);
|
||||
markup += '</td><td>';
|
||||
if (i < (tokens.length - 1)) {
|
||||
markup += tokenButton('moveToken', i, '▼', 1);
|
||||
}
|
||||
markup += '</td><td>';
|
||||
if (i > 0) {
|
||||
markup += tokenButton('moveToken', i, '▲', -1);
|
||||
}
|
||||
markup += '</td></tr>';
|
||||
}
|
||||
markup += '</table>';
|
||||
markup += '<button type="button" onclick="addToken()">Add Token</button>';
|
||||
markup += '<button type="button" onclick="saveTokens()">Save to watch</button>';
|
||||
document.getElementById('tokens').innerHTML = markup;
|
||||
document.body.className = 'select';
|
||||
}
|
||||
|
||||
/* Original QR-code reader: https://www.sitepoint.com/create-qr-code-reader-mobile-website/ */
|
||||
qrcode.callback = res => {
|
||||
if (res) {
|
||||
if (res.startsWith(otpAuthUrl)) {
|
||||
res = decodeURIComponent(res);
|
||||
var paramsidx = res.indexOf('?');
|
||||
var params = res.substr(paramsidx+1).split('&');
|
||||
var t = {
|
||||
'algorithm':'SHA1',
|
||||
'digits':'6',
|
||||
'counter':'0',
|
||||
'period':'30',
|
||||
'secret':'',
|
||||
'issuer':''
|
||||
};
|
||||
var otpok = true;
|
||||
for (let pi in params) {
|
||||
var param = params[pi].split('=');
|
||||
if (param[0] in t) {
|
||||
t[param[0]] = param[1];
|
||||
} else {
|
||||
otpok = false;
|
||||
}
|
||||
}
|
||||
t['account'] = res.substring(res.lastIndexOf('/', paramsidx)+1, paramsidx);
|
||||
if ((t['account'] == '') || (t['secret'] == '')) {
|
||||
otpok = false;
|
||||
}
|
||||
if (otpok) {
|
||||
scanning = false;
|
||||
editToken(parseInt(document.forms['edittoken'].elements['tokenid'].value));
|
||||
t['label'] = (t['issuer'] == '') ? t['account'] : t['issuer'] + ' (' + t['account'] + ')';
|
||||
var fe = document.forms['edittoken'].elements;
|
||||
if (res.startsWith(otpAuthUrl + 'hotp/')) {
|
||||
t['period'] = '30';
|
||||
fe['type'].value = tokentypes[1];
|
||||
} else {
|
||||
t['counter'] = '0';
|
||||
fe['type'].value = tokentypes[0];
|
||||
}
|
||||
fe['algorithm'].value = t['algorithm'];
|
||||
fe['digits' ].value = t['digits' ];
|
||||
fe['count' ].value = t['counter' ];
|
||||
fe['period' ].value = t['period' ];
|
||||
fe['secret' ].value = t['secret' ];
|
||||
fe['issuer' ].value = t['issuer' ];
|
||||
fe['account' ].value = t['account' ];
|
||||
fe['label' ].value = t['label' ];
|
||||
onTypeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
function startScan() {
|
||||
document.body.className = 'scanning';
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({video:{facingMode:'environment'}})
|
||||
.then(function(stream){
|
||||
scanning=true;
|
||||
video.setAttribute('playsinline',true);
|
||||
video.srcObject = stream;
|
||||
video.play();
|
||||
scanTick();
|
||||
doScan();
|
||||
});
|
||||
}
|
||||
function scanTick() {
|
||||
canvasElement.height = video.videoHeight;
|
||||
canvasElement.width = video.videoWidth;
|
||||
canvas.drawImage(video,0,0,canvasElement.width,canvasElement.height);
|
||||
if (scanning) {
|
||||
requestAnimationFrame(scanTick);
|
||||
} else {
|
||||
video.srcObject.getTracks().forEach(track => {
|
||||
track.stop();
|
||||
});
|
||||
}
|
||||
}
|
||||
function doScan() {
|
||||
try {
|
||||
qrcode.decode();
|
||||
} catch (e) {
|
||||
setTimeout(doScan,300);
|
||||
}
|
||||
}
|
||||
|
||||
/* Load settings JSON file from the watch.
|
||||
*/
|
||||
function loadTokens() {
|
||||
Util.showModal('Loading...');
|
||||
Puck.eval(`require('Storage').read(${JSON.stringify('authentiwatch.json')})`,data=>{
|
||||
Util.hideModal();
|
||||
try {
|
||||
tokens = JSON.parse(data);
|
||||
updateTokens();
|
||||
} catch {
|
||||
tokens = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
/* Save settings as a JSON file on the watch.
|
||||
*/
|
||||
function saveTokens() {
|
||||
Util.showModal('Saving...');
|
||||
Puck.write(`\x10require('Storage').write(${JSON.stringify('authentiwatch.json')},${JSON.stringify(tokens)})\n`,()=>{
|
||||
Util.hideModal();
|
||||
});
|
||||
}
|
||||
function onInit() {
|
||||
loadTokens();
|
||||
updateTokens();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="select">
|
||||
<h1>Authentiwatch</h1>
|
||||
<div id="tokens">
|
||||
<p>No watch comms.</p>
|
||||
</div>
|
||||
<div id="scan">
|
||||
<table>
|
||||
<tr><td><canvas id="qr-canvas"></canvas></td></tr>
|
||||
<tr><td><button type="button" onclick="editToken(parseInt(document.forms['edittoken'].elements['tokenid'].value))">Cancel</button></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="edit">
|
||||
</div>
|
||||
<div id="tokenqr">
|
||||
<table><tr><td id="qrcode"></td></tr><tr><td>
|
||||
<button type="button" onclick="document.body.className='editing'">Back</button>
|
||||
</td></tr></table>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
const video=document.createElement('video');
|
||||
const canvasElement=document.getElementById('qr-canvas');
|
||||
const canvas=canvasElement.getContext('2d');
|
||||
let scanning=false;
|
||||
const tokenqr=new QRCode(document.getElementById('qrcode'), '');
|
||||
</script>
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
/* Packed with Google Closure
|
||||
*
|
||||
* Ported to JavaScript by Lazar Laszlo 2011
|
||||
* lazarsoft@gmail.com, www.lazarsoft.info
|
||||
*
|
||||
* Copyright 2007 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
var qrcode=function(){"use strict";function a(h,b){this.count=h;this.dataCodewords=b;this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("DataCodewords",function(){return this.dataCodewords})}function f(h,b,e){this.ecCodewordsPerBlock=h;this.ecBlocks=e?[b,e]:Array(b);this.__defineGetter__("ECCodewordsPerBlock",function(){return this.ecCodewordsPerBlock});this.__defineGetter__("TotalECCodewords",function(){return this.ecCodewordsPerBlock*this.NumBlocks});this.__defineGetter__("NumBlocks",
|
||||
function(){for(var d=0,c=0;c<this.ecBlocks.length;c++)d+=this.ecBlocks[c].length;return d});this.getECBlocks=function(){return this.ecBlocks}}function k(h,b,e,d,c,a){this.versionNumber=h;this.alignmentPatternCenters=b;this.ecBlocks=[e,d,c,a];h=0;b=e.ECCodewordsPerBlock;e=e.getECBlocks();for(d=0;d<e.length;d++)c=e[d],h+=c.Count*(c.DataCodewords+b);this.totalCodewords=h;this.__defineGetter__("VersionNumber",function(){return this.versionNumber});this.__defineGetter__("AlignmentPatternCenters",function(){return this.alignmentPatternCenters});
|
||||
this.__defineGetter__("TotalCodewords",function(){return this.totalCodewords});this.__defineGetter__("DimensionForVersion",function(){return 17+4*this.versionNumber});this.buildFunctionPattern=function(){var d=this.DimensionForVersion,c=new I(d);c.setRegion(0,0,9,9);c.setRegion(d-8,0,8,9);c.setRegion(0,d-8,9,8);for(var e=this.alignmentPatternCenters.length,b=0;b<e;b++)for(var a=this.alignmentPatternCenters[b]-2,h=0;h<e;h++)0==b&&(0==h||h==e-1)||b==e-1&&0==h||c.setRegion(this.alignmentPatternCenters[h]-
|
||||
2,a,5,5);c.setRegion(6,9,1,d-17);c.setRegion(9,6,d-17,1);6<this.versionNumber&&(c.setRegion(d-11,0,3,6),c.setRegion(0,d-11,6,3));return c};this.getECBlocksForLevel=function(c){return this.ecBlocks[c.ordinal()]}}function z(h,b,e,d,c,a,l,m,f){this.a11=h;this.a12=d;this.a13=l;this.a21=b;this.a22=c;this.a23=m;this.a31=e;this.a32=a;this.a33=f;this.transformPoints1=function(c){for(var d=c.length,e=this.a11,b=this.a12,h=this.a13,a=this.a21,l=this.a22,p=this.a23,m=this.a31,f=this.a32,g=this.a33,y=0;y<d;y+=
|
||||
2){var q=c[y],k=c[y+1],n=h*q+p*k+g;c[y]=(e*q+a*k+m)/n;c[y+1]=(b*q+l*k+f)/n}};this.transformPoints2=function(c,d){for(var e=c.length,b=0;b<e;b++){var h=c[b],a=d[b],l=this.a13*h+this.a23*a+this.a33;c[b]=(this.a11*h+this.a21*a+this.a31)/l;d[b]=(this.a12*h+this.a22*a+this.a32)/l}};this.buildAdjoint=function(){return new z(this.a22*this.a33-this.a23*this.a32,this.a23*this.a31-this.a21*this.a33,this.a21*this.a32-this.a22*this.a31,this.a13*this.a32-this.a12*this.a33,this.a11*this.a33-this.a13*this.a31,this.a12*
|
||||
this.a31-this.a11*this.a32,this.a12*this.a23-this.a13*this.a22,this.a13*this.a21-this.a11*this.a23,this.a11*this.a22-this.a12*this.a21)};this.times=function(c){return new z(this.a11*c.a11+this.a21*c.a12+this.a31*c.a13,this.a11*c.a21+this.a21*c.a22+this.a31*c.a23,this.a11*c.a31+this.a21*c.a32+this.a31*c.a33,this.a12*c.a11+this.a22*c.a12+this.a32*c.a13,this.a12*c.a21+this.a22*c.a22+this.a32*c.a23,this.a12*c.a31+this.a22*c.a32+this.a32*c.a33,this.a13*c.a11+this.a23*c.a12+this.a33*c.a13,this.a13*c.a21+
|
||||
this.a23*c.a22+this.a33*c.a23,this.a13*c.a31+this.a23*c.a32+this.a33*c.a33)}}function P(h,b){this.bits=h;this.points=b}function Q(h){this.image=h;this.resultPointCallback=null;this.sizeOfBlackWhiteBlackRun=function(b,e,d,c){var h=Math.abs(c-e)>Math.abs(d-b);if(h){var a=b;b=e;e=a;a=d;d=c;c=a}for(var m=Math.abs(d-b),f=Math.abs(c-e),q=-m>>1,k=e<c?1:-1,x=b<d?1:-1,v=0,t=b,a=e;t!=d;t+=x){var J=h?a:t,n=h?t:a;1==v?this.image[J+n*g.width]&&v++:this.image[J+n*g.width]||v++;if(3==v)return c=t-b,e=a-e,Math.sqrt(c*
|
||||
c+e*e);q+=f;if(0<q){if(a==c)break;a+=k;q-=m}}b=d-b;e=c-e;return Math.sqrt(b*b+e*e)};this.sizeOfBlackWhiteBlackRunBothWays=function(b,e,d,c){var a=this.sizeOfBlackWhiteBlackRun(b,e,d,c),h=1;d=b-(d-b);0>d?(h=b/(b-d),d=0):d>=g.width&&(h=(g.width-1-b)/(d-b),d=g.width-1);c=Math.floor(e-(c-e)*h);h=1;0>c?(h=e/(e-c),c=0):c>=g.height&&(h=(g.height-1-e)/(c-e),c=g.height-1);d=Math.floor(b+(d-b)*h);a+=this.sizeOfBlackWhiteBlackRun(b,e,d,c);return a-1};this.calculateModuleSizeOneWay=function(b,e){var d=this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(b.X),
|
||||
Math.floor(b.Y),Math.floor(e.X),Math.floor(e.Y)),c=this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(e.X),Math.floor(e.Y),Math.floor(b.X),Math.floor(b.Y));return isNaN(d)?c/7:isNaN(c)?d/7:(d+c)/14};this.calculateModuleSize=function(b,e,d){return(this.calculateModuleSizeOneWay(b,e)+this.calculateModuleSizeOneWay(b,d))/2};this.distance=function(b,e){var d=b.X-e.X,c=b.Y-e.Y;return Math.sqrt(d*d+c*c)};this.computeDimension=function(b,e,d,c){e=Math.round(this.distance(b,e)/c);b=Math.round(this.distance(b,
|
||||
d)/c);b=(e+b>>1)+7;switch(b&3){case 0:b++;break;case 2:b--;break;case 3:throw"Error";}return b};this.findAlignmentInRegion=function(b,e,d,c){c=Math.floor(c*b);var h=Math.max(0,e-c);e=Math.min(g.width-1,e+c);if(e-h<3*b)throw"Error";var a=Math.max(0,d-c);return(new R(this.image,h,a,e-h,Math.min(g.height-1,d+c)-a,b,this.resultPointCallback)).find()};this.createTransform=function(b,e,d,c,h){h-=3.5;var a;if(null!=c){var p=c.X;c=c.Y;var f=a=h-3}else p=e.X-b.X+d.X,c=e.Y-b.Y+d.Y,f=a=h;return z.quadrilateralToQuadrilateral(3.5,
|
||||
3.5,h,3.5,f,a,3.5,h,b.X,b.Y,e.X,e.Y,p,c,d.X,d.Y)};this.sampleGrid=function(b,e,d){return F.sampleGrid3(b,d,e)};this.processFinderPatternInfo=function(b){var e=b.TopLeft,d=b.TopRight;b=b.BottomLeft;var c=this.calculateModuleSize(e,d,b);if(1>c)throw"Error";var h=this.computeDimension(e,d,b,c),a=k.getProvisionalVersionForDimension(h),m=a.DimensionForVersion-7,f=null;if(0<a.AlignmentPatternCenters.length)for(a=1-3/m,f=Math.floor(e.X+a*(d.X-e.X+b.X-e.X)),a=Math.floor(e.Y+a*(d.Y-e.Y+b.Y-e.Y));;){f=this.findAlignmentInRegion(c,
|
||||
f,a,4);break}c=this.createTransform(e,d,b,f,h);h=this.sampleGrid(this.image,c,h);return new P(h,null==f?[b,e,d]:[b,e,d,f])};this.detect=function(){var b=(new S).findFinderPattern(this.image);return this.processFinderPatternInfo(b)}}function r(h){this.errorCorrectionLevel=C.forBits(h>>3&3);this.dataMask=h&7;this.__defineGetter__("ErrorCorrectionLevel",function(){return this.errorCorrectionLevel});this.__defineGetter__("DataMask",function(){return this.dataMask});this.GetHashCode=function(){return this.errorCorrectionLevel.ordinal()<<
|
||||
3|this.dataMask};this.Equals=function(b){return this.errorCorrectionLevel==b.errorCorrectionLevel&&this.dataMask==b.dataMask}}function C(h,b,e){this.ordinal_Renamed_Field=h;this.bits=b;this.name=e;this.__defineGetter__("Bits",function(){return this.bits});this.__defineGetter__("Name",function(){return this.name});this.ordinal=function(){return this.ordinal_Renamed_Field}}function I(h,b){b||(b=h);if(1>h||1>b)throw"Both dimensions must be greater than 0";this.width=h;this.height=b;var e=h>>5;0!=(h&
|
||||
31)&&e++;this.rowSize=e;this.bits=Array(e*b);for(e=0;e<this.bits.length;e++)this.bits[e]=0;this.__defineGetter__("Width",function(){return this.width});this.__defineGetter__("Height",function(){return this.height});this.__defineGetter__("Dimension",function(){if(this.width!=this.height)throw"Can't call getDimension() on a non-square matrix";return this.width});this.get_Renamed=function(d,c){return 0!=(u(this.bits[c*this.rowSize+(d>>5)],d&31)&1)};this.set_Renamed=function(d,c){this.bits[c*this.rowSize+
|
||||
(d>>5)]|=1<<(d&31)};this.flip=function(d,c){this.bits[c*this.rowSize+(d>>5)]^=1<<(d&31)};this.clear=function(){for(var d=this.bits.length,c=0;c<d;c++)this.bits[c]=0};this.setRegion=function(d,c,e,b){if(0>c||0>d)throw"Left and top must be nonnegative";if(1>b||1>e)throw"Height and width must be at least 1";e=d+e;b=c+b;if(b>this.height||e>this.width)throw"The region must fit inside the matrix";for(;c<b;c++)for(var h=c*this.rowSize,a=d;a<e;a++)this.bits[h+(a>>5)]|=1<<(a&31)}}function G(a,b){this.numDataCodewords=
|
||||
a;this.codewords=b;this.__defineGetter__("NumDataCodewords",function(){return this.numDataCodewords});this.__defineGetter__("Codewords",function(){return this.codewords})}function T(a){var b=a.Dimension;if(21>b||1!=(b&3))throw"Error BitMatrixParser";this.bitMatrix=a;this.parsedFormatInfo=this.parsedVersion=null;this.copyBit=function(e,d,c){return this.bitMatrix.get_Renamed(e,d)?c<<1|1:c<<1};this.readFormatInformation=function(){if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;for(var e=
|
||||
0,d=0;6>d;d++)e=this.copyBit(d,8,e);e=this.copyBit(7,8,e);e=this.copyBit(8,8,e);e=this.copyBit(8,7,e);for(d=5;0<=d;d--)e=this.copyBit(8,d,e);this.parsedFormatInfo=r.decodeFormatInformation(e);if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;for(var c=this.bitMatrix.Dimension,e=0,b=c-8,d=c-1;d>=b;d--)e=this.copyBit(d,8,e);for(d=c-7;d<c;d++)e=this.copyBit(8,d,e);this.parsedFormatInfo=r.decodeFormatInformation(e);if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;throw"Error readFormatInformation";
|
||||
};this.readVersion=function(){if(null!=this.parsedVersion)return this.parsedVersion;var e=this.bitMatrix.Dimension,d=e-17>>2;if(6>=d)return k.getVersionForNumber(d);for(var d=0,c=e-11,b=5;0<=b;b--)for(var a=e-9;a>=c;a--)d=this.copyBit(a,b,d);this.parsedVersion=k.decodeVersionInformation(d);if(null!=this.parsedVersion&&this.parsedVersion.DimensionForVersion==e)return this.parsedVersion;d=0;for(a=5;0<=a;a--)for(b=e-9;b>=c;b--)d=this.copyBit(a,b,d);this.parsedVersion=k.decodeVersionInformation(d);if(null!=
|
||||
this.parsedVersion&&this.parsedVersion.DimensionForVersion==e)return this.parsedVersion;throw"Error readVersion";};this.readCodewords=function(){var b=this.readFormatInformation(),d=this.readVersion(),c=H.forReference(b.DataMask),b=this.bitMatrix.Dimension;c.unmaskBitMatrix(this.bitMatrix,b);for(var c=d.buildFunctionPattern(),a=!0,h=Array(d.TotalCodewords),m=0,f=0,g=0,k=b-1;0<k;k-=2){6==k&&k--;for(var x=0;x<b;x++)for(var v=a?b-1-x:x,t=0;2>t;t++)c.get_Renamed(k-t,v)||(g++,f<<=1,this.bitMatrix.get_Renamed(k-
|
||||
t,v)&&(f|=1),8==g&&(h[m++]=f,f=g=0));a^=1}if(m!=d.TotalCodewords)throw"Error readCodewords";return h}}function w(a,b){if(null==b||0==b.length)throw"System.ArgumentException";this.field=a;var e=b.length;if(1<e&&0==b[0]){for(var d=1;d<e&&0==b[d];)d++;if(d==e)this.coefficients=a.Zero.coefficients;else{this.coefficients=Array(e-d);for(e=0;e<this.coefficients.length;e++)this.coefficients[e]=0;for(e=0;e<this.coefficients.length;e++)this.coefficients[e]=b[d+e]}}else this.coefficients=b;this.__defineGetter__("Zero",
|
||||
function(){return 0==this.coefficients[0]});this.__defineGetter__("Degree",function(){return this.coefficients.length-1});this.__defineGetter__("Coefficients",function(){return this.coefficients});this.getCoefficient=function(c){return this.coefficients[this.coefficients.length-1-c]};this.evaluateAt=function(c){if(0==c)return this.getCoefficient(0);var d=this.coefficients.length;if(1==c){for(var b=c=0;b<d;b++)c=n.addOrSubtract(c,this.coefficients[b]);return c}for(var e=this.coefficients[0],b=1;b<
|
||||
d;b++)e=n.addOrSubtract(this.field.multiply(c,e),this.coefficients[b]);return e};this.addOrSubtract=function(c){if(this.field!=c.field)throw"GF256Polys do not have same GF256 field";if(this.Zero)return c;if(c.Zero)return this;var d=this.coefficients;c=c.coefficients;if(d.length>c.length){var b=d,d=c;c=b}for(var b=Array(c.length),e=c.length-d.length,h=0;h<e;h++)b[h]=c[h];for(h=e;h<c.length;h++)b[h]=n.addOrSubtract(d[h-e],c[h]);return new w(a,b)};this.multiply1=function(c){if(this.field!=c.field)throw"GF256Polys do not have same GF256 field";
|
||||
if(this.Zero||c.Zero)return this.field.Zero;var d=this.coefficients,b=d.length;c=c.coefficients;for(var e=c.length,a=Array(b+e-1),h=0;h<b;h++)for(var f=d[h],g=0;g<e;g++)a[h+g]=n.addOrSubtract(a[h+g],this.field.multiply(f,c[g]));return new w(this.field,a)};this.multiply2=function(c){if(0==c)return this.field.Zero;if(1==c)return this;for(var d=this.coefficients.length,b=Array(d),e=0;e<d;e++)b[e]=this.field.multiply(this.coefficients[e],c);return new w(this.field,b)};this.multiplyByMonomial=function(c,
|
||||
d){if(0>c)throw"System.ArgumentException";if(0==d)return this.field.Zero;for(var b=this.coefficients.length,e=Array(b+c),a=0;a<e.length;a++)e[a]=0;for(a=0;a<b;a++)e[a]=this.field.multiply(this.coefficients[a],d);return new w(this.field,e)};this.divide=function(c){if(this.field!=c.field)throw"GF256Polys do not have same GF256 field";if(c.Zero)throw"Divide by 0";for(var d=this.field.Zero,b=this,e=c.getCoefficient(c.Degree),e=this.field.inverse(e);b.Degree>=c.Degree&&!b.Zero;)var a=b.Degree-c.Degree,
|
||||
h=this.field.multiply(b.getCoefficient(b.Degree),e),f=c.multiplyByMonomial(a,h),a=this.field.buildMonomial(a,h),d=d.addOrSubtract(a),b=b.addOrSubtract(f);return[d,b]}}function n(a){this.expTable=Array(256);this.logTable=Array(256);for(var b=1,e=0;256>e;e++)this.expTable[e]=b,b<<=1,256<=b&&(b^=a);for(e=0;255>e;e++)this.logTable[this.expTable[e]]=e;a=Array(1);a[0]=0;this.zero=new w(this,Array(a));a=Array(1);a[0]=1;this.one=new w(this,Array(a));this.__defineGetter__("Zero",function(){return this.zero});
|
||||
this.__defineGetter__("One",function(){return this.one});this.buildMonomial=function(d,c){if(0>d)throw"System.ArgumentException";if(0==c)return this.zero;for(var b=Array(d+1),e=0;e<b.length;e++)b[e]=0;b[0]=c;return new w(this,b)};this.exp=function(d){return this.expTable[d]};this.log=function(d){if(0==d)throw"System.ArgumentException";return this.logTable[d]};this.inverse=function(d){if(0==d)throw"System.ArithmeticException";return this.expTable[255-this.logTable[d]]};this.multiply=function(d,c){return 0==
|
||||
d||0==c?0:1==d?c:1==c?d:this.expTable[(this.logTable[d]+this.logTable[c])%255]}}function u(a,b){return 0<=a?a>>b:(a>>b)+(2<<~b)}function U(a,b,e){this.x=a;this.y=b;this.count=1;this.estimatedModuleSize=e;this.__defineGetter__("EstimatedModuleSize",function(){return this.estimatedModuleSize});this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("X",function(){return this.x});this.__defineGetter__("Y",function(){return this.y});this.incrementCount=function(){this.count++};
|
||||
this.aboutEquals=function(d,c,b){return Math.abs(c-this.y)<=d&&Math.abs(b-this.x)<=d?(d=Math.abs(d-this.estimatedModuleSize),1>=d||1>=d/this.estimatedModuleSize):!1}}function V(a){this.bottomLeft=a[0];this.topLeft=a[1];this.topRight=a[2];this.__defineGetter__("BottomLeft",function(){return this.bottomLeft});this.__defineGetter__("TopLeft",function(){return this.topLeft});this.__defineGetter__("TopRight",function(){return this.topRight})}function S(){this.image=null;this.possibleCenters=[];this.hasSkipped=
|
||||
!1;this.crossCheckStateCount=[0,0,0,0,0];this.resultPointCallback=null;this.__defineGetter__("CrossCheckStateCount",function(){this.crossCheckStateCount[0]=0;this.crossCheckStateCount[1]=0;this.crossCheckStateCount[2]=0;this.crossCheckStateCount[3]=0;this.crossCheckStateCount[4]=0;return this.crossCheckStateCount});this.foundPatternCross=function(a){for(var b=0,e=0;5>e;e++){var d=a[e];if(0==d)return!1;b+=d}if(7>b)return!1;b=Math.floor((b<<D)/7);e=Math.floor(b/2);return Math.abs(b-(a[0]<<D))<e&&Math.abs(b-
|
||||
(a[1]<<D))<e&&Math.abs(3*b-(a[2]<<D))<3*e&&Math.abs(b-(a[3]<<D))<e&&Math.abs(b-(a[4]<<D))<e};this.centerFromEnd=function(a,b){return b-a[4]-a[3]-a[2]/2};this.crossCheckVertical=function(a,b,e,d){for(var c=this.image,h=g.height,l=this.CrossCheckStateCount,m=a;0<=m&&c[b+m*g.width];)l[2]++,m--;if(0>m)return NaN;for(;0<=m&&!c[b+m*g.width]&&l[1]<=e;)l[1]++,m--;if(0>m||l[1]>e)return NaN;for(;0<=m&&c[b+m*g.width]&&l[0]<=e;)l[0]++,m--;if(l[0]>e)return NaN;for(m=a+1;m<h&&c[b+m*g.width];)l[2]++,m++;if(m==h)return NaN;
|
||||
for(;m<h&&!c[b+m*g.width]&&l[3]<e;)l[3]++,m++;if(m==h||l[3]>=e)return NaN;for(;m<h&&c[b+m*g.width]&&l[4]<e;)l[4]++,m++;return l[4]>=e||5*Math.abs(l[0]+l[1]+l[2]+l[3]+l[4]-d)>=2*d?NaN:this.foundPatternCross(l)?this.centerFromEnd(l,m):NaN};this.crossCheckHorizontal=function(a,b,e,d){for(var c=this.image,h=g.width,l=this.CrossCheckStateCount,m=a;0<=m&&c[m+b*g.width];)l[2]++,m--;if(0>m)return NaN;for(;0<=m&&!c[m+b*g.width]&&l[1]<=e;)l[1]++,m--;if(0>m||l[1]>e)return NaN;for(;0<=m&&c[m+b*g.width]&&l[0]<=
|
||||
e;)l[0]++,m--;if(l[0]>e)return NaN;for(m=a+1;m<h&&c[m+b*g.width];)l[2]++,m++;if(m==h)return NaN;for(;m<h&&!c[m+b*g.width]&&l[3]<e;)l[3]++,m++;if(m==h||l[3]>=e)return NaN;for(;m<h&&c[m+b*g.width]&&l[4]<e;)l[4]++,m++;return l[4]>=e||5*Math.abs(l[0]+l[1]+l[2]+l[3]+l[4]-d)>=d?NaN:this.foundPatternCross(l)?this.centerFromEnd(l,m):NaN};this.handlePossibleCenter=function(a,b,e){var d=a[0]+a[1]+a[2]+a[3]+a[4];e=this.centerFromEnd(a,e);b=this.crossCheckVertical(b,Math.floor(e),a[2],d);if(!isNaN(b)&&(e=this.crossCheckHorizontal(Math.floor(e),
|
||||
Math.floor(b),a[2],d),!isNaN(e))){a=d/7;for(var d=!1,c=this.possibleCenters.length,h=0;h<c;h++){var l=this.possibleCenters[h];if(l.aboutEquals(a,b,e)){l.incrementCount();d=!0;break}}d||(e=new U(e,b,a),this.possibleCenters.push(e),null!=this.resultPointCallback&&this.resultPointCallback.foundPossibleResultPoint(e));return!0}return!1};this.selectBestPatterns=function(){var a=this.possibleCenters.length;if(3>a)throw"Couldn't find enough finder patterns (found "+a+")";if(3<a){for(var b=0,e=0,d=0;d<a;d++)var c=
|
||||
this.possibleCenters[d].EstimatedModuleSize,b=b+c,e=e+c*c;var p=b/a;this.possibleCenters.sort(function(c,d){var b=Math.abs(d.EstimatedModuleSize-p),e=Math.abs(c.EstimatedModuleSize-p);return b<e?-1:b==e?0:1});a=Math.max(.2*p,Math.sqrt(e/a-p*p));for(d=this.possibleCenters.length-1;0<=d;d--)Math.abs(this.possibleCenters[d].EstimatedModuleSize-p)>a&&this.possibleCenters.splice(d,1)}3<this.possibleCenters.length&&this.possibleCenters.sort(function(c,d){return c.count>d.count?-1:c.count<d.count?1:0});
|
||||
return[this.possibleCenters[0],this.possibleCenters[1],this.possibleCenters[2]]};this.findRowSkip=function(){var a=this.possibleCenters.length;if(1>=a)return 0;for(var b=null,e=0;e<a;e++){var d=this.possibleCenters[e];if(d.Count>=K)if(null==b)b=d;else return this.hasSkipped=!0,Math.floor((Math.abs(b.X-d.X)-Math.abs(b.Y-d.Y))/2)}return 0};this.haveMultiplyConfirmedCenters=function(){for(var a,b=0,e=0,d=this.possibleCenters.length,c=0;c<d;c++)a=this.possibleCenters[c],a.Count>=K&&(b++,e+=a.EstimatedModuleSize);
|
||||
if(3>b)return!1;for(var b=e/d,p=0,c=0;c<d;c++)a=this.possibleCenters[c],p+=Math.abs(a.EstimatedModuleSize-b);return p<=.05*e};this.findFinderPattern=function(a){var b;this.image=a;var e=g.height,d=g.width,c=Math.floor(3*e/(4*W));c<L&&(c=L);for(var h=!1,l=Array(5),m=c-1;m<e&&!h;m+=c){l[0]=0;l[1]=0;l[2]=0;l[3]=0;for(var f=b=l[4]=0;f<d;f++)if(a[f+m*g.width])1==(b&1)&&b++,l[b]++;else if(0==(b&1))if(4==b)if(this.foundPatternCross(l)){if(b=this.handlePossibleCenter(l,m,f))c=2,this.hasSkipped?h=this.haveMultiplyConfirmedCenters():
|
||||
(b=this.findRowSkip(),b>l[2]&&(m+=b-l[2]-c,f=d-1));else{do f++;while(f<d&&!a[f+m*g.width]);f--}b=0;l[0]=0;l[1]=0;l[2]=0;l[3]=0;l[4]=0}else l[0]=l[2],l[1]=l[3],l[2]=l[4],l[3]=1,l[4]=0,b=3;else l[++b]++;else l[b]++;this.foundPatternCross(l)&&(b=this.handlePossibleCenter(l,m,d))&&(c=l[0],this.hasSkipped&&(h=this.haveMultiplyConfirmedCenters()))}a=this.selectBestPatterns();g.orderBestPatterns(a);return new V(a)}}function M(a,b,e){this.x=a;this.y=b;this.count=1;this.estimatedModuleSize=e;this.__defineGetter__("EstimatedModuleSize",
|
||||
function(){return this.estimatedModuleSize});this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("X",function(){return Math.floor(this.x)});this.__defineGetter__("Y",function(){return Math.floor(this.y)});this.incrementCount=function(){this.count++};this.aboutEquals=function(d,c,b){return Math.abs(c-this.y)<=d&&Math.abs(b-this.x)<=d?(d=Math.abs(d-this.estimatedModuleSize),1>=d||1>=d/this.estimatedModuleSize):!1}}function R(a,b,e,d,c,f,l){this.image=a;this.possibleCenters=
|
||||
[];this.startX=b;this.startY=e;this.width=d;this.height=c;this.moduleSize=f;this.crossCheckStateCount=[0,0,0];this.resultPointCallback=l;this.centerFromEnd=function(c,d){return d-c[2]-c[1]/2};this.foundPatternCross=function(c){for(var d=this.moduleSize,b=d/2,a=0;3>a;a++)if(Math.abs(d-c[a])>=b)return!1;return!0};this.crossCheckVertical=function(c,d,b,a){var e=this.image,h=g.height,f=this.crossCheckStateCount;f[0]=0;f[1]=0;f[2]=0;for(var l=c;0<=l&&e[d+l*g.width]&&f[1]<=b;)f[1]++,l--;if(0>l||f[1]>b)return NaN;
|
||||
for(;0<=l&&!e[d+l*g.width]&&f[0]<=b;)f[0]++,l--;if(f[0]>b)return NaN;for(l=c+1;l<h&&e[d+l*g.width]&&f[1]<=b;)f[1]++,l++;if(l==h||f[1]>b)return NaN;for(;l<h&&!e[d+l*g.width]&&f[2]<=b;)f[2]++,l++;return f[2]>b||5*Math.abs(f[0]+f[1]+f[2]-a)>=2*a?NaN:this.foundPatternCross(f)?this.centerFromEnd(f,l):NaN};this.handlePossibleCenter=function(c,d,b){var a=c[0]+c[1]+c[2];b=this.centerFromEnd(c,b);d=this.crossCheckVertical(d,Math.floor(b),2*c[1],a);if(!isNaN(d)){c=(c[0]+c[1]+c[2])/3;for(var a=this.possibleCenters.length,
|
||||
e=0;e<a;e++)if(this.possibleCenters[e].aboutEquals(c,d,b))return new M(b,d,c);b=new M(b,d,c);this.possibleCenters.push(b);null!=this.resultPointCallback&&this.resultPointCallback.foundPossibleResultPoint(b)}return null};this.find=function(){for(var c,b=this.startX,h=this.height,f=b+d,l=e+(h>>1),p=[0,0,0],k=0;k<h;k++){var n=l+(0==(k&1)?k+1>>1:-(k+1>>1));p[0]=0;p[1]=0;p[2]=0;for(var A=b;A<f&&!a[A+g.width*n];)A++;for(c=0;A<f;){if(a[A+n*g.width])if(1==c)p[c]++;else if(2==c){if(this.foundPatternCross(p)&&
|
||||
(c=this.handlePossibleCenter(p,n,A),null!=c))return c;p[0]=p[2];p[1]=1;p[2]=0;c=1}else p[++c]++;else 1==c&&c++,p[c]++;A++}if(this.foundPatternCross(p)&&(c=this.handlePossibleCenter(p,n,f),null!=c))return c}if(0!=this.possibleCenters.length)return this.possibleCenters[0];throw"Couldn't find enough alignment patterns";}}function X(a,b,e){this.blockPointer=0;this.bitPointer=7;this.dataLength=0;this.blocks=a;this.numErrorCorrectionCode=e;9>=b?this.dataLengthMode=0:10<=b&&26>=b?this.dataLengthMode=1:27<=
|
||||
b&&40>=b&&(this.dataLengthMode=2);this.getNextBits=function(b){var c,d;if(b<this.bitPointer+1){var a=0;for(d=0;d<b;d++)a+=1<<d;a<<=this.bitPointer-b+1;d=(this.blocks[this.blockPointer]&a)>>this.bitPointer-b+1;this.bitPointer-=b;return d}if(b<this.bitPointer+1+8){for(d=c=0;d<this.bitPointer+1;d++)c+=1<<d;d=(this.blocks[this.blockPointer]&c)<<b-(this.bitPointer+1);this.blockPointer++;d+=this.blocks[this.blockPointer]>>8-(b-(this.bitPointer+1));this.bitPointer-=b%8;0>this.bitPointer&&(this.bitPointer=
|
||||
8+this.bitPointer);return d}if(b<this.bitPointer+1+16){for(d=a=c=0;d<this.bitPointer+1;d++)c+=1<<d;c=(this.blocks[this.blockPointer]&c)<<b-(this.bitPointer+1);this.blockPointer++;var e=this.blocks[this.blockPointer]<<b-(this.bitPointer+1+8);this.blockPointer++;for(d=0;d<b-(this.bitPointer+1+8);d++)a+=1<<d;a<<=8-(b-(this.bitPointer+1+8));d=(this.blocks[this.blockPointer]&a)>>8-(b-(this.bitPointer+1+8));this.bitPointer-=(b-8)%8;0>this.bitPointer&&(this.bitPointer=8+this.bitPointer);return c+e+d}return 0};
|
||||
this.NextMode=function(){return this.blockPointer>this.blocks.length-this.numErrorCorrectionCode-2?0:this.getNextBits(4)};this.getDataLength=function(b){for(var c=0;1!=b>>c;)c++;return this.getNextBits(g.sizeOfDataLengthInfo[this.dataLengthMode][c])};this.getRomanAndFigureString=function(b){var c="",d="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".split("");do if(1<b){var a=this.getNextBits(11);var e=a%45,c=c+d[Math.floor(a/45)],c=c+d[e];b-=2}else 1==b&&(a=this.getNextBits(6),c+=d[a],--b);while(0<
|
||||
b);return c};this.getFigureString=function(b){var c=0,d="";do 3<=b?(c=this.getNextBits(10),100>c&&(d+="0"),10>c&&(d+="0"),b-=3):2==b?(c=this.getNextBits(7),10>c&&(d+="0"),b-=2):1==b&&(c=this.getNextBits(4),--b),d+=c;while(0<b);return d};this.get8bitByteArray=function(b){var c=[];do{var d=this.getNextBits(8);c.push(d);b--}while(0<b);return c};this.getKanjiString=function(b){var c="";do{var d=this.getNextBits(13);d=(d/192<<8)+d%192;c+=String.fromCharCode(40956>=d+33088?d+33088:d+49472);b--}while(0<
|
||||
b);return c};this.parseECIValue=function(){var b=0,c=this.getNextBits(8);0==(c&128)&&(b=c&127);128==(c&192)&&(b=this.getNextBits(8),b|=(c&63)<<8);192==(c&224)&&(b=this.getNextBits(8),b|=(c&31)<<16);return b};this.__defineGetter__("DataByte",function(){var b=[];do{var c=this.NextMode();if(0==c)if(0<b.length)break;else throw"Empty data block";if(1!=c&&2!=c&&4!=c&&8!=c&&7!=c)throw"Invalid mode: "+c+" in (block:"+this.blockPointer+" bit:"+this.bitPointer+")";if(7==c)this.parseECIValue();else{var a=this.getDataLength(c);
|
||||
if(1>a)throw"Invalid data length: "+a;switch(c){case 1:c=this.getFigureString(a);for(var a=Array(c.length),e=0;e<c.length;e++)a[e]=c.charCodeAt(e);b.push(a);break;case 2:c=this.getRomanAndFigureString(a);a=Array(c.length);for(e=0;e<c.length;e++)a[e]=c.charCodeAt(e);b.push(a);break;case 4:c=this.get8bitByteArray(a);b.push(c);break;case 8:c=this.getKanjiString(a),b.push(c)}}}while(1);return b})}var F={checkAndNudgePoints:function(a,b){for(var e,d,c=g.width,h=g.height,f=!0,m=0;m<b.length&&f;m+=2){d=
|
||||
Math.floor(b[m]);e=Math.floor(b[m+1]);if(-1>d||d>c||-1>e||e>h)throw"Error.checkAndNudgePoints ";f=!1;-1==d?(b[m]=0,f=!0):d==c&&(b[m]=c-1,f=!0);-1==e?(b[m+1]=0,f=!0):e==h&&(b[m+1]=h-1,f=!0)}f=!0;for(m=b.length-2;0<=m&&f;m-=2){d=Math.floor(b[m]);e=Math.floor(b[m+1]);if(-1>d||d>c||-1>e||e>h)throw"Error.checkAndNudgePoints ";f=!1;-1==d?(b[m]=0,f=!0):d==c&&(b[m]=c-1,f=!0);-1==e?(b[m+1]=0,f=!0):e==h&&(b[m+1]=h-1,f=!0)}},sampleGrid3:function(a,b,e){for(var d=new I(b),c=Array(b<<1),h=0;h<b;h++){for(var f=
|
||||
c.length,m=h+.5,k=0;k<f;k+=2)c[k]=(k>>1)+.5,c[k+1]=m;e.transformPoints1(c);F.checkAndNudgePoints(a,c);try{for(k=0;k<f;k+=2)a[Math.floor(c[k])+g.width*Math.floor(c[k+1])]&&d.set_Renamed(k>>1,h)}catch(q){throw"Error.checkAndNudgePoints";}}return d},sampleGridx:function(a,b,e,d,c,f,l,g,k,q,n,x,v,t,r,A,u,w){e=z.quadrilateralToQuadrilateral(e,d,c,f,l,g,k,q,n,x,v,t,r,A,u,w);return F.sampleGrid3(a,b,e)}};k.VERSION_DECODE_INFO=[31892,34236,39577,42195,48118,51042,55367,58893,63784,68472,70749,76311,79154,
|
||||
84390,87683,92361,96236,102084,102881,110507,110734,117786,119615,126325,127568,133589,136944,141498,145311,150283,152622,158308,161089,167017];k.VERSIONS=[new k(1,[],new f(7,new a(1,19)),new f(10,new a(1,16)),new f(13,new a(1,13)),new f(17,new a(1,9))),new k(2,[6,18],new f(10,new a(1,34)),new f(16,new a(1,28)),new f(22,new a(1,22)),new f(28,new a(1,16))),new k(3,[6,22],new f(15,new a(1,55)),new f(26,new a(1,44)),new f(18,new a(2,17)),new f(22,new a(2,13))),new k(4,[6,26],new f(20,new a(1,80)),new f(18,
|
||||
new a(2,32)),new f(26,new a(2,24)),new f(16,new a(4,9))),new k(5,[6,30],new f(26,new a(1,108)),new f(24,new a(2,43)),new f(18,new a(2,15),new a(2,16)),new f(22,new a(2,11),new a(2,12))),new k(6,[6,34],new f(18,new a(2,68)),new f(16,new a(4,27)),new f(24,new a(4,19)),new f(28,new a(4,15))),new k(7,[6,22,38],new f(20,new a(2,78)),new f(18,new a(4,31)),new f(18,new a(2,14),new a(4,15)),new f(26,new a(4,13),new a(1,14))),new k(8,[6,24,42],new f(24,new a(2,97)),new f(22,new a(2,38),new a(2,39)),new f(22,
|
||||
new a(4,18),new a(2,19)),new f(26,new a(4,14),new a(2,15))),new k(9,[6,26,46],new f(30,new a(2,116)),new f(22,new a(3,36),new a(2,37)),new f(20,new a(4,16),new a(4,17)),new f(24,new a(4,12),new a(4,13))),new k(10,[6,28,50],new f(18,new a(2,68),new a(2,69)),new f(26,new a(4,43),new a(1,44)),new f(24,new a(6,19),new a(2,20)),new f(28,new a(6,15),new a(2,16))),new k(11,[6,30,54],new f(20,new a(4,81)),new f(30,new a(1,50),new a(4,51)),new f(28,new a(4,22),new a(4,23)),new f(24,new a(3,12),new a(8,13))),
|
||||
new k(12,[6,32,58],new f(24,new a(2,92),new a(2,93)),new f(22,new a(6,36),new a(2,37)),new f(26,new a(4,20),new a(6,21)),new f(28,new a(7,14),new a(4,15))),new k(13,[6,34,62],new f(26,new a(4,107)),new f(22,new a(8,37),new a(1,38)),new f(24,new a(8,20),new a(4,21)),new f(22,new a(12,11),new a(4,12))),new k(14,[6,26,46,66],new f(30,new a(3,115),new a(1,116)),new f(24,new a(4,40),new a(5,41)),new f(20,new a(11,16),new a(5,17)),new f(24,new a(11,12),new a(5,13))),new k(15,[6,26,48,70],new f(22,new a(5,
|
||||
87),new a(1,88)),new f(24,new a(5,41),new a(5,42)),new f(30,new a(5,24),new a(7,25)),new f(24,new a(11,12),new a(7,13))),new k(16,[6,26,50,74],new f(24,new a(5,98),new a(1,99)),new f(28,new a(7,45),new a(3,46)),new f(24,new a(15,19),new a(2,20)),new f(30,new a(3,15),new a(13,16))),new k(17,[6,30,54,78],new f(28,new a(1,107),new a(5,108)),new f(28,new a(10,46),new a(1,47)),new f(28,new a(1,22),new a(15,23)),new f(28,new a(2,14),new a(17,15))),new k(18,[6,30,56,82],new f(30,new a(5,120),new a(1,121)),
|
||||
new f(26,new a(9,43),new a(4,44)),new f(28,new a(17,22),new a(1,23)),new f(28,new a(2,14),new a(19,15))),new k(19,[6,30,58,86],new f(28,new a(3,113),new a(4,114)),new f(26,new a(3,44),new a(11,45)),new f(26,new a(17,21),new a(4,22)),new f(26,new a(9,13),new a(16,14))),new k(20,[6,34,62,90],new f(28,new a(3,107),new a(5,108)),new f(26,new a(3,41),new a(13,42)),new f(30,new a(15,24),new a(5,25)),new f(28,new a(15,15),new a(10,16))),new k(21,[6,28,50,72,94],new f(28,new a(4,116),new a(4,117)),new f(26,
|
||||
new a(17,42)),new f(28,new a(17,22),new a(6,23)),new f(30,new a(19,16),new a(6,17))),new k(22,[6,26,50,74,98],new f(28,new a(2,111),new a(7,112)),new f(28,new a(17,46)),new f(30,new a(7,24),new a(16,25)),new f(24,new a(34,13))),new k(23,[6,30,54,74,102],new f(30,new a(4,121),new a(5,122)),new f(28,new a(4,47),new a(14,48)),new f(30,new a(11,24),new a(14,25)),new f(30,new a(16,15),new a(14,16))),new k(24,[6,28,54,80,106],new f(30,new a(6,117),new a(4,118)),new f(28,new a(6,45),new a(14,46)),new f(30,
|
||||
new a(11,24),new a(16,25)),new f(30,new a(30,16),new a(2,17))),new k(25,[6,32,58,84,110],new f(26,new a(8,106),new a(4,107)),new f(28,new a(8,47),new a(13,48)),new f(30,new a(7,24),new a(22,25)),new f(30,new a(22,15),new a(13,16))),new k(26,[6,30,58,86,114],new f(28,new a(10,114),new a(2,115)),new f(28,new a(19,46),new a(4,47)),new f(28,new a(28,22),new a(6,23)),new f(30,new a(33,16),new a(4,17))),new k(27,[6,34,62,90,118],new f(30,new a(8,122),new a(4,123)),new f(28,new a(22,45),new a(3,46)),new f(30,
|
||||
new a(8,23),new a(26,24)),new f(30,new a(12,15),new a(28,16))),new k(28,[6,26,50,74,98,122],new f(30,new a(3,117),new a(10,118)),new f(28,new a(3,45),new a(23,46)),new f(30,new a(4,24),new a(31,25)),new f(30,new a(11,15),new a(31,16))),new k(29,[6,30,54,78,102,126],new f(30,new a(7,116),new a(7,117)),new f(28,new a(21,45),new a(7,46)),new f(30,new a(1,23),new a(37,24)),new f(30,new a(19,15),new a(26,16))),new k(30,[6,26,52,78,104,130],new f(30,new a(5,115),new a(10,116)),new f(28,new a(19,47),new a(10,
|
||||
48)),new f(30,new a(15,24),new a(25,25)),new f(30,new a(23,15),new a(25,16))),new k(31,[6,30,56,82,108,134],new f(30,new a(13,115),new a(3,116)),new f(28,new a(2,46),new a(29,47)),new f(30,new a(42,24),new a(1,25)),new f(30,new a(23,15),new a(28,16))),new k(32,[6,34,60,86,112,138],new f(30,new a(17,115)),new f(28,new a(10,46),new a(23,47)),new f(30,new a(10,24),new a(35,25)),new f(30,new a(19,15),new a(35,16))),new k(33,[6,30,58,86,114,142],new f(30,new a(17,115),new a(1,116)),new f(28,new a(14,46),
|
||||
new a(21,47)),new f(30,new a(29,24),new a(19,25)),new f(30,new a(11,15),new a(46,16))),new k(34,[6,34,62,90,118,146],new f(30,new a(13,115),new a(6,116)),new f(28,new a(14,46),new a(23,47)),new f(30,new a(44,24),new a(7,25)),new f(30,new a(59,16),new a(1,17))),new k(35,[6,30,54,78,102,126,150],new f(30,new a(12,121),new a(7,122)),new f(28,new a(12,47),new a(26,48)),new f(30,new a(39,24),new a(14,25)),new f(30,new a(22,15),new a(41,16))),new k(36,[6,24,50,76,102,128,154],new f(30,new a(6,121),new a(14,
|
||||
122)),new f(28,new a(6,47),new a(34,48)),new f(30,new a(46,24),new a(10,25)),new f(30,new a(2,15),new a(64,16))),new k(37,[6,28,54,80,106,132,158],new f(30,new a(17,122),new a(4,123)),new f(28,new a(29,46),new a(14,47)),new f(30,new a(49,24),new a(10,25)),new f(30,new a(24,15),new a(46,16))),new k(38,[6,32,58,84,110,136,162],new f(30,new a(4,122),new a(18,123)),new f(28,new a(13,46),new a(32,47)),new f(30,new a(48,24),new a(14,25)),new f(30,new a(42,15),new a(32,16))),new k(39,[6,26,54,82,110,138,
|
||||
166],new f(30,new a(20,117),new a(4,118)),new f(28,new a(40,47),new a(7,48)),new f(30,new a(43,24),new a(22,25)),new f(30,new a(10,15),new a(67,16))),new k(40,[6,30,58,86,114,142,170],new f(30,new a(19,118),new a(6,119)),new f(28,new a(18,47),new a(31,48)),new f(30,new a(34,24),new a(34,25)),new f(30,new a(20,15),new a(61,16)))];k.getVersionForNumber=function(a){if(1>a||40<a)throw"ArgumentException";return k.VERSIONS[a-1]};k.getProvisionalVersionForDimension=function(a){if(1!=a%4)throw"Error getProvisionalVersionForDimension";
|
||||
try{return k.getVersionForNumber(a-17>>2)}catch(b){throw"Error getVersionForNumber";}};k.decodeVersionInformation=function(a){for(var b=4294967295,e=0,d=0;d<k.VERSION_DECODE_INFO.length;d++){var c=k.VERSION_DECODE_INFO[d];if(c==a)return this.getVersionForNumber(d+7);c=r.numBitsDiffering(a,c);c<b&&(e=d+7,b=c)}return 3>=b?this.getVersionForNumber(e):null};z.quadrilateralToQuadrilateral=function(a,b,e,d,c,f,g,m,k,q,n,x,v,t,r,u){a=this.quadrilateralToSquare(a,b,e,d,c,f,g,m);return this.squareToQuadrilateral(k,
|
||||
q,n,x,v,t,r,u).times(a)};z.squareToQuadrilateral=function(a,b,e,d,c,f,g,m){var h=m-f,l=b-d+f-m;if(0==h&&0==l)return new z(e-a,c-e,a,d-b,f-d,b,0,0,1);var p=e-c,k=g-c;c=a-e+c-g;f=d-f;var n=p*h-k*f,h=(c*h-k*l)/n,l=(p*l-c*f)/n;return new z(e-a+h*e,g-a+l*g,a,d-b+h*d,m-b+l*m,b,h,l,1)};z.quadrilateralToSquare=function(a,b,e,d,c,f,g,m){return this.squareToQuadrilateral(a,b,e,d,c,f,g,m).buildAdjoint()};var N=[[21522,0],[20773,1],[24188,2],[23371,3],[17913,4],[16590,5],[20375,6],[19104,7],[30660,8],[29427,
|
||||
9],[32170,10],[30877,11],[26159,12],[25368,13],[27713,14],[26998,15],[5769,16],[5054,17],[7399,18],[6608,19],[1890,20],[597,21],[3340,22],[2107,23],[13663,24],[12392,25],[16177,26],[14854,27],[9396,28],[8579,29],[11994,30],[11245,31]],B=[0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4];r.numBitsDiffering=function(a,b){a^=b;return B[a&15]+B[u(a,4)&15]+B[u(a,8)&15]+B[u(a,12)&15]+B[u(a,16)&15]+B[u(a,20)&15]+B[u(a,24)&15]+B[u(a,28)&15]};r.decodeFormatInformation=function(a){var b=r.doDecodeFormatInformation(a);return null!=
|
||||
b?b:r.doDecodeFormatInformation(a^21522)};r.doDecodeFormatInformation=function(a){for(var b=4294967295,e=0,d=0;d<N.length;d++){var c=N[d],h=c[0];if(h==a)return new r(c[1]);h=this.numBitsDiffering(a,h);h<b&&(e=c[1],b=h)}return 3>=b?new r(e):null};C.forBits=function(a){if(0>a||a>=O.length)throw"ArgumentException";return O[a]};var Y=new C(0,1,"L"),Z=new C(1,0,"M"),aa=new C(2,3,"Q"),ba=new C(3,2,"H"),O=[Z,Y,ba,aa];G.getDataBlocks=function(a,b,e){if(a.length!=b.TotalCodewords)throw"ArgumentException";
|
||||
var d=b.getECBlocksForLevel(e);e=0;var c=d.getECBlocks();for(b=0;b<c.length;b++)e+=c[b].Count;e=Array(e);for(var h=0,f=0;f<c.length;f++){var g=c[f];for(b=0;b<g.Count;b++){var k=g.DataCodewords,q=d.ECCodewordsPerBlock+k;e[h++]=new G(k,Array(q))}}b=e[0].codewords.length;for(c=e.length-1;0<=c&&e[c].codewords.length!=b;)c--;c++;d=b-d.ECCodewordsPerBlock;for(b=g=0;b<d;b++)for(f=0;f<h;f++)e[f].codewords[b]=a[g++];for(f=c;f<h;f++)e[f].codewords[d]=a[g++];k=e[0].codewords.length;for(b=d;b<k;b++)for(f=0;f<
|
||||
h;f++)e[f].codewords[f<c?b:b+1]=a[g++];return e};var H={forReference:function(a){if(0>a||7<a)throw"System.ArgumentException";return H.DATA_MASKS[a]}};H.DATA_MASKS=[new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(a+b&1)}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(a&1)}},
|
||||
new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==b%3}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(a+b)%3}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(u(a,
|
||||
1)+b/3&1)}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){var e=a*b;return 0==(e&1)+e%3}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){var e=a*b;return 0==((e&1)+e%3&1)}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,
|
||||
e)};this.isMasked=function(a,b){return 0==((a+b&1)+a*b%3&1)}}];n.QR_CODE_FIELD=new n(285);n.DATA_MATRIX_FIELD=new n(301);n.addOrSubtract=function(a,b){return a^b};var E={};E.rsDecoder=new function(a){this.field=a;this.decode=function(a,e){for(var b=new w(this.field,a),c=Array(e),f=0;f<c.length;f++)c[f]=0;for(var h=!0,f=0;f<e;f++){var g=b.evaluateAt(this.field.exp(f));c[c.length-1-f]=g;0!=g&&(h=!1)}if(!h)for(f=new w(this.field,c),b=this.runEuclideanAlgorithm(this.field.buildMonomial(e,1),f,e),f=b[1],
|
||||
b=this.findErrorLocations(b[0]),c=this.findErrorMagnitudes(f,b,!1),f=0;f<b.length;f++){h=a.length-1-this.field.log(b[f]);if(0>h)throw"ReedSolomonException Bad error location";a[h]=n.addOrSubtract(a[h],c[f])}};this.runEuclideanAlgorithm=function(a,e,d){if(a.Degree<e.Degree){var b=a;a=e;e=b}for(var b=this.field.One,f=this.field.Zero,h=this.field.Zero,g=this.field.One;e.Degree>=Math.floor(d/2);){var k=a,q=b,n=h;a=e;b=f;h=g;if(a.Zero)throw"r_{i-1} was zero";e=k;g=this.field.Zero;f=a.getCoefficient(a.Degree);
|
||||
for(f=this.field.inverse(f);e.Degree>=a.Degree&&!e.Zero;){var k=e.Degree-a.Degree,r=this.field.multiply(e.getCoefficient(e.Degree),f),g=g.addOrSubtract(this.field.buildMonomial(k,r));e=e.addOrSubtract(a.multiplyByMonomial(k,r))}f=g.multiply1(b).addOrSubtract(q);g=g.multiply1(h).addOrSubtract(n)}d=g.getCoefficient(0);if(0==d)throw"ReedSolomonException sigmaTilde(0) was zero";d=this.field.inverse(d);a=g.multiply2(d);d=e.multiply2(d);return[a,d]};this.findErrorLocations=function(a){var b=a.Degree;if(1==
|
||||
b)return Array(a.getCoefficient(1));for(var d=Array(b),c=0,f=1;256>f&&c<b;f++)0==a.evaluateAt(f)&&(d[c]=this.field.inverse(f),c++);if(c!=b)throw"Error locator degree does not match number of roots";return d};this.findErrorMagnitudes=function(a,e,d){for(var b=e.length,f=Array(b),h=0;h<b;h++){for(var g=this.field.inverse(e[h]),k=1,q=0;q<b;q++)h!=q&&(k=this.field.multiply(k,n.addOrSubtract(1,this.field.multiply(e[q],g))));f[h]=this.field.multiply(a.evaluateAt(g),this.field.inverse(k));d&&(f[h]=this.field.multiply(f[h],
|
||||
g))}return f}}(n.QR_CODE_FIELD);E.correctErrors=function(a,b){for(var e=a.length,d=Array(e),c=0;c<e;c++)d[c]=a[c]&255;e=a.length-b;try{E.rsDecoder.decode(d,e)}catch(p){throw p;}for(c=0;c<b;c++)a[c]=d[c]};E.decode=function(a){var b=new T(a);a=b.readVersion();for(var e=b.readFormatInformation().ErrorCorrectionLevel,b=b.readCodewords(),b=G.getDataBlocks(b,a,e),d=0,c=0;c<b.length;c++)d+=b[c].NumDataCodewords;for(var d=Array(d),f=0,h=0;h<b.length;h++){var c=b[h],g=c.Codewords,k=c.NumDataCodewords;E.correctErrors(g,
|
||||
k);for(c=0;c<k;c++)d[f++]=g[c]}return new X(d,a.VersionNumber,e.Bits)};var g={imagedata:null,width:0,height:0,qrCodeSymbol:null,debug:!1,maxImgSize:1048576,sizeOfDataLengthInfo:[[10,9,8,8],[12,11,16,10],[14,13,16,12]],callback:null,vidSuccess:function(a){g.localstream=a;g.webkit?g.video.src=window.webkitURL.createObjectURL(a):g.moz?(g.video.mozSrcObject=a,g.video.play()):g.video.src=a;g.gUM=!0;g.canvas_qr2=document.createElement("canvas");g.canvas_qr2.id="qr-canvas";g.qrcontext2=g.canvas_qr2.getContext("2d");
|
||||
g.canvas_qr2.width=g.video.videoWidth;g.canvas_qr2.height=g.video.videoHeight;setTimeout(g.captureToCanvas,500)},vidError:function(a){g.gUM=!1},captureToCanvas:function(){if(g.gUM)try{if(0==g.video.videoWidth)setTimeout(g.captureToCanvas,500);else{g.canvas_qr2.width=g.video.videoWidth;g.canvas_qr2.height=g.video.videoHeight;g.qrcontext2.drawImage(g.video,0,0);try{g.decode()}catch(h){console.log(h),setTimeout(g.captureToCanvas,500)}}}catch(h){console.log(h),setTimeout(g.captureToCanvas,500)}},setWebcam:function(a){var b=
|
||||
navigator;g.video=document.getElementById(a);var e=!0;if(navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices)try{navigator.mediaDevices.enumerateDevices().then(function(a){a.forEach(function(a){console.log("deb1");"videoinput"===a.kind&&-1<a.label.toLowerCase().search("back")&&(e=[{sourceId:a.deviceId}]);console.log(a.kind+": "+a.label+" id = "+a.deviceId)})})}catch(d){console.log(d)}else console.log("no navigator.mediaDevices.enumerateDevices");b.getUserMedia?b.getUserMedia({video:e,
|
||||
audio:!1},g.vidSuccess,g.vidError):b.webkitGetUserMedia?(g.webkit=!0,b.webkitGetUserMedia({video:e,audio:!1},g.vidSuccess,g.vidError)):b.mozGetUserMedia&&(g.moz=!0,b.mozGetUserMedia({video:e,audio:!1},g.vidSuccess,g.vidError))},decode:function(a){if(0==arguments.length){if(g.canvas_qr2){var b=g.canvas_qr2;var e=g.qrcontext2}else b=document.getElementById("qr-canvas"),e=b.getContext("2d");g.width=b.width;g.height=b.height;g.imagedata=e.getImageData(0,0,g.width,g.height);g.result=g.process(e);null!=
|
||||
g.callback&&g.callback(g.result);return g.result}var d=new Image;d.crossOrigin="Anonymous";d.onload=function(){var a=document.getElementById("out-canvas");null!=a&&(a=a.getContext("2d"),a.clearRect(0,0,320,240),a.drawImage(d,0,0,320,240));var a=document.createElement("canvas"),b=a.getContext("2d"),e=d.height,f=d.width;d.width*d.height>g.maxImgSize&&(f=d.width/d.height,e=Math.sqrt(g.maxImgSize/f),f*=e);a.width=f;a.height=e;b.drawImage(d,0,0,a.width,a.height);g.width=a.width;g.height=a.height;try{g.imagedata=
|
||||
b.getImageData(0,0,a.width,a.height)}catch(y){g.result=Error("Cross domain image reading not supported in your browser! Save it to your computer then drag and drop the file!");null!=g.callback&&g.callback(g.result);return}try{g.result=g.process(b)}catch(y){console.log(y),g.result=Error("error decoding QR Code")}null!=g.callback&&g.callback(g.result)};d.onerror=function(){null!=g.callback&&g.callback(Error("Failed to load the image"))};d.src=a},isUrl:function(a){return/(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/.test(a)},
|
||||
decode_url:function(a){var b="";try{b=escape(a)}catch(e){console.log(e),b=a}a="";try{a=decodeURIComponent(b)}catch(e){console.log(e),a=b}return a},decode_utf8:function(a){return g.isUrl(a)?g.decode_url(a):a},process:function(a){var b=(new Date).getTime(),e=g.grayScaleToBitmap(g.grayscale());if(g.debug){for(var d=0;d<g.height;d++)for(var c=0;c<g.width;c++){var f=4*c+d*g.width*4;g.imagedata.data[f]=0;g.imagedata.data[f+1]=0;g.imagedata.data[f+2]=e[c+d*g.width]?255:0}a.putImageData(g.imagedata,0,0)}e=
|
||||
(new Q(e)).detect();if(g.debug){for(d=0;d<e.bits.Height;d++)for(c=0;c<e.bits.Width;c++)f=8*c+2*d*g.width*4,g.imagedata.data[f]=(e.bits.get_Renamed(c,d),0),g.imagedata.data[f+1]=(e.bits.get_Renamed(c,d),0),g.imagedata.data[f+2]=e.bits.get_Renamed(c,d)?255:0;a.putImageData(g.imagedata,0,0)}f=E.decode(e.bits).DataByte;a="";for(d=0;d<f.length;d++)for(c=0;c<f[d].length;c++)a+=String.fromCharCode(f[d][c]);f=(new Date).getTime();console.log(f-b);return g.decode_utf8(a)},getPixel:function(a,b){if(g.width<
|
||||
a)throw"point error";if(g.height<b)throw"point error";var e=4*a+b*g.width*4;return(33*g.imagedata.data[e]+34*g.imagedata.data[e+1]+33*g.imagedata.data[e+2])/100},binarize:function(a){for(var b=Array(g.width*g.height),e=0;e<g.height;e++)for(var d=0;d<g.width;d++){var c=g.getPixel(d,e);b[d+e*g.width]=c<=a?!0:!1}return b},getMiddleBrightnessPerArea:function(a){for(var b=Math.floor(g.width/4),e=Math.floor(g.height/4),d=Array(4),c=0;4>c;c++){d[c]=Array(4);for(var f=0;4>f;f++)d[c][f]=[0,0]}for(c=0;4>c;c++)for(f=
|
||||
0;4>f;f++){d[f][c][0]=255;for(var h=0;h<e;h++)for(var m=0;m<b;m++){var k=a[b*f+m+(e*c+h)*g.width];k<d[f][c][0]&&(d[f][c][0]=k);k>d[f][c][1]&&(d[f][c][1]=k)}}a=Array(4);for(b=0;4>b;b++)a[b]=Array(4);for(c=0;4>c;c++)for(f=0;4>f;f++)a[f][c]=Math.floor((d[f][c][0]+d[f][c][1])/2);return a},grayScaleToBitmap:function(a){for(var b=g.getMiddleBrightnessPerArea(a),e=b.length,d=Math.floor(g.width/e),c=Math.floor(g.height/e),f=new ArrayBuffer(g.width*g.height),f=new Uint8Array(f),h=0;h<e;h++)for(var m=0;m<e;m++)for(var k=
|
||||
0;k<c;k++)for(var n=0;n<d;n++)f[d*m+n+(c*h+k)*g.width]=a[d*m+n+(c*h+k)*g.width]<b[m][h]?!0:!1;return f},grayscale:function(){for(var a=new ArrayBuffer(g.width*g.height),a=new Uint8Array(a),b=0;b<g.height;b++)for(var e=0;e<g.width;e++){var d=g.getPixel(e,b);a[e+b*g.width]=d}return a}},L=3,W=57,D=8,K=2;g.orderBestPatterns=function(a){function b(a,b){var c=a.X-b.X,d=a.Y-b.Y;return Math.sqrt(c*c+d*d)}var e=b(a[0],a[1]),d=b(a[1],a[2]),c=b(a[0],a[2]);d>=e&&d>=c?(d=a[0],e=a[1],c=a[2]):c>=d&&c>=e?(d=a[1],
|
||||
e=a[0],c=a[2]):(d=a[2],e=a[0],c=a[1]);if(0>function(a,b,c){var d=b.x;b=b.y;return(c.x-d)*(a.y-b)-(c.y-b)*(a.x-d)}(e,d,c))var f=e,e=c,c=f;a[0]=e;a[1]=d;a[2]=c};return g}();
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Bangle.js 2 compatibility
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
g.setBgColor(0, 0, 0);
|
||||
g.clear().flip();
|
||||
var imgbat = require("heatshrink").decompress(atob("nlWhH+AH4A/AH4AHwoAQHXQ8pHf47rF6YAXHXQ8OHVo8NHf47/Hf47/Hf47/Hf47/Hf47/Hf47r1I766Y756Z351I766ayTHco6BHfCxBHfI6CdyY7jHQQ73WIayUHcQ6DHew6EHeqxEdyo7gOwo70HQqyVHbyxFHeo6GHeY6Hdyo7cWI47zHQ6yWHbY6IHeKxIABa9MHbI6TQJo7YHUI7YWMKzbQKQYOHdYYPHcK9IWJw7sDKA7hHTA7pWKA7qDKQ7gdwwaTHcyxSHcR2ZHcwZUHcqxUHcLuEHSo7kHSw7gWLI7kHS47iHTA7fdwKxYHcQ6ZHb46bO8A76ADg7/Hf47/Hf47/Hf47/Hf47/Hf47/HbY8uHRg8tHRwA/AH4AsA=="));
|
||||
var imgbubble = require("heatshrink").decompress(atob("ikQhH+AAc0AAgKEAAwRFCpgMDnVerwULCIuCCYoUGCQQQBnQ9MA4Q3GChI5DEpATIJYISKCY46LCYwANCa4UObJ7INeCoSOCpAOI"));
|
||||
var imgbat = require("heatshrink").decompress(atob("nFYhBC/AH4A/AGUeACA22HEo3/G8YrTAC422HBQ2tHBI3/G/43/G/43/G/43/G/43/G/43/G+fTG+vSN+w326Q31GwI3/G9g2WG742CG/43rGwY3yGwg33RKo3bNzQ3bGwo3/G9A2GG942dG/43QGw43uGxA34IKw3VGyY3iG0I3pb8pBRG+wYPG8wYQG/42uG8oZSG/43bDKY3iDKg3cNzI3iRKo3gGyo3/G7A2WG7g2aG/43WGzA3dGzI3/G6fTGzRvcG/43/G/43/G/43/G/43/G/43/G/437HFw2IHFo2KAH4A/AH4Aa"));
|
||||
var imgbubble = require("heatshrink").decompress(atob("i0UhAebgoAFCaYXNBocjAAIWNCYoVHCw4UFIZwqELJQWFKZQVOChYVzABwVaCx7wKCqIWNCg4WMChIXJCZgAnA=="));
|
||||
|
||||
var W=240,H=240;
|
||||
var W=g.getWidth(),H=g.getHeight();
|
||||
var b2v = (W != 240)?-1:1;
|
||||
var b2rot = (W != 240)?Math.PI:0;
|
||||
var b2scale = W/240.0;
|
||||
var bubbles = [];
|
||||
for (var i=0;i<10;i++) {
|
||||
bubbles.push({y:Math.random()*H,ly:0,x:(0.5+(i<5?i:i+8))*W/18,v:0.6+Math.random(),s:0.5+Math.random()});
|
||||
|
|
@ -12,12 +16,16 @@ function anim() {
|
|||
/* we don't use any kind of buffering here. Just draw one image
|
||||
at a time (image contains a background) too, and there is minimal
|
||||
flicker. */
|
||||
var mx = 120, my = 120;
|
||||
var mx = W/2.0, my = H/2.0;
|
||||
bubbles.forEach(f=>{
|
||||
f.y-=f.v;if (f.y<-24) f.y=H+8;
|
||||
g.drawImage(imgbubble,f.y,f.x,{scale:f.s});
|
||||
f.y-=f.v * b2v;
|
||||
if (f.y<-24)
|
||||
f.y=H+8;
|
||||
else if (f.y > (H+8))
|
||||
f.y=0;
|
||||
g.drawImage(imgbubble,f.y,f.x,{scale:f.s * b2scale, rotate:b2rot});
|
||||
});
|
||||
g.drawImage(imgbat, mx,my,{rotate:Math.sin(getTime()*2)*0.5-Math.PI/2});
|
||||
g.drawImage(imgbat, mx,my,{scale:b2scale, rotate:Math.sin(getTime()*2)*0.5-Math.PI/2 + b2rot});
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
0.01: Submitted to App Loader
|
||||
0.02: Removed unneded code, added HID controlls thanks to t0m1o1 for his code :p
|
||||
|
|
@ -4,24 +4,96 @@ var fontsizeTime = g.getWidth()>200 ? 4 : 4;
|
|||
var fontheight = 10*fontsize;
|
||||
var fontheightTime = 10*fontsizeTime;
|
||||
var locale = require("locale");
|
||||
var marginTop = 40;
|
||||
var marginTop = 25;
|
||||
var flag = false;
|
||||
|
||||
var hrtOn = false;
|
||||
var hrtStr = "Hrt: ??? bpm";
|
||||
var storage = require('Storage');
|
||||
|
||||
const NONE_MODE = "none";
|
||||
const ID_MODE = "id";
|
||||
const VER_MODE = "ver";
|
||||
const BATT_MODE = "batt";
|
||||
const MEM_MODE = "mem";
|
||||
const STEPS_MODE = "step";
|
||||
const HRT_MODE = "hrt";
|
||||
const NONE_FN_MODE = "no_fn";
|
||||
const HRT_FN_MODE = "fn_hrt";
|
||||
const settings = storage.readJSON('setting.json',1) || { HID: false };
|
||||
|
||||
var sendHid, next, prev, toggle, up, down, profile;
|
||||
var lasty = 0;
|
||||
var lastx = 0;
|
||||
|
||||
if (settings.HID=="kbmedia") {
|
||||
profile = 'Music';
|
||||
sendHid = function (code, cb) {
|
||||
try {
|
||||
NRF.sendHIDReport([1,code], () => {
|
||||
NRF.sendHIDReport([1,0], () => {
|
||||
if (cb) cb();
|
||||
});
|
||||
});
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
};
|
||||
next = function (cb) { sendHid(0x01, cb); };
|
||||
prev = function (cb) { sendHid(0x02, cb); };
|
||||
toggle = function (cb) { sendHid(0x10, cb); };
|
||||
up = function (cb) {sendHid(0x40, cb); };
|
||||
down = function (cb) { sendHid(0x80, cb); };
|
||||
} else {
|
||||
E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) {
|
||||
if (enable) {
|
||||
settings.HID = "kbmedia";
|
||||
require("Storage").write('setting.json', settings);
|
||||
setTimeout(load, 1000, "hidmsicswipe.app.js");
|
||||
} else setTimeout(load, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
if (next) {
|
||||
setWatch(function(e) {
|
||||
var len = e.time - e.lastTime;
|
||||
E.showMessage('lock');
|
||||
setTimeout(drawApp, 1000);
|
||||
Bangle.setLocked(true);
|
||||
}, BTN1, { edge:"falling",repeat:true,debounce:50});
|
||||
Bangle.on('drag', function(e) {
|
||||
if(!e.b){
|
||||
console.log(lasty);
|
||||
console.log(lastx);
|
||||
if(lasty > 40){
|
||||
writeLine('Down', 3);
|
||||
// setTimeout(drawApp, 1000);
|
||||
// Bluetooth.println(JSON.stringify({t:"music", n:"volumedown"}));
|
||||
down(() => {});
|
||||
}
|
||||
else if(lasty < -40){
|
||||
writeLine('Up', 3);
|
||||
// setTimeout(drawApp, 1000);
|
||||
//Bluetooth.println(JSON.stringify({t:"music", n:"volumeup"}));
|
||||
|
||||
up(() => {});
|
||||
} else if(lastx < -40){
|
||||
writeLine('Prev', 3);
|
||||
// setTimeout(drawApp, 1000);
|
||||
// Bluetooth.println(JSON.stringify({t:"music", n:"previous"}));
|
||||
prev(() => {});
|
||||
} else if(lastx > 40){
|
||||
writeLine('Next', 3);
|
||||
// setTimeout(drawApp, 1000);
|
||||
// Bluetooth.println(JSON.stringify({t:"music", n:"next"}));
|
||||
next(() => {});
|
||||
} else if(lastx==0 && lasty==0){
|
||||
writeLine('play/pause', 3);
|
||||
//setTimeout(drawApp, 1000);
|
||||
// Bluetooth.println(JSON.stringify({t:"music", n:"play"}));
|
||||
|
||||
toggle(() => {});
|
||||
}
|
||||
lastx = 0;
|
||||
lasty = 0;
|
||||
}
|
||||
else{
|
||||
lastx = lastx + e.dx;
|
||||
lasty = lasty + e.dy;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
let infoMode = NONE_MODE;
|
||||
let functionMode = NONE_FN_MODE;
|
||||
|
||||
let textCol = g.theme.dark ? "#0f0" : "#080";
|
||||
|
||||
|
|
@ -33,13 +105,12 @@ function drawAll(){
|
|||
function updateRest(now){
|
||||
writeLine(locale.dow(now),1);
|
||||
writeLine(locale.date(now,1),2);
|
||||
drawInfo(5);
|
||||
}
|
||||
function updateTime(){
|
||||
if (!Bangle.isLCDOn()) return;
|
||||
let now = new Date();
|
||||
writeLine(locale.time(now,1),0);
|
||||
writeLine(flag?" ":"_",3);
|
||||
writeLine(flag?" ":"_ ",3);
|
||||
flag = !flag;
|
||||
if(now.getMinutes() == 0)
|
||||
updateRest(now);
|
||||
|
|
@ -65,142 +136,13 @@ function writeLine(str,line){
|
|||
var y = marginTop+(line-1)*fontheight+fontheightTime;
|
||||
g.setFont("6x8",fontsize);
|
||||
g.setColor(textCol).setFontAlign(-1,-1);
|
||||
g.clearRect(0,y,((str.length+1)*20),y+fontheight-1);
|
||||
g.clearRect(0,y,((str.length+10)*40),y+fontheightTime-1);
|
||||
writeLineStart(line);
|
||||
g.drawString(str,25,y);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function drawInfo(line) {
|
||||
let val;
|
||||
let str = "";
|
||||
let col = textCol; // green
|
||||
|
||||
//console.log("drawInfo(), infoMode=" + infoMode + " funcMode=" + functionMode);
|
||||
|
||||
switch(functionMode) {
|
||||
case NONE_FN_MODE:
|
||||
break;
|
||||
case HRT_FN_MODE:
|
||||
col = g.theme.dark ? "#0ff": "#088"; // cyan
|
||||
str = "HRM: " + (hrtOn ? "ON" : "OFF");
|
||||
drawModeLine(line,str,col);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(infoMode) {
|
||||
case NONE_MODE:
|
||||
col = g.theme.bg;
|
||||
str = "";
|
||||
break;
|
||||
case HRT_MODE:
|
||||
str = hrtStr;
|
||||
break;
|
||||
case STEPS_MODE:
|
||||
str = "Steps: " + stepsWidget().getSteps();
|
||||
break;
|
||||
case ID_MODE:
|
||||
val = NRF.getAddress().split(":");
|
||||
str = "Id: " + val[4] + val[5];
|
||||
break;
|
||||
case VER_MODE:
|
||||
str = "Fw: " + process.env.VERSION;
|
||||
break;
|
||||
case MEM_MODE:
|
||||
val = process.memory();
|
||||
str = "Memory: " + Math.round(val.usage*100/val.total) + "%";
|
||||
break;
|
||||
case BATT_MODE:
|
||||
default:
|
||||
str = "Battery: " + E.getBattery() + "%";
|
||||
}
|
||||
|
||||
drawModeLine(line,str,col);
|
||||
}
|
||||
|
||||
function drawModeLine(line, str, col) {
|
||||
g.setColor(col);
|
||||
var y = marginTop+line*fontheight;
|
||||
g.fillRect(0, y, 239, y+fontheight-1);
|
||||
g.setColor(g.theme.bg).setFontAlign(0, 0);
|
||||
g.drawString(str, g.getWidth()/2, y+fontheight/2);
|
||||
}
|
||||
|
||||
function changeInfoMode() {
|
||||
switch(functionMode) {
|
||||
case NONE_FN_MODE:
|
||||
break;
|
||||
case HRT_FN_MODE:
|
||||
hrtOn = !hrtOn;
|
||||
Bangle.buzz();
|
||||
Bangle.setHRMPower(hrtOn ? 1 : 0);
|
||||
if (hrtOn) infoMode = HRT_MODE;
|
||||
return;
|
||||
}
|
||||
|
||||
switch(infoMode) {
|
||||
case NONE_MODE:
|
||||
if (stepsWidget() !== undefined)
|
||||
infoMode = hrtOn ? HRT_MODE : STEPS_MODE;
|
||||
else
|
||||
infoMode = VER_MODE;
|
||||
break;
|
||||
case HRT_MODE:
|
||||
if (stepsWidget() !== undefined)
|
||||
infoMode = STEPS_MODE;
|
||||
else
|
||||
infoMode = VER_MODE;
|
||||
break;
|
||||
case STEPS_MODE:
|
||||
infoMode = ID_MODE;
|
||||
break;
|
||||
case ID_MODE:
|
||||
infoMode = VER_MODE;
|
||||
break;
|
||||
case VER_MODE:
|
||||
infoMode = BATT_MODE;
|
||||
break;
|
||||
case BATT_MODE:
|
||||
infoMode = MEM_MODE;
|
||||
break;
|
||||
case MEM_MODE:
|
||||
default:
|
||||
infoMode = NONE_MODE;
|
||||
}
|
||||
}
|
||||
|
||||
function changeFunctionMode() {
|
||||
//console.log("changeFunctionMode()");
|
||||
switch(functionMode) {
|
||||
case NONE_FN_MODE:
|
||||
functionMode = HRT_FN_MODE;
|
||||
break;
|
||||
case HRT_FN_MODE:
|
||||
default:
|
||||
functionMode = NONE_FN_MODE;
|
||||
}
|
||||
//console.log(functionMode);
|
||||
|
||||
}
|
||||
|
||||
function stepsWidget() {
|
||||
if (WIDGETS.activepedom !== undefined) {
|
||||
return WIDGETS.activepedom;
|
||||
} else if (WIDGETS.wpedom !== undefined) {
|
||||
return WIDGETS.wpedom;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Bangle.on('HRM', function(hrm) {
|
||||
if(hrm.confidence > 90){
|
||||
hrtStr = "Hrt: " + hrm.bpm + " bpm";
|
||||
} else {
|
||||
hrtStr = "Hrt: ??? bpm";
|
||||
}
|
||||
});
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
|
@ -211,6 +153,5 @@ Bangle.on('lcdPower',function(on) {
|
|||
var click = setInterval(updateTime, 1000);
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clockupdown", btn=>{
|
||||
if (btn<0) changeInfoMode();
|
||||
drawAll();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Show text if uncalibrated
|
||||
0.03: Eliminate flickering
|
||||
0.04: Fix for Bangle.js 2 and themes
|
||||
0.05: Fix bearing not clearing correctly (visible in single or double digit bearings)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ Bangle.on('mag', function(m) {
|
|||
}
|
||||
g.setFontAlign(0,0).setFont("6x8",3);
|
||||
var y = 36;
|
||||
g.clearRect(M-40,y,M+40,y+24);
|
||||
g.clearRect(M-40,24,M+40,48);
|
||||
g.drawString(Math.round(m.heading),M,y,true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Initial Release
|
||||
0.02: Replace icon with one found on https://icons8.com
|
||||
0.03: Re-render icon fixing display in settings
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("AH4A6iIAQCwkBC6MQC4kYxAACwMT/4ACmMe9wAC6IXLj4XD+IXE8IX/C9/zR4oXOmYABC6UTCwQXZjrXKf5IAHC713AAURindAAVBiatDmIXFi4XDuMdC4fRYooX/5nBC6Xc5gABC6UcCwQXF+aPMC471DC6MTCwQXHa4V2szXBC4bXBC5YAQC7se9wAC6MYxAACwJTCAAIXL8IXFQYoX/C/4XtjrXNu1ma4z/JAA4XEgAXRCwgA/AGo"))
|
||||
require("heatshrink").decompress(atob("mEwwhC/AHcRACAWEgIXRiAXEjGIAAWBif/AAUxj3uAAXRC5cfC4fxC4nhC/4Xv+aPFC50zAAIXSiYWCC7Mda5T/JAA4Xeu4ACiMU7oACoMTVocxC4sXC4dxjoXD6LFFC//M4IXS7nMAAIXSjgWCC4vzR5gXHeoYXRiYWCC47XCu1ma4IXDa4IXLACAXdj3uAAXRjGIAAWBKYQABC5fhC4qDFC/4X/C9sda5t2szXGf5IAHC4kAC6IWEAH4A1"))
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Multiple pages
|
||||
0.03: cycle thru pages
|
||||
0.04: reset to clock after 2 mins of inactivity
|
||||
0.05: add Bangle 2 version
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||

|
||||
|
||||
In the picture above, the Settings app is selected.
|
||||
## Controls
|
||||
## Controls- Bangle
|
||||
|
||||
**BTN1** - move backward through app icons on a page
|
||||
|
||||
|
|
@ -13,4 +13,12 @@ In the picture above, the Settings app is selected.
|
|||
|
||||
**Swipe Left** - move to next page of app icons
|
||||
|
||||
**Swipe Right** - move to previous page of app icons
|
||||
|
||||
## Controls- Bangle 2
|
||||
|
||||
**Touch** - icon to select, scond touch launches app
|
||||
|
||||
**Swipe Left** - move to next page of app icons
|
||||
|
||||
**Swipe Right** - move to previous page of app icons
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
/* Desktop launcher
|
||||
*
|
||||
*/
|
||||
|
||||
var s = require("Storage");
|
||||
var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type));
|
||||
apps.sort((a,b)=>{
|
||||
var n=(0|a.sortorder)-(0|b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
if (a.name<b.name) return -1;
|
||||
if (a.name>b.name) return 1;
|
||||
return 0;
|
||||
});
|
||||
apps.forEach(app=>{
|
||||
if (app.icon)
|
||||
app.icon = s.read(app.icon); // should just be a link to a memory area
|
||||
});
|
||||
|
||||
var Napps = apps.length;
|
||||
var Npages = Math.ceil(Napps/4);
|
||||
var maxPage = Npages-1;
|
||||
var selected = -1;
|
||||
var oldselected = -1;
|
||||
var page = 0;
|
||||
const XOFF = 24;
|
||||
const YOFF = 30;
|
||||
|
||||
function draw_icon(p,n,selected) {
|
||||
var x = (n%2)*72+XOFF;
|
||||
var y = n>1?72+YOFF:YOFF;
|
||||
(selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+10,y+2,x+60,y+52);
|
||||
g.clearRect(x+12,y+4,x+59,y+51);
|
||||
g.setColor(g.theme.fg);
|
||||
try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){}
|
||||
g.setFontAlign(0,-1,0).setFont("6x8",1);
|
||||
var txt = apps[p*4+n].name.split(" ");
|
||||
for (var i = 0; i < txt.length; i++) {
|
||||
txt[i] = txt[i].trim();
|
||||
g.drawString(txt[i],x+36,y+54+i*8);
|
||||
}
|
||||
}
|
||||
|
||||
function drawPage(p){
|
||||
g.reset();
|
||||
g.clearRect(0,24,175,175);
|
||||
var O = 88+YOFF/2-12*(Npages/2);
|
||||
for (var j=0;j<Npages;j++){
|
||||
var y = O+j*12;
|
||||
g.setColor(g.theme.fg);
|
||||
if (j==page) g.fillCircle(XOFF/2,y,4);
|
||||
else g.drawCircle(XOFF/2,y,4);
|
||||
}
|
||||
for (var i=0;i<4;i++) {
|
||||
if (!apps[p*4+i]) return i;
|
||||
draw_icon(p,i,selected==i);
|
||||
}
|
||||
g.flip();
|
||||
}
|
||||
|
||||
Bangle.on("swipe",(dir)=>{
|
||||
selected = 0;
|
||||
oldselected=-1;
|
||||
if (dir<0){
|
||||
++page; if (page>maxPage) page=0;
|
||||
drawPage(page);
|
||||
} else {
|
||||
--page; if (page<0) page=maxPage;
|
||||
drawPage(page);
|
||||
}
|
||||
});
|
||||
|
||||
function isTouched(p,n){
|
||||
if (n<0 || n>3) return false;
|
||||
var x1 = (n%2)*72+XOFF; var y1 = n>1?72+YOFF:YOFF;
|
||||
var x2 = x1+71; var y2 = y1+81;
|
||||
return (p.x>x1 && p.y>y1 && p.x<x2 && p.y<y2);
|
||||
}
|
||||
|
||||
Bangle.on("touch",(_,p)=>{
|
||||
var i;
|
||||
for (i=0;i<4;i++){
|
||||
if((page*4+i)<Napps){
|
||||
if (isTouched(p,i)) {
|
||||
draw_icon(page,i,true);
|
||||
if (selected>=0) {
|
||||
if (selected!=i){
|
||||
draw_icon(page,selected,false);
|
||||
} else {
|
||||
load(apps[page*4+i].src);
|
||||
}
|
||||
}
|
||||
selected=i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((i==4 || (page*4+i)>Napps) && selected>=0) {
|
||||
draw_icon(page,selected,false);
|
||||
selected=-1;
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
drawPage(0);
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Upgraded text to images, added welcome screen and subtitles.
|
||||
|
|
|
|||
|
|
@ -4,29 +4,48 @@
|
|||
*/
|
||||
|
||||
|
||||
// Emojis are integer pairs with the form [ image, Unicode code point ]
|
||||
// Emoji images are 96px x 96px, 4bpp (https://www.espruino.com/Image+Converter)
|
||||
// and adapted from Font Awesome 5
|
||||
const GRIN = "sFgwkBiIATDwoaUFi4ynQZ4uuGDzlTF1wwaFyowYFy4wWiAvZgIutGCgubSKRecMCQudMCBeeMCAufMBxegMBwuhMBheiMBgujMBRekMBQvvF0qQIL0xgIF94unSA4vuR1CQGF94upSAovuR1SQEF94urSAY/PCBivQF5z/DEBQ+DEB5ePCJYOEMBgNNF8MBHpogNHwqBNF/4vsEAovOX7TviBhYgFD5Q/EEJoANEAY/OLxgAQPx5edAH4A/AH4A/AH4A/AEUQF1sBF/4v/F/4vviILJBRQANEZYLJHQIMKFpYABQhIiKC4QaMIhBHLF6AAVEhRQIF8ZuCF5B6GACYjMF9ZrOF8jAiKRgvvSEJROBo5gYEBw+IMCwfPB5BgWDxBPHCCBeVJxBgdJqIvJMCQcTCRAwRFxJ8KChQwODKwVJGBouKbZgXLDBQVLPBoZLDYxDMLxocQACLXOMBwARFxxgfLx5gfFyBgdLyIwcFyaRbFygwZFywwXFzAwVFzQwTFzgwRFzwxOFsIyKDSg";
|
||||
const MEH = "sFgwkBiIATDwoaUFi4ynQZ4uuGDzlTF1wwaFyowYFy4wWiAvZgIutGCgubSKRecMCQudMCBeeMCAufMBxegMBwuhMBheiMBgujMBRekMBQvvF0qQIL0xgIF94unSA4vuR1CQGF94upSAovuR1SQEF94urSAY/PCBivQF5z/DEBQ+DEB5ePCJYOEMBgNNF8MBHpogNHwqBNF/4vsEAovOX7TviBhYgFD5Q/EEJoANEAY/OLxgAQPx5edAH4A/AH4A/AH4A/AEUQF1sBF/4v/F/4vviIvtiIv/F9qeBACDgNB5ouSECAOLFyaBMKAYvrByQvgSBS/fD4jAfXxwQMADxAQF8iQLADjeGF96QoFwxgnLw4vwSEwuIMEpeJMEouKMEZeLMEYuMMEJeNMEIuOMD5ePMD4uQMDpeRGDguTSLYuUGDIuWGC4uYGCouaGCYucGCIueGJwthGRQaUA";
|
||||
const FROWN = "sFgwkBiIATDwoaUFi4ynQZ4uuGDzlTF1wwaFyowYFy4wWiAvZgIutGCgubSKRecMCQudMCBeeMCAufMBxegMBwuhMBheiMBgujMBRekMBQvvF0qQIL0xgIF94unSA4vuR1CQGF94upSAovuR1SQEF94urSAY/PCBivQF5z/DEBQ+DEB5ePCJYOEMBgNNF8MBHpogNHwqBNF/4vsEAovOX7TviBhYgFD5Q/EEJoANEAY/OLxgAQPx5edAH4A/AH4A/AH4A/AEUQF1sBF/4v/F/4vUgMRAAQZWFqwxWCgIuZGCYvSFxIcUFzYdTOZyNKSKQdCCJwuNMB5NDLzZOPIKAviCJguPJxpNEF94RLRyBONIKAvHNRQvRCKAMUJpIvOZxx9WAEbSTADReHF+CQmFxBglLxJglFxRgjLxZgjFxhghLxpghFxxgfLx5gfFyBgdLyIwcFyaRbFygwZFywwXFzAwVFzQwTFzgwRFzwxOFsIyKDSg";
|
||||
const THUMBS_UP = "sFgwkBiIAaiAiBDzYAQKYZQcLyAwsF4qSpcoxgoF4xgnRwwvxSEwvvFw4vwYEwv/F/4AOiAv/R1Av/F/6+PgIv/RzwvjLxQvkFxTujLxYvjFxaOiLxgvvR1wviR3gviR3YviFxg6iF7AwVRxowhFzUAgIvuMCSObF6YucSCJedF6IudSARQIHQheeAAIgKGAYufF+CbMF/4v/WYQv/F/6yPF/6OeF9wgNL/4v/F/4vhEQIv/R/4v/F/7ueF/4v/Xx4v/F/4v/F/4v/F/4v/F7ogOF/6OSEAgHCiAvrAwQHHRz4v/F/4v/F58QF8cBE4wPDGLYvHB5aTaKwQvUMS4vYGCx8QF5AwULwgvWYiZJQIAowXDowvYGJyqRFx4bKDRQA==";
|
||||
const THUMBS_DOWN = "sFgwkBiIAbiAoGEroAHLZgttMcK9RXEZgmFyZgHDZA/JFyogFDZQwHFqovXLiyQHB5wtaF6gubF/4v/F/4vwgIv/F7wgPF/6QTF/4v/F/4v/F/4v/F/4AdF/4v/YCIv/F/4v9EQIv/R/4v/F/7ueL+gFBiMQF8oiBE4wHHF/6QQF/4v/YigvugInBiAvrM5QvvM4gvqMFgvDMD0BF55gegJPKgIvEMDoeLF4pgdJ5QuGF7gjHABaQbFyRgbFygvZFyqQOEixgYF8RgMgIv/SH5gPYH6QfF8aQvMBgvjMBaQjMBYvkMBQv/SEAv/F/7APF/6QfF/4v/F/0BF8sQF/4vnF0rAJF9yOmSBAunF4xeoSAouqMAYTQA==";
|
||||
const HEART = "sFgwkBiIA/AH4A/AH4AogAADC1EQC4gaQCo8BIqYwRCyxdJDJoVLMJYuMGBIVNGBQYNDI5FOO5IXODI4WWI6BgGCywYTDIYVVO6gvXSAoYTDIQVTMAgYTDIJFUMAgYUACyOXAC7XWF7YurSAYvuR1iQCF/4v/F54utAH4A/AH4A/AH4A/AGMQF1sBF/4v/F58RF9sRF/4vgYFi+BMFouCF+CQqRwYvwSFQuEMFJeFMFIuGME5eHME4uIMEpeJMEouKMEZeLMEYuMMEJeNMEIuOMD5ePMD4uQMDpeRMDouSMDZeTMDYuUMDJeVMDIuWMC5eXMC4uYMCpeZMCouaMCZebMCYucMCJedF+CQQFzxgPFz5gPF8JgMXr5gPF0RgLL0ZgLF0hgJL0pgJF0xgHL05gHF1BgFL1JgFF1QwDF1gA/AH4A/AH4AJA=";
|
||||
const TX = "k8XwkBiIAYEYogLHBAUIiBNKGxooKEggvJCYYHDKxAMFAoRrOCRAsHCYqbNHQibLKAauOLBCJHQw6JMQBIJBRJDWJThK5JJJi5KbpaJKFBaKEE5ybGHRhcOACEQA";
|
||||
|
||||
|
||||
// Emojis are pairs with the form [ Image String, Unicode code point ]
|
||||
// For code points see https://unicode.org/emoji/charts/emoji-list.html
|
||||
const EMOJIS = [
|
||||
[ ':)', 0x1f642 ], // Slightly smiling
|
||||
[ ':|', 0x1f610 ], // Neutral
|
||||
[ ':(', 0x1f641 ], // Slightly frowning
|
||||
[ '+1', 0x1f44d ], // Thumbs up
|
||||
[ '-1', 0x1f44e ], // Thumbs down
|
||||
[ '<3', 0x02764 ], // Heart
|
||||
[ GRIN, 0x1f642 ], // Slightly smiling
|
||||
[ MEH, 0x1f610 ], // Neutral
|
||||
[ FROWN, 0x1f641 ], // Slightly frowning
|
||||
[ THUMBS_UP, 0x1f44d ], // Thumbs up
|
||||
[ THUMBS_DOWN, 0x1f44e ], // Thumbs down
|
||||
[ HEART, 0x02764 ], // Heart
|
||||
];
|
||||
const EMOJI_TRANSMISSION_MILLISECONDS = 5000;
|
||||
const BLINK_PERIOD_MILLISECONDS = 500;
|
||||
const TRANSMIT_BUZZ_MILLISECONDS = 200;
|
||||
const CYCLE_BUZZ_MILLISECONDS = 50;
|
||||
const WELCOME_MESSAGE = 'Emojuino:\r\n\r\n< Swipe >\r\nto select\r\n\r\nTap\r\nto transmit';
|
||||
|
||||
// Non-user-configurable constants
|
||||
const IMAGE_INDEX = 0;
|
||||
const CODE_POINT_INDEX = 1;
|
||||
const EMOJI_PX = 96;
|
||||
const EMOJI_X = (g.getWidth() - EMOJI_PX) / 2;
|
||||
const EMOJI_Y = (g.getHeight() - EMOJI_PX) / 2;
|
||||
const TX_X = 68;
|
||||
const TX_Y = 12;
|
||||
const FONT_SIZE = 24;
|
||||
const BTN_WATCH_OPTIONS = { repeat: true, debounce: 20, edge: "falling" };
|
||||
const UNICODE_CODE_POINT_ELIDED_UUID = [ 0x49, 0x6f, 0x49, 0x44, 0x55,
|
||||
0x54, 0x46, 0x2d, 0x33, 0x32 ];
|
||||
|
||||
|
||||
|
||||
// Global variables
|
||||
let emojiIndex = 0;
|
||||
let isToggleOn = false;
|
||||
|
|
@ -72,6 +91,7 @@ function transmitEmoji(image, codePoint, duration) {
|
|||
require('ble_eddystone_uid').advertise(UNICODE_CODE_POINT_ELIDED_UUID,
|
||||
instance);
|
||||
isTransmitting = true;
|
||||
drawImage(EMOJIS[emojiIndex][IMAGE_INDEX], true);
|
||||
|
||||
let displayIntervalId = setInterval(toggleImage, BLINK_PERIOD_MILLISECONDS,
|
||||
image);
|
||||
|
|
@ -85,14 +105,14 @@ function terminateEmoji(displayIntervalId) {
|
|||
NRF.setAdvertising({ });
|
||||
isTransmitting = false;
|
||||
clearInterval(displayIntervalId);
|
||||
drawImage(EMOJIS[emojiIndex][IMAGE_INDEX]);
|
||||
drawImage(EMOJIS[emojiIndex][IMAGE_INDEX], false);
|
||||
}
|
||||
|
||||
|
||||
// Toggle the display between image/off
|
||||
function toggleImage(image) {
|
||||
if(isToggleOn) {
|
||||
drawImage(EMOJIS[emojiIndex][IMAGE_INDEX]);
|
||||
drawImage(EMOJIS[emojiIndex][IMAGE_INDEX], true);
|
||||
}
|
||||
else {
|
||||
g.clear();
|
||||
|
|
@ -102,9 +122,15 @@ function toggleImage(image) {
|
|||
|
||||
|
||||
// Draw the given emoji
|
||||
function drawImage(image) {
|
||||
function drawImage(image, isTx) {
|
||||
g.clear();
|
||||
g.drawString(image, g.getWidth() / 2, g.getHeight() / 2);
|
||||
g.drawImage(require("heatshrink").decompress(atob(image)), EMOJI_X, EMOJI_Y);
|
||||
if(isTx) {
|
||||
g.drawImage(require("heatshrink").decompress(atob(TX)), TX_X, TX_Y);
|
||||
}
|
||||
else {
|
||||
g.drawString("< Swipe >", g.getWidth() / 2, g.getHeight() - FONT_SIZE);
|
||||
}
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
|
@ -131,15 +157,15 @@ function handleDrag(event) {
|
|||
// Special function to handle display switch on
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
if(on) {
|
||||
drawImage(EMOJIS[emojiIndex][IMAGE_INDEX]);
|
||||
drawImage(EMOJIS[emojiIndex][IMAGE_INDEX], false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// On start: display the first emoji and handle drag and touch events
|
||||
g.clear();
|
||||
g.setFont('Vector', 80);
|
||||
g.setFont('Vector', FONT_SIZE);
|
||||
g.setFontAlign(0, 0);
|
||||
drawImage(EMOJIS[emojiIndex][IMAGE_INDEX]);
|
||||
g.drawString(WELCOME_MESSAGE, g.getWidth() / 2, g.getHeight() / 2);
|
||||
Bangle.on('touch', handleTouch);
|
||||
Bangle.on('drag', handleDrag);
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwghC/AEkIxAABwUiAAwKBC6+AC6ERiIXDGBAXPGA8JzIAByQXKGA4XUA4eDmYAGJwQXVxEizAXPIgIXDwWZC6uIxIwCC6eIGAQX/C9i/FC5mCCw0yC5wAMC/4Xnx//ABf4C/Xzdw8zn4XkL/5f/L+oUDI6YX3AB4XeAH4AdA=="))
|
||||
require("heatshrink").decompress(atob("mEw4cA///7c0AYMXlm3gf42s1yvb5xT/ABdJkmStu27YCCtMkCKOACJdm7YRCyARQyQRLBwIRDoARTgVLtu3K4tJl4RQkvpCJdbtwRBkm5CKGZCKGTCKGSsgR/R4gRHpIMBCInaCJIIBARAR/CJtPB5FLCI1KEhMSCLN//4AE/QRbI/5H/CI4PCGpwRXp4RIpZFDCIQiJAQIRWAH4AGA"))
|
||||
|
|
|
|||
|
|
@ -2,4 +2,6 @@
|
|||
0.02: Increase text brightness, improve controls, (try to) reduce memory usage
|
||||
0.03: Only auto-start if active app is a clock, auto close after 1 hour of inactivity
|
||||
0.04: Setting to disable touch controls, minor bugfix
|
||||
0.05: Setting to disable double/triple press control, remove touch controls setting, reduce fadeout flicker
|
||||
0.05: Setting to disable double/triple press control, remove touch controls setting, reduce fadeout flicker
|
||||
0.06: Bangle.js 2 support
|
||||
0.07: Fix "previous" button image
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
If you have an Android phone with Gadgetbridge, this app allows you to view
|
||||
and control music playback.
|
||||
|
||||
 
|
||||
| Bangle.js 1 | Bangle.js 2 |
|
||||
|:-------------------------------------------|:-------------------------------------------|
|
||||
|  |  |
|
||||
|
||||
Download the [latest Gadgetbridge for Android here](https://f-droid.org/packages/nodomain.freeyourgadget.gadgetbridge/).
|
||||
|
||||
|
|
@ -23,25 +25,27 @@ Automatically load the app when you play music and close when the music stops.
|
|||
(If the app opened automatically, it closes after music has been paused for 5 minutes.)
|
||||
|
||||
**Simple button**:
|
||||
Disable double/triple pressing Button 2: always simply toggle play/pause.
|
||||
Disable double/triple pressing Middle Button: always simply toggle play/pause.
|
||||
(For music players which handle multiple button presses themselves.)
|
||||
|
||||
## Controls
|
||||
|
||||
### Buttons
|
||||
* Button 1: Volume up
|
||||
* Button 2:
|
||||
- Single press: toggle play/pause
|
||||
- Double press: next song
|
||||
- Triple press: previous song
|
||||
* Button 1 (*Bangle.js 1*): Volume up
|
||||
* Middle Button:
|
||||
- Single press: Toggle play/pause
|
||||
- Double press: Next song
|
||||
- Triple press: Previous song
|
||||
- Long-press: open application launcher
|
||||
* Button 3: Volume down
|
||||
* Button 3 (*Bangle.js 1*): Volume down
|
||||
|
||||
### Touch
|
||||
* Left: pause/previous song
|
||||
* Right: next song/resume
|
||||
* Center: toggle play/pause
|
||||
* Swipe: next/previous song
|
||||
* Left: Pause/previous song
|
||||
* Right: Next song/resume
|
||||
* Center: Toggle play/pause
|
||||
* Swipe left/right: Next/previous song
|
||||
* Swipe up/down (*Bangle.js 2*): Volume up/down
|
||||
|
||||
|
||||
## Creator
|
||||
|
||||
|
|
|
|||
|
|
@ -4,77 +4,9 @@
|
|||
**/
|
||||
let auto = false; // auto close if opened automatically
|
||||
let stat = "";
|
||||
let info = {
|
||||
artist: "",
|
||||
album: "",
|
||||
track: "",
|
||||
n: 0,
|
||||
c: 0,
|
||||
};
|
||||
const POUT = 300000; // auto close timeout when paused: 5 minutes (in ms)
|
||||
const IOUT = 3600000; // auto close timeout for inactivity: 1 hour (in ms)
|
||||
|
||||
///////////////////////
|
||||
// Self-repeating timeouts
|
||||
///////////////////////
|
||||
|
||||
// Clock
|
||||
let tock = -1;
|
||||
function tick() {
|
||||
if (!Bangle.isLCDOn()) {
|
||||
return;
|
||||
}
|
||||
const now = new Date;
|
||||
if (now.getHours()*60+now.getMinutes()!==tock) {
|
||||
drawDateTime();
|
||||
tock = now.getHours()*60+now.getMinutes();
|
||||
}
|
||||
setTimeout(tick, 1000); // we only show minute precision anyway
|
||||
}
|
||||
|
||||
// Fade out while paused and auto closing
|
||||
let fade = null;
|
||||
function fadeOut() {
|
||||
if (!Bangle.isLCDOn() || !fade) {
|
||||
return;
|
||||
}
|
||||
drawMusic(false); // don't clear: draw over existing text to prevent flicker
|
||||
setTimeout(fadeOut, 500);
|
||||
}
|
||||
function brightness() {
|
||||
if (!fade) {
|
||||
return 1;
|
||||
}
|
||||
return Math.max(0, 1-((Date.now()-fade)/POUT));
|
||||
}
|
||||
|
||||
// Scroll long track names
|
||||
// use an interval to get smooth movement
|
||||
let offset = null, // scroll Offset: null = no scrolling
|
||||
iScroll;
|
||||
function scroll() {
|
||||
offset += 10;
|
||||
drawScroller();
|
||||
}
|
||||
function scrollStart() {
|
||||
if (offset!==null) {
|
||||
return; // already started
|
||||
}
|
||||
offset = 0;
|
||||
if (Bangle.isLCDOn()) {
|
||||
if (!iScroll) {
|
||||
iScroll = setInterval(scroll, 200);
|
||||
}
|
||||
drawScroller();
|
||||
}
|
||||
}
|
||||
function scrollStop() {
|
||||
if (iScroll) {
|
||||
clearInterval(iScroll);
|
||||
iScroll = null;
|
||||
}
|
||||
offset = null;
|
||||
}
|
||||
const BANGLE2 = process.env.HWVERSION===2;
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
|
|
@ -85,21 +17,22 @@ function fitText(text) {
|
|||
return Infinity;
|
||||
}
|
||||
// make a guess, then shrink/grow until it fits
|
||||
const test = (s) => g.setFont("Vector", s).stringWidth(text);
|
||||
let best = Math.floor(24000/test(100));
|
||||
if (test(best)===240) { // good guess!
|
||||
const w = Bangle.appRect.w,
|
||||
test = (s) => g.setFont("Vector", s).stringWidth(text);
|
||||
let best = Math.floor(100*w/test(100));
|
||||
if (test(best)===w) { // good guess!
|
||||
return best;
|
||||
}
|
||||
if (test(best)<240) {
|
||||
if (test(best)<w) {
|
||||
do {
|
||||
best++;
|
||||
} while(test(best)<=240);
|
||||
} while(test(best)<=w);
|
||||
return best-1;
|
||||
}
|
||||
// width > 240
|
||||
// width > w
|
||||
do {
|
||||
best--;
|
||||
} while(test(best)>240);
|
||||
} while(test(best)>w);
|
||||
return best;
|
||||
}
|
||||
|
||||
|
|
@ -115,14 +48,6 @@ function textCode(text) {
|
|||
}
|
||||
return code%360;
|
||||
}
|
||||
// dark magic
|
||||
function hsv2rgb(h, s, v) {
|
||||
const f = (n) => {
|
||||
const k = (n+h/60)%6;
|
||||
return v-v*s*Math.max(Math.min(k, 4-k, 1), 0);
|
||||
};
|
||||
return {r: f(5), g: f(3), b: f(1)};
|
||||
}
|
||||
function f2hex(f) {
|
||||
return ("00"+(Math.round(f*255)).toString(16)).substr(-2);
|
||||
}
|
||||
|
|
@ -131,38 +56,218 @@ function f2hex(f) {
|
|||
* @return {string} Semi-random color to use for given info
|
||||
*/
|
||||
function infoColor(name) {
|
||||
let h, s, v;
|
||||
if (name==="num") {
|
||||
// always white
|
||||
h = 0;
|
||||
s = 0;
|
||||
} else {
|
||||
// make color depend deterministically on info
|
||||
let code = textCode(info[name]);
|
||||
switch(name) {
|
||||
case "track": // also use album
|
||||
code += textCode(info.album);
|
||||
// fallthrough
|
||||
case "album": // also use artist
|
||||
code += textCode(info.artist);
|
||||
}
|
||||
h = code%360;
|
||||
s = 0.7;
|
||||
// make color depend deterministically on info
|
||||
let code = textCode(layout[name].label);
|
||||
switch(name) {
|
||||
case "title": // also use album and artist
|
||||
code += textCode(layout.album.label);
|
||||
// fallthrough
|
||||
case "album": // also use artist
|
||||
code += textCode(layout.artist.label);
|
||||
}
|
||||
v = brightness();
|
||||
const rgb = hsv2rgb(h, s, v);
|
||||
return "#"+f2hex(rgb.r)+f2hex(rgb.g)+f2hex(rgb.b);
|
||||
let rgb;
|
||||
if (g.getBPP()===3) {
|
||||
// only pick 3-bit colors, always at full brightness
|
||||
rgb = [code&1, (code&2)/2, (code&4)/4];
|
||||
if (g.setColor(rgb[0], rgb[1], rgb[2]).getColor()===g.theme.bg) {
|
||||
// avoid picking the bg color
|
||||
rgb = rgb.map(c => 1-c);
|
||||
}
|
||||
return "#"+f2hex(rgb[0])+f2hex(rgb[1])+f2hex(rgb[2]);
|
||||
} else {
|
||||
// pick any hue, adjust for brightness
|
||||
const h = code%360, s = 0.7, b = brightness();
|
||||
return E.HSBtoRGB(h/360, s, b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render scrolling title
|
||||
* @param l
|
||||
*/
|
||||
function rScroller(l) {
|
||||
g.setFont("Vector", Math.round(g.getHeight()*l.fsz.slice(0, -1)/100));
|
||||
const w = g.stringWidth(l.label)+40,
|
||||
y = l.y+l.h/2;
|
||||
l.offset = l.offset%w;
|
||||
g.setClipRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1)
|
||||
.setColor(l.col)
|
||||
.setFontAlign(-1, 0) // left center
|
||||
.clearRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1)
|
||||
.drawString(l.label, l.x-l.offset+40, y)
|
||||
.drawString(l.label, l.x-l.offset+40+w, y);
|
||||
}
|
||||
/**
|
||||
* Remember track color until info changes
|
||||
* Because we need this every time we move the scroller
|
||||
* @return {string}
|
||||
* Render title
|
||||
* @param l
|
||||
*/
|
||||
function trackColor() {
|
||||
if (!("track_color" in info) || fade) {
|
||||
info.track_color = infoColor("track");
|
||||
function rTitle(l) {
|
||||
if (l.offset!==null) {
|
||||
rScroller(l); // already scrolling
|
||||
return;
|
||||
}
|
||||
return info.track_color;
|
||||
let size = fitText(l.label);
|
||||
if (size<l.h/2) {
|
||||
// the title is too long: start the scroller
|
||||
scrollStart();
|
||||
} else {
|
||||
rInfo(l);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Render info field
|
||||
* @param l
|
||||
*/
|
||||
function rInfo(l) {
|
||||
let size = fitText(l.label);
|
||||
if (size>l.h) {
|
||||
size = l.h;
|
||||
}
|
||||
g.setFont("Vector", size)
|
||||
.setFontAlign(0, -1) // center top
|
||||
.drawString(l.label, l.x+l.w/2, l.y);
|
||||
}
|
||||
/**
|
||||
* Render icon
|
||||
* @param l
|
||||
*/
|
||||
function rIcon(l) {
|
||||
const x2 = l.x+l.w-1,
|
||||
y2 = l.y+l.h-1;
|
||||
switch(l.icon) {
|
||||
case "pause":
|
||||
const w13 = l.w/3;
|
||||
g.drawRect(l.x, l.y, l.x+w13, y2);
|
||||
g.drawRect(l.x+l.w-w13, l.y, x2, y2);
|
||||
break;
|
||||
case "play":
|
||||
g.drawPoly([
|
||||
l.x, l.y,
|
||||
x2, l.y+l.h/2,
|
||||
l.x, y2,
|
||||
], true);
|
||||
break;
|
||||
case "previous":
|
||||
const w15 = l.w*1/5;
|
||||
g.drawPoly([
|
||||
x2, l.y,
|
||||
l.x+w15, l.y+l.h/2,
|
||||
x2, y2,
|
||||
], true);
|
||||
g.drawRect(l.x, l.y, l.x+w15, y2);
|
||||
break;
|
||||
case "next":
|
||||
const w45 = l.w*4/5;
|
||||
g.drawPoly([
|
||||
l.x, l.y,
|
||||
l.x+w45, l.y+l.h/2,
|
||||
l.x, y2,
|
||||
], true);
|
||||
g.drawRect(l.x+w45, l.y, x2, y2);
|
||||
break;
|
||||
default: // red X
|
||||
console.log(`Unknown icon: ${l.icon}`);
|
||||
g.setColor("#f00")
|
||||
.drawRect(l.x, l.y, x2, y2)
|
||||
.drawLine(l.x, l.y, x2, y2)
|
||||
.drawLine(l.x, y2, x2, l.y);
|
||||
}
|
||||
}
|
||||
let layout;
|
||||
function makeUI() {
|
||||
global.gbmusic_active = true; // we don't need our widget (needed for <2.09 devices)
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
delete (global.gbmusic_active);
|
||||
const Layout = require("Layout");
|
||||
layout = new Layout({
|
||||
type: "v", c: [
|
||||
{
|
||||
type: "h", fillx: 1, c: [
|
||||
{id: "time", type: "txt", label: "88:88", valign: -1, halign: -1, font: "8%", bgCol: g.theme.bg},
|
||||
{fillx: 1},
|
||||
{id: "num", type: "txt", label: "88:88", valign: -1, halign: 1, font: "12%", bgCol: g.theme.bg},
|
||||
BANGLE2 ? {} : {id: "up", type: "txt", label: " +", font: "6x8:2"},
|
||||
],
|
||||
},
|
||||
{id: "title", type: "custom", label: "", fillx: 1, filly: 2, offset: null, font: "Vector:20%", render: rTitle, bgCol: g.theme.bg},
|
||||
{id: "artist", type: "custom", label: "", fillx: 1, filly: 1, size: 30, render: rInfo, bgCol: g.theme.bg},
|
||||
{id: "album", type: "custom", label: "", fillx: 1, filly: 1, size: 20, render: rInfo, bgCol: g.theme.bg},
|
||||
{height: 10},
|
||||
{
|
||||
type: "h", c: [
|
||||
{width: 3},
|
||||
{id: "prev", type: "custom", height: 15, width: 15, icon: "previous", render: rIcon, bgCol: g.theme.bg},
|
||||
{id: "date", type: "txt", halign: 0, valign: 1, label: "", font: "8%", fillx: 1, bgCol: g.theme.bg},
|
||||
{id: "next", type: "custom", height: 15, width: 15, icon: "next", render: rIcon, bgCol: g.theme.bg},
|
||||
BANGLE2 ? {width: 3} : {id: "down", type: "txt", label: " -", font: "6x8:2"},
|
||||
],
|
||||
},
|
||||
{height: 10},
|
||||
],
|
||||
}, {lazy: true});
|
||||
layout.render();
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
// Self-repeating timeouts
|
||||
///////////////////////
|
||||
|
||||
// Clock
|
||||
let tock = -1;
|
||||
function tick() {
|
||||
if (!BANGLE2 && !Bangle.isLCDOn()) {
|
||||
return;
|
||||
}
|
||||
const now = new Date();
|
||||
if (now.getHours()*60+now.getMinutes()!==tock) {
|
||||
drawDateTime();
|
||||
tock = now.getHours()*60+now.getMinutes();
|
||||
}
|
||||
setTimeout(tick, 1000); // we only show minute precision anyway
|
||||
}
|
||||
|
||||
// Fade out while paused and auto closing
|
||||
let fade = null;
|
||||
function fadeOut() {
|
||||
if (BANGLE2 || !Bangle.isLCDOn() || !fade) {
|
||||
return;
|
||||
}
|
||||
layout.render();
|
||||
setTimeout(fadeOut, 500);
|
||||
}
|
||||
function brightness() {
|
||||
if (!fade) {
|
||||
return 1;
|
||||
}
|
||||
return Math.max(0, 1-((Date.now()-fade)/POUT));
|
||||
}
|
||||
|
||||
// Scroll long track names
|
||||
// use an interval to get smooth movement
|
||||
let iScroll;
|
||||
function scroll() {
|
||||
layout.title.offset += 10;
|
||||
rScroller(layout.title);
|
||||
}
|
||||
function scrollStart() {
|
||||
if (layout.title.offset!==null) {
|
||||
return; // already started
|
||||
}
|
||||
layout.title.offset = 0;
|
||||
if (BANGLE2 || Bangle.isLCDOn()) {
|
||||
if (!iScroll) {
|
||||
iScroll = setInterval(scroll, 200);
|
||||
}
|
||||
rScroller(layout.title);
|
||||
}
|
||||
}
|
||||
function scrollStop() {
|
||||
if (iScroll) {
|
||||
clearInterval(iScroll);
|
||||
iScroll = null;
|
||||
}
|
||||
layout.title.offset = null;
|
||||
}
|
||||
|
||||
////////////////////
|
||||
|
|
@ -172,10 +277,9 @@ function trackColor() {
|
|||
* Draw date and time
|
||||
*/
|
||||
function drawDateTime() {
|
||||
const now = new Date;
|
||||
const now = new Date();
|
||||
const l = require("locale");
|
||||
const is12 = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
||||
let time;
|
||||
if (is12) {
|
||||
const d12 = new Date(now.getTime());
|
||||
const hour = d12.getHours();
|
||||
|
|
@ -184,29 +288,35 @@ function drawDateTime() {
|
|||
} else if (hour>12) {
|
||||
d12.setHours(hour-12);
|
||||
}
|
||||
time = l.time(d12, true)+l.meridian(now);
|
||||
layout.time.label = l.time(d12, true)+l.meridian(now);
|
||||
} else {
|
||||
time = l.time(now, true);
|
||||
layout.time.label = l.time(now, true);
|
||||
}
|
||||
g.reset();
|
||||
g.setFont("Vector", 24)
|
||||
.setFontAlign(-1, -1) // top left
|
||||
.clearRect(10, 30, 119, 54)
|
||||
.drawString(time, 10, 30);
|
||||
|
||||
const date = require("locale").date(now, true);
|
||||
g.setFont("Vector", 16)
|
||||
.setFontAlign(0, 1) // bottom center
|
||||
.setClipRect(35, 198, 199, 214)
|
||||
.clearRect(31, 198, 199, 214)
|
||||
.drawString(date, 119, 240-26);
|
||||
layout.date.label = require("locale").date(now, true);
|
||||
layout.render();
|
||||
}
|
||||
|
||||
function drawControls() {
|
||||
let l = layout;
|
||||
const cc = a => (a ? "#f00" : "#0f0"); // control color: red for active, green for inactive
|
||||
if (!BANGLE2) {
|
||||
l.up.col = cc("volumeup" in tCommand);
|
||||
l.down.col = cc("volumedown" in tCommand);
|
||||
}
|
||||
l.prev.icon = (stat==="play") ? "pause" : "previous";
|
||||
l.prev.col = cc("prev" in tCommand || "pause" in tCommand);
|
||||
l.next.icon = (stat==="play") ? "next" : "play";
|
||||
l.next.col = cc("next" in tCommand || "play" in tCommand);
|
||||
layout.render();
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
// GB event handlers
|
||||
///////////////////////
|
||||
/**
|
||||
* Draw track number and total count
|
||||
* @param {boolean} clr - Clear area before redrawing?
|
||||
* Mangle track number and total count for display
|
||||
*/
|
||||
function drawNum(clr) {
|
||||
function formatNum(info) {
|
||||
let num = "";
|
||||
if ("n" in info && info.n>0) {
|
||||
num = "#"+info.n;
|
||||
|
|
@ -214,198 +324,26 @@ function drawNum(clr) {
|
|||
num += "/"+info.c;
|
||||
}
|
||||
}
|
||||
g.reset();
|
||||
g.setFont("Vector", 30)
|
||||
.setFontAlign(1, -1); // top right
|
||||
if (clr) {
|
||||
g.clearRect(225, 30, 120, 60);
|
||||
}
|
||||
g.drawString(num, 225, 30);
|
||||
}
|
||||
/**
|
||||
* Clear rectangle used by track title
|
||||
*/
|
||||
function clearTrack() {
|
||||
g.clearRect(0, 60, 239, 119);
|
||||
}
|
||||
/**
|
||||
* Draw track title
|
||||
* @param {boolean} clr - Clear area before redrawing?
|
||||
*/
|
||||
function drawTrack(clr) {
|
||||
let size = fitText(info.track);
|
||||
if (size<25) {
|
||||
// the title is too long: start the scroller
|
||||
scrollStart();
|
||||
return;
|
||||
} else {
|
||||
scrollStop();
|
||||
}
|
||||
// stationary track
|
||||
if (size>40) {
|
||||
size = 40;
|
||||
}
|
||||
g.reset();
|
||||
g.setFont("Vector", size)
|
||||
.setFontAlign(0, 1) // center bottom
|
||||
.setColor(trackColor());
|
||||
if (clr) {
|
||||
clearTrack();
|
||||
}
|
||||
g.drawString(info.track, 119, 109);
|
||||
}
|
||||
/**
|
||||
* Draw scrolling track title
|
||||
*/
|
||||
function drawScroller() {
|
||||
g.reset();
|
||||
g.setFont("Vector", 40);
|
||||
const w = g.stringWidth(info.track)+40;
|
||||
offset = offset%w;
|
||||
g.setFontAlign(-1, 1) // left bottom
|
||||
.setColor(trackColor());
|
||||
clearTrack();
|
||||
g.drawString(info.track, -offset+40, 109)
|
||||
.drawString(info.track, -offset+40+w, 109);
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw track artist and album
|
||||
* @param {boolean} clr - Clear area before redrawing?
|
||||
*/
|
||||
function drawArtistAlbum(clr) {
|
||||
// we just use small enough fonts to make these always fit
|
||||
// calculate stuff before clear+redraw
|
||||
const aCol = infoColor("artist");
|
||||
const bCol = infoColor("album");
|
||||
let aSiz = fitText(info.artist);
|
||||
if (aSiz>30) {
|
||||
aSiz = 30;
|
||||
}
|
||||
let bSiz = fitText(info.album);
|
||||
if (bSiz>20) {
|
||||
bSiz = 20;
|
||||
}
|
||||
g.reset();
|
||||
if (clr) {
|
||||
g.clearRect(0, 120, 240, 189);
|
||||
}
|
||||
let top = 124;
|
||||
if (info.artist) {
|
||||
g.setFont("Vector", aSiz)
|
||||
.setFontAlign(0, -1) // center top
|
||||
.setColor(aCol)
|
||||
.drawString(info.artist, 119, top);
|
||||
top += aSiz+4; // fit album neatly under artist
|
||||
}
|
||||
if (info.album) {
|
||||
g.setFont("Vector", bSiz)
|
||||
.setFontAlign(0, -1) // center top
|
||||
.setColor(bCol)
|
||||
.drawString(info.album, 119, top);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} icon Icon name
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} s Icon size
|
||||
*/
|
||||
function drawIcon(icon, x, y, s) {
|
||||
({
|
||||
pause: function(x, y, s) {
|
||||
const w1 = s/3;
|
||||
g.drawRect(x, y, x+w1, y+s);
|
||||
g.drawRect(x+s-w1, y, x+s, y+s);
|
||||
},
|
||||
play: function(x, y, s) {
|
||||
g.drawPoly([
|
||||
x, y,
|
||||
x+s, y+s/2,
|
||||
x, y+s,
|
||||
], true);
|
||||
},
|
||||
previous: function(x, y, s) {
|
||||
const w2 = s*1/5;
|
||||
g.drawPoly([
|
||||
x+s, y,
|
||||
x+w2, y+s/2,
|
||||
x+s, y+s,
|
||||
], true);
|
||||
g.drawRect(x, y, x+w2, y+s);
|
||||
},
|
||||
next: function(x, y, s) {
|
||||
const w2 = s*4/5;
|
||||
g.drawPoly([
|
||||
x, y,
|
||||
x+w2, y+s/2,
|
||||
x, y+s,
|
||||
], true);
|
||||
g.drawRect(x+w2, y, x+s, y+s);
|
||||
},
|
||||
})[icon](x, y, s);
|
||||
}
|
||||
function controlColor(ctrl) {
|
||||
return (ctrl in tCommand) ? "#ff0000" : "#008800";
|
||||
}
|
||||
function drawControl(ctrl, x, y) {
|
||||
g.setColor(controlColor(ctrl));
|
||||
const s = 20;
|
||||
if (stat!==controlState) {
|
||||
g.clearRect(x, y, x+s, y+s);
|
||||
}
|
||||
drawIcon(ctrl, x, y, s);
|
||||
}
|
||||
let controlState;
|
||||
function drawControls() {
|
||||
g.reset();
|
||||
if (stat==="play") {
|
||||
// left touch
|
||||
drawControl("pause", 10, 190);
|
||||
// right touch
|
||||
drawControl("next", 200, 190);
|
||||
} else {
|
||||
drawControl("previous", 10, 190);
|
||||
drawControl("play", 200, 190);
|
||||
}
|
||||
g.setFont("6x8", 2);
|
||||
// BTN1
|
||||
g.setFontAlign(1, -1);
|
||||
g.setColor(controlColor("volumeup"));
|
||||
g.drawString("+", 240, 30);
|
||||
// BTN2
|
||||
g.setFontAlign(1, 1);
|
||||
g.setColor(controlColor("volumedown"));
|
||||
g.drawString("-", 240, 210);
|
||||
controlState = stat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} [clr=true] Clear area before redrawing?
|
||||
*/
|
||||
function drawMusic(clr) {
|
||||
clr = !(clr===false); // undefined means yes
|
||||
drawNum(clr);
|
||||
drawTrack(clr);
|
||||
drawArtistAlbum(clr);
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
// GB event handlers
|
||||
///////////////////////
|
||||
/**
|
||||
* Update music info
|
||||
* @param {Object} e - Gadgetbridge musicinfo event
|
||||
* @param {Object} info - Gadgetbridge musicinfo event
|
||||
*/
|
||||
function musicInfo(e) {
|
||||
info = e;
|
||||
delete (info.t);
|
||||
offset = null;
|
||||
if (Bangle.isLCDOn()) {
|
||||
drawMusic();
|
||||
}
|
||||
function musicInfo(info) {
|
||||
scrollStop();
|
||||
layout.title.label = info.track || "";
|
||||
layout.album.label = info.album || "";
|
||||
layout.artist.label = info.artist || "";
|
||||
// color depends on all labels
|
||||
layout.title.col = infoColor("title");
|
||||
layout.album.col = infoColor("album");
|
||||
layout.artist.col = infoColor("artist");
|
||||
layout.num.label = formatNum(info);
|
||||
layout.render();
|
||||
rTitle(layout.title); // force redraw of title, or scroller might break
|
||||
// reset auto exit interval
|
||||
if (tIxt) {
|
||||
clearTimeout(tIxt);
|
||||
tIxt = null;
|
||||
|
|
@ -435,7 +373,6 @@ function musicState(e) {
|
|||
tIxt = null;
|
||||
}
|
||||
fade = null;
|
||||
delete info.track_color;
|
||||
if (auto) { // auto opened -> auto close
|
||||
switch(stat) {
|
||||
case "stop": // never actually happens with my phone :-(
|
||||
|
|
@ -444,7 +381,7 @@ function musicState(e) {
|
|||
case "play":
|
||||
// if inactive for double song duration (or an hour if unknown), load the clock
|
||||
// i.e. phone finished playing without bothering to notify the watch
|
||||
tIxt = setTimeout(load, (info.dur*2000) || IOUT);
|
||||
tIxt = setTimeout(load, (e.dur*2000) || IOUT);
|
||||
break;
|
||||
case "pause":
|
||||
default:
|
||||
|
|
@ -456,8 +393,7 @@ function musicState(e) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (Bangle.isLCDOn()) {
|
||||
drawMusic(false); // redraw in case we were fading out but resumed play
|
||||
if (BANGLE2 || Bangle.isLCDOn()) {
|
||||
drawControls();
|
||||
}
|
||||
}
|
||||
|
|
@ -473,30 +409,34 @@ function musicState(e) {
|
|||
*/
|
||||
let tPress, nPress = 0;
|
||||
function startButtonWatches() {
|
||||
// BTN1/3: volume control
|
||||
// Wait for falling edge to avoid messing with volume while long-pressing BTN3
|
||||
// to reload the watch (and same for BTN2 for consistency)
|
||||
setWatch(() => { sendCommand("volumeup"); }, BTN1, {repeat: true, edge: "falling"});
|
||||
setWatch(() => { sendCommand("volumedown"); }, BTN3, {repeat: true, edge: "falling"});
|
||||
let btn = BTN1;
|
||||
if (!BANGLE2) {
|
||||
// BTN1/3: volume control
|
||||
// Wait for falling edge to avoid messing with volume while long-pressing BTN3
|
||||
// to reload the watch (and same for BTN2 for consistency)
|
||||
setWatch(() => { sendCommand("volumeup"); }, BTN1, {repeat: true, edge: "falling"});
|
||||
setWatch(() => { sendCommand("volumedown"); }, BTN3, {repeat: true, edge: "falling"});
|
||||
btn = BTN2;
|
||||
}
|
||||
|
||||
// BTN2: long-press for launcher, otherwise depends on number of presses
|
||||
// middle button: long-press for launcher, otherwise depends on number of presses
|
||||
setWatch(() => {
|
||||
if (nPress===0) {
|
||||
tPress = setTimeout(() => {Bangle.showLauncher();}, 3000);
|
||||
}
|
||||
}, BTN2, {repeat: true, edge: "rising"});
|
||||
}, btn, {repeat: true, edge: "rising"});
|
||||
const s = require("Storage").readJSON("gbmusic.json", 1) || {};
|
||||
if (s.simpleButton) {
|
||||
setWatch(() => {
|
||||
clearTimeout(tPress);
|
||||
togglePlay();
|
||||
}, BTN2, {repeat: true, edge: "falling"});
|
||||
}, btn, {repeat: true, edge: "falling"});
|
||||
} else {
|
||||
setWatch(() => {
|
||||
nPress++;
|
||||
clearTimeout(tPress);
|
||||
tPress = setTimeout(handleButton2Press, 500);
|
||||
}, BTN2, {repeat: true, edge: "falling"});
|
||||
}, btn, {repeat: true, edge: "falling"});
|
||||
}
|
||||
}
|
||||
function handleButton2Press() {
|
||||
|
|
@ -524,7 +464,7 @@ let tCommand = {};
|
|||
*/
|
||||
function sendCommand(command) {
|
||||
Bluetooth.println(JSON.stringify({t: "music", n: command}));
|
||||
// for controlColor
|
||||
// for control color
|
||||
if (command in tCommand) {
|
||||
clearTimeout(tCommand[command]);
|
||||
}
|
||||
|
|
@ -539,18 +479,29 @@ function sendCommand(command) {
|
|||
function togglePlay() {
|
||||
sendCommand(stat==="play" ? "pause" : "play");
|
||||
}
|
||||
function startTouchWatches() {
|
||||
function pausePrev() {
|
||||
sendCommand(stat==="play" ? "pause" : "previous");
|
||||
}
|
||||
function nextPlay() {
|
||||
sendCommand(stat==="play" ? "next" : "play");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup touch+swipe for Bangle.js 1
|
||||
*/
|
||||
function touch1() {
|
||||
Bangle.on("touch", side => {
|
||||
if (!Bangle.isLCDOn()) {return;} // for <2v10 firmware
|
||||
switch(side) {
|
||||
case 1:
|
||||
sendCommand(stat==="play" ? "pause" : "previous");
|
||||
pausePrev();
|
||||
break;
|
||||
case 2:
|
||||
sendCommand(stat==="play" ? "next" : "play");
|
||||
nextPlay();
|
||||
break;
|
||||
case 3:
|
||||
default:
|
||||
togglePlay();
|
||||
break;
|
||||
}
|
||||
});
|
||||
Bangle.on("swipe", dir => {
|
||||
|
|
@ -558,16 +509,56 @@ function startTouchWatches() {
|
|||
sendCommand(dir===1 ? "previous" : "next");
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Setup touch+swipe for Bangle.js 2
|
||||
*/
|
||||
function touch2() {
|
||||
Bangle.on("touch", (side, xy) => {
|
||||
const ar = Bangle.appRect;
|
||||
if (xy.x<ar.x+ar.w/3) {
|
||||
pausePrev();
|
||||
} else if (xy.x>ar.x+ar.w*2/3) {
|
||||
nextPlay();
|
||||
} else {
|
||||
togglePlay();
|
||||
}
|
||||
});
|
||||
// swiping
|
||||
let drag;
|
||||
Bangle.on("drag", e => {
|
||||
if (!drag) { // start dragging
|
||||
drag = {x: e.x, y: e.y};
|
||||
} else if (!e.b) { // released
|
||||
const dx = e.x-drag.x, dy = e.y-drag.y;
|
||||
drag = null;
|
||||
if (Math.abs(dx)>Math.abs(dy)+10) {
|
||||
// horizontal
|
||||
sendCommand(dx>0 ? "previous" : "next");
|
||||
} else if (Math.abs(dy)>Math.abs(dx)+10) {
|
||||
// vertical
|
||||
sendCommand(dy>0 ? "volumedown" : "volumeup");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function startTouchWatches() {
|
||||
if (BANGLE2) {
|
||||
touch2();
|
||||
} else {
|
||||
touch1();
|
||||
}
|
||||
}
|
||||
function startLCDWatch() {
|
||||
if (BANGLE2) {
|
||||
return; // always keep drawing
|
||||
}
|
||||
Bangle.on("lcdPower", (on) => {
|
||||
if (on) {
|
||||
// redraw and resume scrolling
|
||||
tick();
|
||||
drawMusic();
|
||||
drawControls();
|
||||
layout.render();
|
||||
fadeOut();
|
||||
if (offset!==null) {
|
||||
drawScroller();
|
||||
if (offset.offset!==null) {
|
||||
if (!iScroll) {
|
||||
iScroll = setInterval(scroll, 200);
|
||||
}
|
||||
|
|
@ -585,15 +576,10 @@ function startLCDWatch() {
|
|||
/////////////////////
|
||||
// Startup
|
||||
/////////////////////
|
||||
// check for saved music stat (by widget) to load
|
||||
g.clear();
|
||||
global.gbmusic_active = true; // we don't need our widget (needed for <2.09 devices)
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
delete (global.gbmusic_active);
|
||||
|
||||
function startEmulator() {
|
||||
if (typeof Bluetooth==="undefined") { // emulator!
|
||||
if (typeof Bluetooth==="undefined" || typeof Bluetooth.println==="undefined") { // emulator!
|
||||
Bluetooth = {
|
||||
println: (line) => {console.log("Bluetooth:", line);},
|
||||
};
|
||||
|
|
@ -609,6 +595,7 @@ function startWatches() {
|
|||
}
|
||||
|
||||
function start() {
|
||||
makeUI();
|
||||
// start listening for music updates
|
||||
const _GB = global.GB;
|
||||
global.GB = (event) => {
|
||||
|
|
@ -628,43 +615,39 @@ function start() {
|
|||
return;
|
||||
}
|
||||
};
|
||||
drawMusic();
|
||||
drawControls();
|
||||
startWatches();
|
||||
tick();
|
||||
startEmulator();
|
||||
}
|
||||
|
||||
function init() {
|
||||
// check for saved music status (by widget) to load
|
||||
let saved = require("Storage").readJSON("gbmusic.load.json", true);
|
||||
require("Storage").erase("gbmusic.load.json");
|
||||
if (saved) {
|
||||
// autoloaded: load state was saved by widget
|
||||
info = saved.info;
|
||||
stat = saved.state;
|
||||
delete saved;
|
||||
auto = true;
|
||||
start();
|
||||
} else {
|
||||
delete saved;
|
||||
let s = require("Storage").readJSON("gbmusic.json", 1) || {};
|
||||
if (!("autoStart" in s)) {
|
||||
// user opened the app, but has not picked a setting yet
|
||||
// ask them about autoloading now
|
||||
E.showPrompt(
|
||||
"Automatically load\n"+
|
||||
"when playing music?\n",
|
||||
).then(choice => {
|
||||
s.autoStart = choice;
|
||||
require("Storage").writeJSON("gbmusic.json", s);
|
||||
delete s;
|
||||
setTimeout(start, 0);
|
||||
});
|
||||
} else {
|
||||
delete s;
|
||||
start();
|
||||
}
|
||||
musicInfo(saved.info);
|
||||
musicState(saved.state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
init();
|
||||
|
||||
let s = require("Storage").readJSON("gbmusic.json", 1) || {};
|
||||
if ("autoStart" in s) {
|
||||
start();
|
||||
return;
|
||||
}
|
||||
|
||||
// user opened the app, but has not picked a autoStart setting yet
|
||||
// ask them about autoloading now
|
||||
E.showPrompt(
|
||||
"Automatically load\n"+
|
||||
"when playing music?\n"
|
||||
).then(choice => {
|
||||
s.autoStart = choice;
|
||||
require("Storage").writeJSON("gbmusic.json", s);
|
||||
setTimeout(start, 0);
|
||||
});
|
||||
}
|
||||
init();
|
||||
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
|
@ -15,7 +15,7 @@ GB({"t":"notify","id":1592721714,"src":"ALARMCLOCKRECEIVER"})
|
|||
GB({"t":"notify-","id":1592721714})
|
||||
|
||||
// Weather update (doesn't show a notification, not handled by gbridge app: see weather app)
|
||||
GB({"t":"weather","temp":288,"hum":94,"txt":"Light rain","wind":0,"loc":"Test City"})
|
||||
GB({"t":"weather","temp":288,"hum":94,"txt":"Light rain","wind":0,"wdir":120,"loc":"Test City"})
|
||||
|
||||
// Nextcloud updated a file
|
||||
GB({"t":"notify","id":1594184421,"src":"Nextcloud","title":"Downloaded","body":"test.file downloaded"})
|
||||
|
|
|
|||
|
|
@ -6,3 +6,4 @@
|
|||
0.05: Fix daily summary calculation
|
||||
0.06: Fix daily health summary for movement (a line got deleted!)
|
||||
0.07: Added coloured bar charts
|
||||
0.08: Suppress bleed through of E.showMenu's when displaying bar charts
|
||||
|
|
|
|||
|
|
@ -236,6 +236,9 @@ Bangle.on('swipe', dir => {
|
|||
|
||||
// use setWatch() as Bangle.setUI("updown",..) interacts with swipes
|
||||
function setButton(fn) {
|
||||
// cancel callback, otherwise a slight up down movement will show the E.showMenu()
|
||||
Bangle.setUI("updown", undefined);
|
||||
|
||||
if (process.env.HWVERSION == 1)
|
||||
btn = setWatch(fn, BTN2);
|
||||
else
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: Core functionnality based entirely on hidmsic
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwhC/AH4A5xGICquZzAVUAAIXQCogXQCoxHPCox0BxIXNxIVFBAQXPUAwXPBw4XowAvuC/4X/C9sIC6kIxGZzIXSFgIWBC6QWEC6RECAAOJwAXQFwoXLxAqBC4MICweZCxhWEC4mICxxuDA4I3BCxQ/FQxpyEK6AucC4idMI5OICyQwBQpgA/AH4Au"))
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
var storage = require('Storage');
|
||||
|
||||
const settings = storage.readJSON('setting.json',1) || { HID: false };
|
||||
|
||||
var sendHid, next, prev, toggle, up, down, profile;
|
||||
var lasty = 0;
|
||||
var lastx = 0;
|
||||
|
||||
if (settings.HID=="kbmedia") {
|
||||
profile = 'Music';
|
||||
sendHid = function (code, cb) {
|
||||
try {
|
||||
NRF.sendHIDReport([1,code], () => {
|
||||
NRF.sendHIDReport([1,0], () => {
|
||||
if (cb) cb();
|
||||
});
|
||||
});
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
};
|
||||
next = function (cb) { sendHid(0x01, cb); };
|
||||
prev = function (cb) { sendHid(0x02, cb); };
|
||||
toggle = function (cb) { sendHid(0x10, cb); };
|
||||
up = function (cb) {sendHid(0x40, cb); };
|
||||
down = function (cb) { sendHid(0x80, cb); };
|
||||
} else {
|
||||
E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) {
|
||||
if (enable) {
|
||||
settings.HID = "kbmedia";
|
||||
require("Storage").write('setting.json', settings);
|
||||
setTimeout(load, 1000, "hidmsicswipe.app.js");
|
||||
} else setTimeout(load, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
function drawApp() {
|
||||
g.clear();
|
||||
if(Bangle.isLocked()==false) E.showMessage('Swipe', 'Music');
|
||||
else E.showMessage('Locked', 'Music');
|
||||
}
|
||||
|
||||
if (next) {
|
||||
setWatch(function(e) {
|
||||
var len = e.time - e.lastTime;
|
||||
E.showMessage('lock');
|
||||
setTimeout(drawApp, 1000);
|
||||
Bangle.setLocked(true);
|
||||
}, BTN1, { edge:"falling",repeat:true,debounce:50});
|
||||
Bangle.on('drag', function(e) {
|
||||
if(!e.b){
|
||||
//console.log(lasty);
|
||||
//console.log(lastx);
|
||||
if(lasty > 40){
|
||||
E.showMessage('down');
|
||||
setTimeout(drawApp, 1000);
|
||||
down(() => {});
|
||||
}
|
||||
else if(lasty < -40){
|
||||
E.showMessage('up');
|
||||
setTimeout(drawApp, 1000);
|
||||
up(() => {});
|
||||
} else if(lastx < -40){
|
||||
E.showMessage('prev');
|
||||
setTimeout(drawApp, 1000);
|
||||
prev(() => {});
|
||||
} else if(lastx > 40){
|
||||
E.showMessage('next');
|
||||
setTimeout(drawApp, 1000);
|
||||
next(() => {});
|
||||
} else if(lastx==0 && lasty==0){
|
||||
E.showMessage('play/pause');
|
||||
setTimeout(drawApp, 1000);
|
||||
toggle(() => {});
|
||||
}
|
||||
lastx = 0;
|
||||
lasty = 0;
|
||||
}
|
||||
else{
|
||||
lastx = lastx + e.dx;
|
||||
lasty = lasty + e.dy;
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.on("lock", function(on) {
|
||||
if(!on){
|
||||
E.showMessage('unlock');
|
||||
setTimeout(drawApp, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
drawApp();
|
||||
}
|
||||
|
After Width: | Height: | Size: 632 B |
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Remove messages on disconnect
|
||||
|
|
|
|||
|
|
@ -95,7 +95,8 @@ E.on('AMS',a=>{
|
|||
Bangle.musicControl = cmd => {
|
||||
// play, pause, playpause, next, prev, volup, voldown, repeat, shuffle, skipforward, skipback, like, dislike, bookmark
|
||||
NRF.amsCommand(cmd);
|
||||
}
|
||||
};
|
||||
NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect
|
||||
|
||||
/*
|
||||
// For testing...
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
0.01: Launch app
|
||||
0.02: Swipe left/right to set an alarm.
|
||||
0.03: New design with different icons if gps, hrm or compass is on.
|
||||
0.04: Inluded LCARS Logo.
|
||||
|
|
@ -1,8 +1,19 @@
|
|||
# LCARS clock
|
||||
|
||||
A simple LCARS inspired clock that shows:
|
||||
* Current time
|
||||
* Current date
|
||||
* Battery level
|
||||
* Steps
|
||||
A simple LCARS inspired clock.
|
||||
Note: To display the steps, its necessary to install
|
||||
the [Pedometer widget](https://banglejs.com/apps/#pedometer%20widget).
|
||||
|
||||
## Features
|
||||
* Shows the time
|
||||
* Shows the date
|
||||
* Shows the current battery level in %
|
||||
* Shows the number of daily steps
|
||||
* Swipe left/right to activate an alarm
|
||||
|
||||
## Icons
|
||||
<div>Icons made by <a href="https://www.flaticon.com/authors/smashicons" title="Smashicons">Smashicons</a>, <a href="https://www.freepik.com" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div>
|
||||
|
||||
|
||||
## Creator
|
||||
Made by [David Peer](https://github.com/peerdavid)
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
|
@ -1,90 +1,226 @@
|
|||
const locale = require('locale');
|
||||
|
||||
|
||||
/*
|
||||
* Assets: Images, fonts etc.
|
||||
* Requirements and globals
|
||||
*/
|
||||
var img = {
|
||||
const locale = require('locale');
|
||||
var alarm = -1;
|
||||
var hrmValue = "-";
|
||||
|
||||
var backgroundImage = {
|
||||
width : 176, height : 151, bpp : 3,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("gF58+eAR14IN1fvv374CN7yD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AH4A/AH4A/AB1z588+YCN+RBuj158+eARyD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4AUhyD/gEDQaHz4BCuQaNAIN0PQaHIIN0BQaF5IN0AQaHPkBBug6DQ8iEvQaE8yBBuhyDPAQNAINsBQaACBkhCuQaACpVo0cQaACo4CFGjyD/AAMPQf4ACQf4ADgiD+AH4A/AH8J02atICIwEAgPnz15AR3gEgM27dt2wCTF4IABgYROgN9+/fAR14ILsaQBKDakwjKF5oABKZ6DwgxTPQeEmQf5cPQeMBLhyDxgJTRQd0JKaKDuhKD/gENQf6D/F4VNQf8AKaKDvKBYnBAGZQKzBB1QZOwIGqDJsBA2QZJA3QZGYIPCDH4CD/0xA4QY+wIPKDGwCD/tpB6Qf6DHthA5QY1oIPSD/QY9gQf/bIPaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AF8JQYgCdsEHnnz54CJgIdLwEAhqDEATtggPnz15ARHkgIdLIIKAgQcCAgQcAA/gAA=="))
|
||||
transparent : 2,
|
||||
buffer : require("heatshrink").decompress(atob("jlx44CdEQMHnnz54Ca/+OnHjAThlC8+evICaQf4CBQDqD/Qf6DruAlCAHJlC8BA8gCDDIPqD/Qf6D/QZEUQf6D/QYUEG2VwQf8D/yD/j//4CD+IIP4Qf6D/gH/Qf8HIIP/QfpBDGpCDzGQJBCj/x4CD4gY/CAAPj//4QZDCw+DFD/kBAQKD2n44Bn5BDJQWAQeh6BAQRBEgEf+CD0h7+CQYaACgf+Qel/4CDFYQYLCQeJ3DIJCPDQeNwII/wBASD0HAUPIJCDzj44DIJH/QeUffwZBE/yD6v5BE//AQesDIISD/QYvHj6D4PQRBCAoJBDh6DzgF+IIiJBIId/AQKDxGoZBCwCMB/6ABIIiDwF4RBB/hKEjlwCAaDwgP/8aCBGQcP/DLCQecB4/8QYJKFRIaDyAAKCB/AGDh6JEQeQABj//48cgEHHAKJEAGkD/0/QwIABAoJB4j/wQASGDIPQHFg/gIO59BIIyD4AH4A/AH4A/AH4A/AH4A/AEMcuPHAQoLLARvADQUYsOGASgZBkv/AA39EwUbtu27YCSwAaC6dNmgCUgEBNZImCj158+eASSDDjVp02aAScAh6CHQfDvKQesTQRCD3QBCD4QRKD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6DNxkgwUIAQYmCiZoVDIUAyaDaD4YA5QQXgIPr+FQfxB+Qf6DD/qD/Qf4A/AH4A/AH4A/AH4A/AB0cuPHIP3z588Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6DbwCD/Qf6DC8CD/48cQf88+fNmnTpoCLkAXCi/fvv3ASgvCARk4QYYCP4CbGAUpQNAQqDE/4Am/qD/Qf6D/Qf6D/Qf6D/ARoA="))
|
||||
}
|
||||
|
||||
Graphics.prototype.setFontMinaSmall = function(scale) {
|
||||
var iconPlanet = {
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 5,
|
||||
buffer : require("heatshrink").decompress(atob("23btoCD6PHjlx9oLGAQuGiVJkmSpIRK2lxEYQCDCJOGjEhEYNBwUI5drEw/xEYwCB8oRGDoMhwmSsAFBkGM237NZICGj15OgnaDoOGI4cgwUa5dv332EwdHEZACB8+evYRCtAdBEAQpDscs3379+9HAW8EZPHz158+WSQQjFwUYsMs2QjBEwPrSRZuCJQN5TAJuCEYkhwUS5cvJQRxCNxZKDOIXgJQkh0mYtMk2XLJQXv1u0EZSVDOIWsJQsSpMkyVJljgB9gmB7YjLOgtq4BKEsIjCAQNLlgCBt+9EZwCCj8sJQpxB00aJoYCB5cBEZ4CB+RKFJoeGjAjCoOGzBKaAQeGJQQFBwJKSsAjIcweSBwRKRjojKOgYFCxZKRtAaBjHrlm4FJUN3hKQi3ShAjB2XLAQQmI7dHJR97tsh9gjEAQLpHlu2+PnExvF23an3794mF2BKFm3btsevImMjwRB23v3wmB3xNF5BuDCIPb8+eEwOeExIRCtojCJo5uEEwRxBEwRuJHAdI+YmCTYlgJQIREtrjCEwLdHCIiYBhF7OgnJSQgmFjhxCOgiSDAQvSX4QmB90IkQRIX4gmCEZICDvwmCBY3QA"))
|
||||
}
|
||||
|
||||
var iconGps = {
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 2,
|
||||
buffer : require("heatshrink").decompress(atob("pMkyQCFpH0BAwCJv/6CJ8l589CJ0kyf//wIDpVEChM8+/fBAdZ8QRIp++///0gIBlMkxI4IuZKB+/SKAPHzpKJ/YkB//pKAP2BYeXhIFDx88+fPvqYBnibEkmUAofv34lC/RQBBYdcmPCXIYjBEwPfvnzJoILBQoUlHAUuJQYmCDodw48cuBKGTA0WEYIEBJQ6YEQwMMuImBJQyYEkmZFAVkyVSJQ6YCyUcmPDjgmBTAJKETAlJiS4ETANPJQpxCJQtxTALgBEwnfvohBI4NZkmWpNlcAgAD/wzBEYaYCy8cJQiYEyIjCTAWS3wlGTAVIEwkerJKFTAkmOIclToK8GAAIPBIgImCufHyxxG59pEIS8DvfypMr968HEwOHEwfx8+cEYkpCIeSoiYByVf/uSkmTEQP7ZIiYDnl5AQNwBYgCGyOn38k2+2pIRKyVeuPPj1x4ccCJVKSgP/5cJA4NSExMps+cSoMMKAIVCCg7SBpd7TANZkmUHBMevPnjlwcwXCCJFEzYDBA4WWKIIRHpEw4+eNwUxEwKYIkVJk1IyIKFHA+DR4VcJQYCBJRBoCkxHBAgNkyyYKkmXEYaYMAQMSEYKYNAQOHEwnSfBYjBAgVaCJdJJSMkTAK8KYQyVKAQ4jBNxiYEcBCYJXIkgA="))
|
||||
}
|
||||
|
||||
var iconCompass = {
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 2,
|
||||
buffer : require("heatshrink").decompress(atob("pMkyQCDl//AAPSBYwCFv4RCAAOkCJNLCAgACCJm2rNn34FB+g1Jvny5cs2XPn///QRI9uWEYP2rNly5NHNYN82YjB/4mC5YmBOgkl//9y1bsuW/4CB/Nlz//9I4D3/8I4M8EAICB55NCL4g/BIgRKBAQtnL4lf+QdCI4YCD2Y4DSQPZtojHsuerI4Dv/flnzEZB3CHAJuB8ojIAQY4CNwJHI2XHTAY4B/4gJrGBAoSqBpf2EZMQmRxEv/5Nw9YyVCAoO+rf/0v/Nw/PjFB4ZxCn/+y7dBJQyNBkAIDz/6/7dBJQsYsMEhgsE//+7IjFsTYBwAIE/4ABEYs8uPEiFyF4gRBXIImEBAPSpAjDtuX//9+YmERgMcuODBAU9+xKCr68Ev4lBNwm//IJCnhxDDQPx4xuFJQhBDDQXwTwpKBSos8//HjlwYQyVG34aB2zCG//1Nw6SFAQTgD/JuD+wjFrbgCr/yMQI+B/lxEY08UgPpl4jCNwP+I4wCBUgOk3/8DoXxI44CBn/0yREDzx0EAQlndANJv4gJAQf3/VJkq8CJoZuGXIPpkg4BOIZuI5/9CII4BEZAmDNwIRBHAJxDNxH+CII4CSQW+NALgBtomBt5uCHAbjB2ZoCAQPyJQP/NwIRCkm//4gBIgP/SQn/CImSYALjDviSDQAYUDL4ImEEYYRGL4X/76PCI4P/SQYCFl4MBAAgRJEwYRPOgZrHpMgA"))
|
||||
}
|
||||
|
||||
var iconAlarm = {
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("kmSpICEp//BAwCJn/+CJ8k//5CKAABCJs8uPH//x48EI5YjCAARNKEYUcv//jgFBExEnEYoAC+QmHIgIgC/gpCuPBCI2fIgU4AQXjA4P8CIuTEYZKBAolwHApXBEAWP//jxwpBAALaFDoYCIiQmDDIP4EAT+CEwnJEwYjLAQLaFEYomDKALmDNwoCIOIZuD8AkFgCYDHAQjMAQTdDNwOAEg0Dx0/cYeREZtxQYOTHgJuHOIvkXJy8DNwIACJQ8Ah4NDAAfxEZARHOIIkHg4jQAQb1CQ4KVJgEOnDIBSoIjNAQPBcAaVJcAKVBcDGOcD7OBMQM48BuH8f//JKCnhKNggRBkmfTQJxBEwhuD/gRCyVHJRlyCIVJXgYmB8ZQBAoIKBXIQmCOIt/NxAUCOIImCIgIpCBAJuDAQZEE/huIAQWTDgImBTYQGC8gRFcYpKFCI8kDwQAFCJBfBEAX/+IjBiQRIEw4jJAQc8v//NYwCIOgJrIJpA1OcwbaFAQWQA="))
|
||||
}
|
||||
|
||||
Graphics.prototype.setFontAntonioMedium = function(scale) {
|
||||
// Actual height 18 (17 - 0)
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAA/8w/8wAAQAAAAAA4AA8AAAAA8AAwAAAAAAEABEABEQB/w/8AxEABEwB/w/8AxEABEABEAAAAH4MP8MMEM8GPcGOMGMMGMMH4ABwAAA/gAwgAggAggQwhw/nAAOAA4ADgAOfw8YwwQwAQQAYwAfwAPgAGAAfg85w/wwzgww4wwdgwHggHgAPwAIQAAA8AAwAAAAAAfwH/+fAP4ABgAAgAA4ABfAPH/+A/wAAAAAAEAAHgAfAAfAAHgAMAAAAAAAABgABgABgAf4AP4ABgABgABAAAAAADAADwADAAAAAgAAwAAwAAwAAwAAwAAAAADAADAADAAAAAAEAA8AH4A+AHwA+AAwAAAAAf/g//wwAwwAwwAwwAwwAwwAwf/gH+AAAAAAAYAAwAAwAA//wAAAAAAAAAwAwwBwwDwwDwwGwwcww4wfwwPAwAAAAAAwAwwQwwQwwQwwQwwYww4wf/gHHAAAAAEAAeAA+ADmAPGAcGAwGAh/wD/wAEAAEAAAAf4w/4wwwwwwwwwwwwwwwww/wgfgAAAAAAP/Af/gwwwwwwwwwwwwwwwwwww/gAAAgAAwAAwAAwAwwHww/Az4A/AA8AAAAAAAAfPg//wxwwwwwwwwwwwwww//wffgAAAAAAfwQ/wwwQwwYwwQwwQwwQw//wP/AAAAAAAMDAMDAMDAAAAAAAMDAMDwMDAAAAAAADgADgAHwAGwAMYAMYAIIAAAAEQAGYAGYAGYAGYAGYAGYAGYAAAAMYAMYAGwAGwAHgADgADAAAAAwAAwAAwAAwcwwcwwQAwQA/wAfgAAAAAAAB/8D/+TAGbHjbPzbMTbMzbMzb/zZ/zYAGf/+H/8AAAAAAABwAPwA+AH+A+GA8GAfmAD+AAfgADwAAQAAA//w//wwwwwwwwwwwwwxww//wffgAAAAAAP/Af/gwBwwAwwAwwAwwAwwAwwAwAAA//w//wwAwwAwwAwwAwwAw4Bwf/gH+AAAAAAAf/g//wwQwgQQgQQgQQgQQgQQgAQAAAf/w//wwQAgQAgQAgQAgQAgQAgAAAAAP/Af/gwAwwAwwAwwYwwYwwfwwfwAAAAAA//w//wAYAAYAAYAAYAAYAAYA//w//wAAA//w//wAAAAAAAAwAAwAAw//w//AAAA//w//wAYAA4AD8AHHAeDg4AwgAQAAAAAA//g//wAAwAAwAAwAAwAAwAAwAAQAAAP/w//w+AAPwAB+AAHwADwA/gH4A/AA/4A//wAAwAAA//w//wcAAPAADgAA4AAeAAHAADw//wAAAAAAH/Af/g4AwwAwwAwwAwwAwwAwcDwP/gB4AAAA//w//wwQAwQAwQAwYAwwA/wAPgAAAAH/Af/gwAwwAwwAwwA8wA8wA2cDkP/gB4AAAA//w//wwYAwYAwYAwcAwfA/zwPgwAAAAAAfgA/wwwQwwYwwYwwYwwYwwfwAPgAAAAAAwAAwAAwAA//w//wwAAwAAwAAwAAAAA/+A//gABwAAwAAwAAwAAwAAwAPg//AAAAAAA4AA/AAH4AA/AAHwADwAfgD8AfgA8AAgAAAAA4AA/AAH4AA/AAHwAHwA/AP4A/4Aw/AAHwAHwA/AP4A+AAwAAAAAwAw8DwOHAD8AB4AD8AOHA8DwwAwAAAAAAwAA8AAPAADwAA/wB/wHgAeAA4AAgAAgAQwBwwHwwOww8wxww3gw+Aw4AwwAQAAAH//f//YAAYAAQAAwAA+AAPwAB+AAPwAB8AAMQAAYAAYAAf//AAAAAA"), 32, atob("BgUHDAoRCwMGBggJBQYFBwwHCwsLCwsKCwsFBQkICQoPDAsKDAoKCwsEBgsKDgwMCgwLCwoMDBELCwoGBwY="), 18+(scale<<8)+(1<<16));
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAf4Mf/sYAMAAAAAAfgAfAAAAAfgAeAAAAAAiAAj8H/4fyEAv8f/gfiAAgAAAAD54H98eOPHn8Hz8AhwAAAP8Af+AYGAYCAf+AP8MAB8AHwA+AD4AfAAcf4A/8AwMAwMA/8Af4AAAAAwGD8f/8f8MY/cfz4PD8AHMAAAfAAeAAAAAAAAP/+f//YADAAAQABYADf//P/+AAAAAANAAPAAfwAfgAPAANAAAAAAEAAEAA/AA/AAEAAEAAAAAAZAAfAAYAAAAIAAIAAIAAIAAAAAAAAAMAAMAAAAAAAAEAB8Af4H+AfwAcAAAAAP/4f/8YAMf/8f/8H/wAAAAAAEAAMAAf/8f/8f/8AAAAAAAAAHgcfh8cH8YPMf8MPwEAAAAAAOB4eB8YYMY4Mf/8Pn4AAAAAgAHwA/wPwwf/8f/8AAwAAgAAAf54f58ZwMZwMY/8Qf4AAAAAAP/4f/8YYMYYMff8HP4AAAQAAYAAYD8Y/8f/AfgAcAAAAAAAAPv4f/8YYMY8Mf/8Pn4AAAAAAP94f98YGMcMMf/8H/wAAAAAABgwBgwAAAAAABgABg/Bg8AAAAEAAOAAbAA7gAxgBwwASAAbAAbAAbAAbAASAAAAAxwA5gAbAAPAAOAAAAPAAfHcYPcf8Af4AHgAAAAAAAB/gH/wOA4Y/MZ/sbAsbBkb/MZ/sOBsH/AAAAAAMAP8f/4fwwf4wH/8AH8AAMAAAf/8f/8YYMYYMf/8P/4ADgAAAP/4f/8YAMYAMfj8Pj4AAAAAAf/8f/8YAMYAMf/8P/4B/AAAAf/8f/8YMMYMMYIMAAAAAAf/8f/8YYAYYAYYAAAAAAAP/4f/8YAMYIMfP8Pv8AAAAAAf/8f/8AMAAMAf/8f/8f/8AAAAAAf/8f/8AAAAAAAD4AB8AAMf/8f/4f/gAAAAAAf/8f/8A+AD/gfj4eA8QAEAAAf/8f/8AAMAAMAAMAAAf/8f/8f8AB/wAB8AP8P/Af/8f/8AAAAAAf/8f/8HwAA+AAPwf/8f/8AAAAAAP/4f/8YAMYAMf/8P/4AAAAAAf/8f/8YGAYGAf8AP8ABAAAAAf/w//4wAYwAc//+f/yAAAAAAf/8f/8YMAYMAf/8f/8DA8CAAPj4fz8Y4MeeMfP8HD4YAAYAAf/8f/8YAAQAAAAAf/4f/8AAMAAMf/8f/4AAAYAAf4AP/4AP8AP8f/4fwAQAAYAAf8AP/8AD8D/8f8Af8AD/8AD8f/8f8AAAAQAEeB8P/4B/AP/4fA8QAEYAAfAAP4AB/8H/8fwAcAAAAMYD8Y/8f/MfwMcAMAAAf/+f//YADYADAAAAAAfAAf8AB/wAH8AAMQACYADf//f//AAAAA"), 32, atob("BAUHCAcTCAQFBQgGBAYFBggICAgICAgICAgEBQYGBggNCAgICAcHCAkECAgGCwkICAgIBwYICAwHBwYGBgY="), 18+(scale<<8)+(1<<16));
|
||||
}
|
||||
|
||||
Graphics.prototype.setFontMinaLarge = function(scale) {
|
||||
// Actual height 35 (34 - 0)
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAB4AAAAAPgAAAAA+AAAAAD4AAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAH4AAAAD/gAAAB/8AAAA/+AAAAf/AAAAP/gAAAH/wAAAD/4AAAD/8AAAB/+AAAAP/AAAAA/AAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///4AA////wAH////gA+AAAfADwAAA8AOAAABwA4AAAHADgAAAcAOAAABwA4AAAHADgAAAcAOAAABwA4AAAHADgAAAcAOAAABwA8AAAPAD4AAB8AH////gAP///8AAf///gAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAcAAAAADgAAAAAOAAAAAB4AAAAAHAAAAAA8AAAAAD////8AP////wA/////AD////8AAAAAAAAAAAAAAAAAAAAAGAAAAAA4AAAHADgAAA8AOAAAHwA4AAA/ADgAAD8AOAAAfwA4AAD/ADgAAfcAOAAD5wA4AAfHADgAD4cAOAAfBwA4AD8HADwAfgcAPAD8BwAeA/AHAB+f4AcAD//ABwAH/wAHAAH8AAcAAAAAAAAAAAAAAAAAAAAAGAAABgAYAAAGADgAAAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADwB4A8APAPgDwAfD//+AB////4AD/8//AAD/B/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAA8AAAAAPwAAAAD/AAAAAf8AAAAH9wAAAB/HAAAAPwcAAAD+BwAAAfgHAAAH8AcAAB/ABwAAPwAHAAA+AAcAADgABwAAIAAHgAAAH///AAB///8AAH///wAAAAcAAAAABwAAAAAHAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAH/8AYAP//wBgA///AHAD//wAcAOAPABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgA8AOAOADwA4A8APADgD8H4AOAH//gA4AP/8AAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAAAD//+AAB///+AAP///8AB/BgH4APgOAHwA8A4APADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDwA8AOAP//wA4Af/+ABgA//wAAAA/8AAAAAAAAAAAAAAAAAAAAAA4AAAAADgAAAAAOAAAAAA4AAAAADgAAAAAOAAAAQA4AAAHADgAAD8AOAAA/wA4AAf+ADgAP/gAOAD/wAA4B/8AADg/+AAAOP/AAAA//wAAAD/4AAAAP8AAAAA/AAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/h/4AA//P/wAH////gA+B/AfADwD4A8AOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAPAPgDwA8A+APAB////4AH////gAP/j/8AAD4B8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAA//gAYAH//ABwA//+AHADwB4AcAOADgBwA4AOAHADgA4AcAOADgBwA4AOAHADgA4AcAOADgBwA4AOAHADgA4AcAPADgDwA+AOAfAB+A4f4AD////AAH///4AAD//8AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAeAAB8AD4AAHwAPgAAfAA+AAB4AB4AAAAAAAAAAAAAAAAAAAAAA="), 46, atob("CxAaDhgYGBgZFhkZCw=="), 40+(scale<<8)+(1<<16));
|
||||
Graphics.prototype.setFontAntonioLarge = function(scale) {
|
||||
// Actual height 34 (34 - 1)
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAADwAAAAAeAAAAADwAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAD+AAAAH/wAAAP/+AAAf/+AAA//8AAB//4AAD//wAAD//gAAAf/AAAAD+AAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAB////gA/////AP////8D/////wfAAAA+DwAAADweAAAAeDwAAADwf////+D/////wP////8Af///+AAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAOAAAAADwAAAAAeAAAAAHgAAAAB/////wf////+D/////wf////+D/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AAPwH/4AP+B//AH/wf/4D/+D4AB/9weAAf4ODwAP8BweAP/AOD///gBwP//wAOA//4ABwB/4AAOAAAAAAAAAAAAAAAAAAAAB8AA/gA/gAH/AP8AA/8D/gAH/wfAHAA+DwA4ADweAHgAeDwB8ADwf7/+H+D/////gP/9//8A//H/+AA/AH/AAAAAAAAAAAAAAAAAABwAAAAD+AAAAD/wAAAH/+AAAH/5wAAH/wOAAP/gBwAP/gAOAD/////wf////+D/////wf////+AAAABwAAAAAOAAAAABwAAAAAAAAAAAAAAAAAAeAD//4D/Af//Af8D//4D/wf//Af+DwPAADweB4AAeDwPAADweB///+DwP///weA///8DwD//+AAAA/8AAAAAAAAAAAAAAAAAAAAAA////AA/////AP////8D/////wfgPAB+DwB4ADweAOAAeDwBwADwf+PAA+D/x///wP+H//8A/wf//AAAA//gAAAAAAAAAAAAADgAAAAAeAAAAADwAAAAAeAAAD+DwAAP/weAA//+DwA///weB///8Dx//8AAf//wAAD//gAAAf/AAAAD/AAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAD/wf/wB//v//AP////8D/////weAPwAeDwA8ADwcAHAAeDwB8ADwf////+D/////wP/9//8A//H//AA/AD/AAAAAAAAAAAAAAAAAAAAAD//gfAA///D/AP//8f8D///j/weAA8A+DwADgDweAAcAeDwAHgDwf////+B/////gP////8Af///+AAP//4AAAAAAAAAAAAAAAAAAAAAAD4AfAAAfAD4AAD4AfAAAfAD4AAD4AfAAAAAAAAAAAAAA=="), 46, atob("Cg4QEBAQEBAQEBAQCQ=="), 39+(scale<<8)+(1<<16));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Queue drawing every minute
|
||||
* Draw watch face
|
||||
*/
|
||||
var drawTimeout;
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
draw(true);
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Draw watch face
|
||||
*/
|
||||
function draw(){
|
||||
function draw(queue){
|
||||
g.reset();
|
||||
g.clearRect(0, 24, g.getWidth(), g.getHeight());
|
||||
|
||||
// Draw background image
|
||||
g.drawImage(img, 0, 24);
|
||||
g.drawImage(backgroundImage, 0, 24);
|
||||
|
||||
// Draw raster
|
||||
// g.drawLine(112, 100, 112, 165);
|
||||
for(var x=1; x<7; x++){
|
||||
g.drawLine(110+x*10, 100, 110+x*10, 160);
|
||||
}
|
||||
|
||||
for(var y=0; y<6; y++){
|
||||
g.drawLine(113, 105+y*10, 180, 105+y*10);
|
||||
}
|
||||
|
||||
// Draw symbol
|
||||
var iconImg =
|
||||
alarm >= 0 ? iconAlarm :
|
||||
Bangle.isGPSOn() ? iconGps :
|
||||
Bangle.isCompassOn() ? iconCompass :
|
||||
iconPlanet;
|
||||
g.drawImage(iconImg, 120, 107);
|
||||
|
||||
// Alarm within symbol
|
||||
g.setFontAntonioMedium();
|
||||
if(alarm > 0){
|
||||
g.setFontAlign(0,0,0);
|
||||
g.drawString(alarm, 120+25, 107+25);
|
||||
g.setFontAlign(-1,-1,0);
|
||||
}
|
||||
|
||||
// Write time
|
||||
var currentDate = new Date();
|
||||
var timeStr = locale.time(currentDate,1);
|
||||
g.setFontAlign(0,0,0);
|
||||
g.setFontMinaLarge();
|
||||
g.drawString(timeStr, 115, 53);
|
||||
g.setFontAntonioLarge();
|
||||
g.drawString(timeStr, 55, 57);
|
||||
|
||||
// Write date
|
||||
g.setFontAlign(-1,-1,0);
|
||||
g.setFontMinaSmall();
|
||||
g.setFontAlign(-1,-1, 0);
|
||||
g.setFontAntonioMedium();
|
||||
|
||||
var dayName = locale.dow(currentDate, true).toUpperCase();
|
||||
var day = currentDate.getDate();
|
||||
g.drawString("DATE:", 40, 107);
|
||||
g.drawString(dayName + " " + day, 100, 105);
|
||||
g.drawString(day, 100, 37);
|
||||
g.drawString(dayName, 100, 57);
|
||||
|
||||
// Temperature
|
||||
g.setFontAlign(-1,-1,0);
|
||||
g.drawString("HRM:", 20, 104);
|
||||
g.drawString(hrmValue, 60, 104);
|
||||
|
||||
// Draw steps
|
||||
var steps = getSteps();
|
||||
g.drawString("STEP:", 20, 124);
|
||||
g.drawString(steps, 60, 124);
|
||||
|
||||
// Draw battery
|
||||
var bat = E.getBattery();
|
||||
g.drawString("BAT:", 40, 127);
|
||||
g.drawString(bat+"%", 100, 127);
|
||||
|
||||
// Draw steps
|
||||
var steps = Bangle.getStepCount();
|
||||
g.drawString("STEP:", 40, 147);
|
||||
g.drawString(steps, 100, 147);
|
||||
var charging = Bangle.isCharging() ? "*" : "";
|
||||
g.drawString("BAT:", 20, 144);
|
||||
g.drawString(charging + bat+ "%", 60, 144);
|
||||
|
||||
// Queue draw in one minute
|
||||
queueDraw();
|
||||
if(queue){
|
||||
queueDraw();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the screen once, at startup
|
||||
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
|
||||
/*
|
||||
* Step counter via widget
|
||||
*/
|
||||
function getSteps() {
|
||||
if (stepsWidget() !== undefined)
|
||||
return stepsWidget().getSteps();
|
||||
return "???";
|
||||
}
|
||||
|
||||
// draw immediately at first, queue update
|
||||
draw();
|
||||
function stepsWidget() {
|
||||
if (WIDGETS.activepedom !== undefined) {
|
||||
return WIDGETS.activepedom;
|
||||
} else if (WIDGETS.wpedom !== undefined) {
|
||||
return WIDGETS.wpedom;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/*
|
||||
* HRM
|
||||
*/
|
||||
Bangle.on('HRM',function(hrm) {
|
||||
hrmValue = hrm.bpm;
|
||||
});
|
||||
|
||||
/*
|
||||
* Handle alarm
|
||||
*/
|
||||
var alarmTimeout;
|
||||
function queueAlarm() {
|
||||
if (alarmTimeout) clearTimeout(alarmTimeout);
|
||||
alarmTimeout = setTimeout(function() {
|
||||
alarmTimeout = undefined;
|
||||
handleAlarm();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function handleAlarm(){
|
||||
|
||||
// Check each minute
|
||||
if(alarm > 0){
|
||||
alarm--;
|
||||
queueAlarm();
|
||||
}
|
||||
|
||||
// After n minutes, inform the user
|
||||
if(alarm == 0){
|
||||
alarm = -1;
|
||||
|
||||
var t = 300;
|
||||
Bangle.buzz(t, 1)
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
||||
.then(() => Bangle.buzz(t, 1))
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
||||
.then(() => Bangle.buzz(t, 1))
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
||||
.then(() => Bangle.buzz(t, 1));
|
||||
}
|
||||
|
||||
// Update UI
|
||||
draw(false);
|
||||
}
|
||||
|
||||
|
||||
// Stop updates when LCD is off, restart when on
|
||||
/*
|
||||
* Swipe to set an alarm
|
||||
*/
|
||||
Bangle.on('swipe',function(dir) {
|
||||
// Increase alarm
|
||||
if(dir == -1){
|
||||
alarm = alarm < 0 ? 0 : alarm;
|
||||
alarm += 5;
|
||||
queueAlarm();
|
||||
}
|
||||
|
||||
// Decrease alarm
|
||||
if(dir == +1){
|
||||
alarm -= 5;
|
||||
alarm = alarm <= 0 ? -1 : alarm;
|
||||
}
|
||||
|
||||
// Update UI
|
||||
draw(false);
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Stop updates when LCD is off, restart when on
|
||||
*/
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
draw(true); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
|
|
@ -94,6 +230,12 @@ Bangle.on('lcdPower',on=>{
|
|||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
||||
// Load widgets
|
||||
// Load widgets - needed by draw
|
||||
Bangle.loadWidgets();
|
||||
|
||||
// Clear the screen once, at startup and draw clock
|
||||
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
|
||||
draw(true);
|
||||
|
||||
// After drawing the watch face, we can draw the widgets
|
||||
Bangle.drawWidgets();
|
||||
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -2,5 +2,6 @@
|
|||
0.02: Course marker
|
||||
0.03: Tilt compensation and calibration
|
||||
0.04: Fix Font size
|
||||
0.05: Inital portable version
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,19 +6,20 @@ This is a tilt and roll compensated compass with a linear display. The compass w
|
|||
|
||||
## Calibration
|
||||
|
||||
Correct operation of this app depends critically on calibration. When first run on a Bangle, the app will request calibration. This lasts for 30 seconds during which you should move the watch slowly through figures of 8. It is important that during calibration the watch is fully rotated around each of it axes. If the app does give the correct direction heading or is not stable with respect to tilt and roll - redo the calibration by pressing *BTN3*. Calibration data is recorded in a storage file named `magnav.json`.
|
||||
Correct operation of this app depends critically on calibration. When first run on a Bangle, the app will request calibration. This lasts for 20 seconds during which you should move the watch slowly through figures of 8. It is important that during calibration the watch is fully rotated around each of it axes. If the app does give the correct direction heading or is not stable with respect to tilt and roll - redo the calibration by pressing *BTN2*. Calibration data is recorded in a storage file named `magnav.json`.
|
||||
|
||||
Note: Charging your Bangle due to the magnetic connector clamp seems to require recalibration afterwards for accurate readings.
|
||||
|
||||
## Controls
|
||||
|
||||
*BTN1* - switches to your selected clock app.
|
||||
*BTN1* - marks the current heading with a blue circle - see screen shot. This can be used to take a bearing and then follow it..
|
||||
(Swipe UP on Bangle 2)
|
||||
|
||||
*BTN2* - switches to the app launcher.
|
||||
*BTN2* - invokes calibration ( can be cancelled if pressed accidentally).
|
||||
(*BTN1* on Bangle 2)
|
||||
|
||||
*BTN3* - invokes calibration ( can be cancelled if pressed accidentally)
|
||||
|
||||
*Touch Left* - marks the current heading with a blue circle - see screen shot. This can be used to take a bearing and then follow it.
|
||||
|
||||
*Touch Right* - cancels the marker (blue circle not displayed).
|
||||
*BTN3* - cancels the marker (blue circle not displayed)
|
||||
(swipe DOWN on Bangle 2)
|
||||
|
||||
|
||||
## Support
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
var Yoff=80,pal2color=new Uint16Array([0,65535,2047,50712],0,2),buf=Graphics.createArrayBuffer(240,60,2,{msb:!0});Bangle.setLCDTimeout(30);function flip(b,c){g.drawImage({width:240,height:60,bpp:2,buffer:b.buffer,palette:pal2color},0,c);b.clear()}var labels="N NE E SE S SW W NW".split(" "),brg=null;
|
||||
function drawCompass(b){buf.setColor(1);buf.setFont("Vector",24);var c=b-90;0>c&&(c+=360);buf.fillRect(28,45,212,49);var a=30,d=15-c%15;15>d?a+=d:d=0;for(var e=d;e<=180-d;e+=15){var f=c+e;0==f%90?(buf.drawString(labels[Math.floor(f/45)%8],a-8,0),buf.fillRect(a-2,25,a+2,45)):0==f%45?(buf.drawString(labels[Math.floor(f/45)%8],a-12,0),buf.fillRect(a-2,30,a+2,45)):0==f%15&&buf.fillRect(a,35,a+1,45);a+=15}brg&&(b=brg-b,180<b&&(b-=360),-180>b&&(b+=360),b+=120,30>b&&(b=14),210<b&&(b=226),buf.setColor(2),
|
||||
buf.fillCircle(b,40,8));flip(buf,Yoff)}var heading=0;function newHeading(b,c){var a=Math.abs(b-c),d=b>c?1:-1;180<=a&&(a=360-a,d=-d);if(2>a)return c;a=c+d*(1+Math.round(a/5));0>a&&(a+=360);360<a&&(a-=360);return a}var candraw=!1,CALIBDATA=require("Storage").readJSON("magnav.json",1)||null;
|
||||
function tiltfixread(b,c){Date.now();var a=Bangle.getCompass(),d=Bangle.getAccel();a.dx=(a.x-b.x)*c.x;a.dy=(a.y-b.y)*c.y;a.dz=(a.z-b.z)*c.z;var e=Math.atan(-d.x/-d.z),f=Math.cos(e);e=Math.sin(e);d=Math.atan(-d.y/(-d.x*e-d.z*f));var k=Math.sin(d);a=180*Math.atan2(a.dz*e-a.dx*f,a.dy*Math.cos(d)+a.dx*e*k+a.dz*f*k)/Math.PI;0>a&&(a+=360);return a}
|
||||
function reading(){var b=tiltfixread(CALIBDATA.offset,CALIBDATA.scale);heading=newHeading(b,heading);drawCompass(heading);buf.setColor(1);buf.setFont("6x8",2);buf.setFontAlign(-1,-1);buf.drawString("o",170,0);buf.setFont("Vector",54);b=Math.round(heading);var c=b.toString();buf.drawString(10>b?"00"+c:100>b?"0"+c:c,70,10);flip(buf,Yoff+80)}
|
||||
function calibrate(){var b=-32E3,c=-32E3,a=-32E3,d=32E3,e=32E3,f=32E3,k=setInterval(function(){var h=Bangle.getCompass();b=h.x>b?h.x:b;c=h.y>c?h.y:c;a=h.z>a?h.z:a;d=h.x<d?h.x:d;e=h.y<e?h.y:e;f=h.z<f?h.z:f},100);return new Promise(function(h){setTimeout(function(){k&&clearInterval(k);var m=(b-d)/2,n=(c-e)/2,p=(a-f)/2,l=(m+n+p)/3;h({offset:{x:(b+d)/2,y:(c+e)/2,z:(a+f)/2},scale:{x:l/m,y:l/n,z:l/p}})},1E4)})}
|
||||
function docalibrate(b,c){function a(d){d?(buf.setColor(1),buf.setFont("Vector",24),buf.setFontAlign(0,-1),buf.drawString("Fig 8s to",120,0),buf.drawString("Calibrate",120,26),flip(buf,Yoff),calibrate().then(function(e){require("Storage").write("magnav.json",e);CALIBDATA=e;startdraw();setButtons()})):(startdraw(),setTimeout(setButtons,1E3))}void 0===c&&(c=!1);stopdraw();clearWatch();c?E.showAlert("takes 10 seconds","Calibrate").then(a.bind(null,!0)):E.showPrompt("takes 10 seconds",{title:"Calibrate",
|
||||
buttons:{Start:!0,Cancel:!1}}).then(a)}Bangle.on("touch",function(b){candraw&&(1==b&&(brg=heading),2==b&&(brg=null))});var intervalRef;function startdraw(){g.clear();g.setColor(1,.5,.5);g.fillPoly([120,Yoff+60,110,Yoff+80,130,Yoff+80]);g.setColor(1,1,1);Bangle.drawWidgets();candraw=!0;intervalRef=setInterval(reading,200)}function stopdraw(){candraw=!1;intervalRef&&clearInterval(intervalRef)}
|
||||
function setButtons(){setWatch(function(){load()},BTN1,{repeat:!1,edge:"falling"});setWatch(Bangle.showLauncher,BTN2,{repeat:!1,edge:"falling"});setWatch(docalibrate,BTN3,{repeat:!1,edge:"falling"})}var SCREENACCESS={withApp:!0,request:function(){this.withApp=!1;stopdraw();clearWatch()},release:function(){this.withApp=!0;startdraw();setButtons()}};Bangle.on("lcdPower",function(b){SCREENACCESS.withApp&&(b?startdraw():stopdraw())});Bangle.on("kill",function(){Bangle.setCompassPower(0)});Bangle.loadWidgets();
|
||||
Bangle.setCompassPower(1);CALIBDATA?(startdraw(),setButtons()):docalibrate({},!0);
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
const Yoff = 80;
|
||||
var pal2color = new Uint16Array([0x0000,0xffff,0x07ff,0xC618],0,2);
|
||||
var pal2color = new Uint16Array([g.theme.bg,g.theme.fg,g.theme.fg2,0xC618],0,2);
|
||||
var buf = Graphics.createArrayBuffer(240,60,2,{msb:true});
|
||||
Bangle.setLCDTimeout(30);
|
||||
|
||||
|
|
@ -13,6 +12,7 @@ const labels = ["N","NE","E","SE","S","SW","W","NW"];
|
|||
var brg=null;
|
||||
|
||||
function drawCompass(course) {
|
||||
"ram"
|
||||
buf.setColor(1);
|
||||
buf.setFont("Vector",24);
|
||||
var start = course-90;
|
||||
|
|
@ -63,7 +63,7 @@ var candraw = false;
|
|||
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||
|
||||
function tiltfixread(O,S){
|
||||
var start = Date.now();
|
||||
"ram"
|
||||
var m = Bangle.getCompass();
|
||||
var g = Bangle.getAccel();
|
||||
m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z;
|
||||
|
|
@ -117,13 +117,20 @@ function calibrate(){
|
|||
var avg = (delta.x+delta.y+delta.z)/3;
|
||||
var scale = {x:avg/delta.x, y:avg/delta.y, z:avg/delta.z};
|
||||
resolve({offset:offset,scale:scale});
|
||||
},30000);
|
||||
},20000);
|
||||
});
|
||||
}
|
||||
|
||||
function docalibrate(e,first){
|
||||
var calibrating=false;
|
||||
function docalibrate(first){
|
||||
calibrating=true;
|
||||
const title = "Calibrate";
|
||||
const msg = "takes 30 seconds";
|
||||
const msg = "takes 20 seconds";
|
||||
function restart() {
|
||||
calibrating=false;
|
||||
setButtons();
|
||||
startdraw();
|
||||
}
|
||||
function action(b){
|
||||
if (b) {
|
||||
buf.setColor(1);
|
||||
|
|
@ -133,31 +140,22 @@ function docalibrate(e,first){
|
|||
buf.drawString("Calibrate",120,26);
|
||||
flip(buf,Yoff);
|
||||
calibrate().then((r)=>{
|
||||
CALIBDATA=r;
|
||||
require("Storage").write("magnav.json",r);
|
||||
CALIBDATA = r;
|
||||
startdraw();
|
||||
setButtons();
|
||||
restart()
|
||||
});
|
||||
} else {
|
||||
startdraw();
|
||||
setTimeout(setButtons,1000);
|
||||
}
|
||||
restart()
|
||||
}
|
||||
}
|
||||
if (first===undefined) first=false;
|
||||
stopdraw();
|
||||
clearWatch();
|
||||
if (first)
|
||||
E.showAlert(msg,title).then(action.bind(null,true));
|
||||
else
|
||||
E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action);
|
||||
}
|
||||
|
||||
Bangle.on('touch', function(b) {
|
||||
if(!candraw) return;
|
||||
if(b==1) brg=heading;
|
||||
if(b==2) brg=null;
|
||||
});
|
||||
|
||||
var intervalRef;
|
||||
|
||||
function startdraw(){
|
||||
|
|
@ -176,29 +174,17 @@ function stopdraw() {
|
|||
}
|
||||
|
||||
function setButtons(){
|
||||
setWatch(()=>{load();}, BTN1, {repeat:false,edge:"falling"});
|
||||
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
|
||||
setWatch(docalibrate, BTN3, {repeat:false,edge:"falling"});
|
||||
function actions(v){
|
||||
if (!v) docalibrate(false);
|
||||
else if (v==1) brg=null;
|
||||
else brg=heading;
|
||||
}
|
||||
Bangle.setUI("updown",actions);
|
||||
}
|
||||
|
||||
var SCREENACCESS = {
|
||||
withApp:true,
|
||||
request:function(){
|
||||
this.withApp=false;
|
||||
stopdraw();
|
||||
clearWatch();
|
||||
},
|
||||
release:function(){
|
||||
this.withApp=true;
|
||||
startdraw();
|
||||
setButtons();
|
||||
}
|
||||
};
|
||||
|
||||
Bangle.on('lcdPower',function(on) {
|
||||
if (!SCREENACCESS.withApp) return;
|
||||
if (on) {
|
||||
startdraw();
|
||||
if (!calibrating) startdraw();
|
||||
} else {
|
||||
stopdraw();
|
||||
}
|
||||
|
|
@ -209,7 +195,7 @@ Bangle.on('kill',()=>{Bangle.setCompassPower(0);});
|
|||
Bangle.loadWidgets();
|
||||
Bangle.setCompassPower(1);
|
||||
if (!CALIBDATA)
|
||||
docalibrate({},true);
|
||||
docalibrate(true);
|
||||
else {
|
||||
startdraw();
|
||||
setButtons();
|
||||
|
|
@ -217,4 +203,3 @@ else {
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
|
||||
const Ypos = 40;
|
||||
|
||||
const labels = ["N","NE","E","SE","S","SW","W","NW"];
|
||||
var brg=null;
|
||||
|
||||
function drawCompass(course) {
|
||||
"ram"
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont("Vector",18);
|
||||
var start = course-90;
|
||||
if (start<0) start+=360;
|
||||
g.fillRect(16,Ypos+45,160,Ypos+49);
|
||||
var xpos = 16;
|
||||
var frag = 15 - start%15;
|
||||
if (frag<15) xpos+=Math.floor((frag*4)/5); else frag = 0;
|
||||
for (var i=frag;i<=180-frag;i+=15){
|
||||
var res = start + i;
|
||||
if (res%90==0) {
|
||||
g.drawString(labels[Math.floor(res/45)%8],xpos-6,Ypos+6);
|
||||
g.fillRect(xpos-2,Ypos+25,xpos+2,Ypos+45);
|
||||
} else if (res%45==0) {
|
||||
g.drawString(labels[Math.floor(res/45)%8],xpos-9,Ypos+6);
|
||||
g.fillRect(xpos-2,Ypos+30,xpos+2,Ypos+45);
|
||||
} else if (res%15==0) {
|
||||
g.fillRect(xpos,Ypos+35,xpos+1,Ypos+45);
|
||||
}
|
||||
xpos+=12;
|
||||
}
|
||||
if (brg) {
|
||||
var bpos = brg - course;
|
||||
if (bpos>180) bpos -=360;
|
||||
if (bpos<-180) bpos +=360;
|
||||
bpos= Math.floor((bpos*4)/5)+88;
|
||||
if (bpos<16) bpos = 8;
|
||||
if (bpos>160) bpos = 170;
|
||||
g.setColor(g.theme.fg2);
|
||||
g.fillCircle(bpos,Ypos+45,6);
|
||||
}
|
||||
}
|
||||
|
||||
var heading = 0;
|
||||
function newHeading(m,h){
|
||||
var s = Math.abs(m - h);
|
||||
var delta = (m>h)?1:-1;
|
||||
if (s>=180){s=360-s; delta = -delta;}
|
||||
if (s<2) return h;
|
||||
var hd = h + delta*(1 + Math.round(s/5));
|
||||
if (hd<0) hd+=360;
|
||||
if (hd>360)hd-= 360;
|
||||
return hd;
|
||||
}
|
||||
|
||||
var candraw = false;
|
||||
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||
|
||||
function tiltfixread(O,S){
|
||||
"ram"
|
||||
var m = Bangle.getCompass();
|
||||
var g = Bangle.getAccel();
|
||||
m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z;
|
||||
var d = Math.atan2(-m.dx,m.dy)*180/Math.PI;
|
||||
if (d<0) d+=360;
|
||||
var phi = Math.atan(-g.x/-g.z);
|
||||
var cosphi = Math.cos(phi), sinphi = Math.sin(phi);
|
||||
var theta = Math.atan(-g.y/(-g.x*sinphi-g.z*cosphi));
|
||||
var costheta = Math.cos(theta), sintheta = Math.sin(theta);
|
||||
var xh = m.dy*costheta + m.dx*sinphi*sintheta + m.dz*cosphi*sintheta;
|
||||
var yh = m.dz*sinphi - m.dx*cosphi;
|
||||
var psi = Math.atan2(yh,xh)*180/Math.PI;
|
||||
if (psi<0) psi+=360;
|
||||
return psi;
|
||||
}
|
||||
|
||||
// Note actual mag is 360-m, error in firmware
|
||||
function reading() {
|
||||
"ram"
|
||||
g.clearRect(0,24,175,175);
|
||||
var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
||||
heading = newHeading(d,heading);
|
||||
drawCompass(heading);
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont("6x8",2);
|
||||
g.setFontAlign(-1,-1);
|
||||
g.drawString("o",120,Ypos+80);
|
||||
g.setFont("Vector",40);
|
||||
var course = Math.round(heading);
|
||||
var cs = course.toString();
|
||||
cs = course<10?"00"+cs : course<100 ?"0"+cs : cs;
|
||||
g.drawString(cs,50,Ypos+90);
|
||||
g.setColor(g.theme.fg2);
|
||||
g.fillPoly([88,Ypos+60,78,Ypos+80,98,Ypos+80]);
|
||||
g.setColor(g.theme.fg);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function calibrate(){
|
||||
var max={x:-32000, y:-32000, z:-32000},
|
||||
min={x:32000, y:32000, z:32000};
|
||||
var ref = setInterval(()=>{
|
||||
var m = Bangle.getCompass();
|
||||
max.x = m.x>max.x?m.x:max.x;
|
||||
max.y = m.y>max.y?m.y:max.y;
|
||||
max.z = m.z>max.z?m.z:max.z;
|
||||
min.x = m.x<min.x?m.x:min.x;
|
||||
min.y = m.y<min.y?m.y:min.y;
|
||||
min.z = m.z<min.z?m.z:min.z;
|
||||
}, 100);
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(()=>{
|
||||
if(ref) clearInterval(ref);
|
||||
var offset = {x:(max.x+min.x)/2,y:(max.y+min.y)/2,z:(max.z+min.z)/2};
|
||||
var delta = {x:(max.x-min.x)/2,y:(max.y-min.y)/2,z:(max.z-min.z)/2};
|
||||
var avg = (delta.x+delta.y+delta.z)/3;
|
||||
var scale = {x:avg/delta.x, y:avg/delta.y, z:avg/delta.z};
|
||||
resolve({offset:offset,scale:scale});
|
||||
},20000);
|
||||
});
|
||||
}
|
||||
|
||||
var calibrating=false;
|
||||
function docalibrate(first){
|
||||
calibrating=true;
|
||||
const title = "Calibrate";
|
||||
const msg = "takes 20 seconds";
|
||||
function restart() {
|
||||
calibrating=false;
|
||||
setButtons();
|
||||
startdraw();
|
||||
}
|
||||
function action(b){
|
||||
if (b) {
|
||||
g.clearRect(0,24,175,175);
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont("Vector",18);
|
||||
g.setFontAlign(0,-1);
|
||||
g.drawString("Fig 8s to",88,Ypos);
|
||||
g.drawString("Calibrate",88,Ypos+18);
|
||||
g.flip();
|
||||
calibrate().then((r)=>{
|
||||
CALIBDATA=r;
|
||||
require("Storage").write("magnav.json",r);
|
||||
restart();
|
||||
});
|
||||
} else {
|
||||
restart();
|
||||
}
|
||||
}
|
||||
if (first===undefined) first=false;
|
||||
stopdraw();
|
||||
if (first)
|
||||
E.showAlert(msg,title).then(action.bind(null,true));
|
||||
else
|
||||
E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action);
|
||||
}
|
||||
|
||||
var intervalRef;
|
||||
|
||||
function startdraw(){
|
||||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
candraw = true;
|
||||
intervalRef = setInterval(reading,200);
|
||||
}
|
||||
|
||||
function stopdraw() {
|
||||
candraw=false;
|
||||
if(intervalRef) {clearInterval(intervalRef);}
|
||||
}
|
||||
|
||||
function setButtons(){
|
||||
function actions(v){
|
||||
if (!v) docalibrate(false);
|
||||
else if (v==1) brg=null;
|
||||
else brg=heading;
|
||||
}
|
||||
Bangle.setUI("updown",actions);
|
||||
}
|
||||
|
||||
Bangle.on('kill',()=>{Bangle.setCompassPower(0);});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.setCompassPower(1);
|
||||
if (!CALIBDATA)
|
||||
docalibrate(true);
|
||||
else {
|
||||
startdraw();
|
||||
setButtons();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Add 'messages' library
|
||||
0.03: Fixes for Bangle.js 1
|
||||
0.04: Add require("messages").clearAll()
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
{"t":"add","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"}
|
||||
// maps
|
||||
{"t":"add","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"GhqBAAAMAAAHgAAD8AAB/gAA/8AAf/gAP/8AH//gD/98B//Pg/4B8f8Afv+PP//n3/f5//j+f/wfn/4D5/8Aef+AD//AAf/gAD/wAAf4AAD8AAAeAAADAAA="}
|
||||
|
||||
// call
|
||||
{"t:"add","id:"call","name":"Bob","number":"12421312"}
|
||||
*/
|
||||
|
||||
var Layout = require("Layout");
|
||||
|
|
@ -61,6 +62,7 @@ function getMessageImage(msg) {
|
|||
if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA==");
|
||||
if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA=");
|
||||
if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA==");
|
||||
if (s=="telegram") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA==");
|
||||
if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA");
|
||||
if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A=");
|
||||
if (msg.id=="back") return getBackImage();
|
||||
|
|
|
|||
|
|
@ -28,10 +28,25 @@ exports.pushMessage = function(event) {
|
|||
// otherwise load after a delay, to ensure we have all the messages
|
||||
if (exports.messageTimeout) clearTimeout(exports.messageTimeout);
|
||||
exports.messageTimeout = setTimeout(function() {
|
||||
exports.messageTimeout = undefined;
|
||||
exports.messageTimeout = undefined;
|
||||
// if we're in a clock or it's important, go straight to messages app
|
||||
if (Bangle.CLOCK || event.important) return load("messages.app.js");
|
||||
if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know
|
||||
WIDGETS.messages.newMessage();
|
||||
WIDGETS.messages.show();
|
||||
}, 500);
|
||||
}
|
||||
exports.clearAll = function(event) {
|
||||
var messages, inApp = "undefined"!=typeof MESSAGES;
|
||||
if (inApp) {
|
||||
MESSAGES = [];
|
||||
messages = MESSAGES; // we're in an app that has already loaded messages
|
||||
} else // no app - empty messages
|
||||
messages = [];
|
||||
// Save all messages
|
||||
require("Storage").writeJSON("messages.json",messages);
|
||||
// update app if in app
|
||||
if (inApp) return onMessagesModified();
|
||||
// if we have a widget, update it
|
||||
if (global.WIDGETS && WIDGETS.messages)
|
||||
WIDGETS.messages.hide();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,15 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() {
|
|||
Bangle.buzz(); // buzz every 4 seconds
|
||||
}
|
||||
setTimeout(()=>WIDGETS["messages"].draw(), 1000);
|
||||
},newMessage:function() {
|
||||
},show:function() {
|
||||
WIDGETS["messages"].t=Date.now(); // first time
|
||||
WIDGETS["messages"].l=Date.now()-10000; // last buzz
|
||||
if (WIDGETS["messages"].c!==undefined) return; // already called
|
||||
WIDGETS["messages"].width=64;
|
||||
Bangle.drawWidgets();
|
||||
Bangle.setLCDPower(1);// turns screen on
|
||||
},hide:function() {
|
||||
delete WIDGETS["messages"].t;
|
||||
delete WIDGETS["messages"].l;
|
||||
WIDGETS["messages"].width=0;
|
||||
Bangle.drawWidgets();
|
||||
}};
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
0.06: add minute tick for efficiency and nifty A clock
|
||||
0.07: compatible with Bang;e.js 2
|
||||
0.08: fix minute tick bug
|
||||
0.09: use setUI clockupdown for controls + fix small display bug in nifty face
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ This is a clock app that supports multiple clock faces. The user can switch betw
|
|||
|
||||
|
||||
## Controls
|
||||
Swipe left and right on both the Bangle and Bangle 2 switch between faces. BTN1 & BTH3 also switch faces on the Bangle.
|
||||
Uses `setUI("clockupdown")`
|
||||
BTN1 & BTH3 switch faces on the Bangle.
|
||||
Touch upper right and lower right quadrant switch faces on the Bangle 2.
|
||||
|
||||
## Adding a new face
|
||||
Clock faces are described in javascript storage files named `name.face.js`. For example, the Analog Clock Face is described in `ana.face.js`. These files have the following structure:
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ function setButtons(){
|
|||
startdraw();
|
||||
}
|
||||
}
|
||||
Bangle.setUI("leftright", newFace);
|
||||
Bangle.setUI("clockupdown", newFace);
|
||||
}
|
||||
|
||||
E.on('kill',()=>{
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
var now = new Date();
|
||||
const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0));
|
||||
const minutes = d02(now.getMinutes());
|
||||
const day = d02(now.getDay());
|
||||
const day = d02(now.getDate());
|
||||
const month = d02(now.getMonth() + 1);
|
||||
const year = now.getFullYear();
|
||||
const month2 = locale.month(now, 3);
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -34,14 +34,12 @@ function getApp() {
|
|||
var ir = require("heatshrink").decompress(atob("jk0ggGDhvdAAXQCYwMEBxAMFAAIaH6c/+c9DgwMC/8zDg4aC/4YCHIwNB7/zHAwNCAgM/DQwqDAYIaHBonT/oNMFBAND74NNBhApEBrQAKBrrrGWpANZHBT7FBpYqIFAYcJBgkA5oMF7gNFFQwoFDgwMHHIoMIAAPM5gMKBrk0oANLmcwBu0zBrMDBv4AFN5gA/ADYA="));
|
||||
var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/+c3DgwMC/8yDg4aC/4YCHIwNBv/zHAwNCAgM/DQwqDAYIaHBolz+4NMFBANDv8nBpgMIFIgNaABQNddYy1IBrI4KfYoNLFRAoDDhIMEgHnBgt+BooqGFAoqGBg4OFBhAODBhQNcmUgBpczmAN2mYNZgYN/AApvMAH4Ab"));
|
||||
var igift = require("heatshrink").decompress(atob("q1QxH+ADOi0QbZ5nMHDQAbKgIACKa4ACKnJWVKghW0KgxWTKgxWyKhBWRKhBWwKhRWPKhRWuKhhWNKhhWtKpxWKKhys8KxBU8Ky5U+KypU/KyhU/KyhU/KynGKn5WTKn5WUKmHCADpJJE7uYABZUfKuuYKv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/AAv+Kv5VT/wADyIAaKpIlbABZSEKv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/ADNtKv6rdKzZVwKhAABy5V/Khw"));
|
||||
|
||||
var W=240,H=240;
|
||||
var blns = [];
|
||||
function updateFlake(f) {
|
||||
f.im = [ir,ig,ib][Math.round(Math.random()*100)%3];
|
||||
f.s = 0.4+Math.random()*0.5;
|
||||
}
|
||||
|
||||
for (var i=0;i<6;i++) {
|
||||
var f = {
|
||||
y:Math.random()*H,x:(0.5+(i<3?i:i+5))*W/11,
|
||||
|
|
@ -51,7 +49,6 @@ var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/
|
|||
updateFlake(f);
|
||||
blns.push(f);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
blns.forEach(f=>{
|
||||
f.y-=f.v;f.r+=f.t;
|
||||
|
|
@ -71,7 +68,6 @@ var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/
|
|||
g.drawString(${JSON.stringify(line4)},x,y+=10);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
g.clear();
|
||||
setInterval(draw,50);
|
||||
})()`;
|
||||
|
|
@ -79,7 +75,6 @@ var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/
|
|||
return `(function() {
|
||||
var isnow = require("heatshrink").decompress(atob("jEagQWTgfAAocf+gFDh4FDiARBggVB3AFBl3Agf8jfkn/AgX/v/9/+Agfv/2//YrBgfwh4wCgfghYFJCIYdFFIw1EIIpNFL44FFOIoAP"));
|
||||
var itree = require("heatshrink").decompress(atob("mtWxH+ADHHDTI0aGuXH5vNGmhqvTYIzBGtoxF6fTG4g4oGgQyBAAZssGoI0Ga1g1FGdo01ZgIAEGmHHNoLSuAAN/rdb0YFBGlgCBGYIABA4YArGYY1CGn4znAAM6GeVd5PQ5Iyurc/vQ0oGZFAn+d4XC3d5GddiGYIEBy+7zoEBGlFhoEcsQ9GT08+oFk1mkGdaVBMgNArnJ6/KzswGs/J6GlrlbqtbvPC5PCy8wGohniMIPJvIpCqmX3e7vI0BqhqlMIY0DqhtBqoEBa0xgBMIIoEqoABGQwzfsIhBv4qHABM50vQGjg1CGaN66DoBGt1ioGd5LoBGjo1PGYNhvLoCa7wnBqgvGA4YzCAgN5GUAsCqoDBmAHCAYU/wPQ0oSDGcBiDqkwAYcxoFd5PX6GdGjrIIqtUAAc3jk5vPC4fCy5pef5I2BTQMcnAHBy+7y95T0oADnFk1ekBpI2aGRUin7NGAA9hsIzVsIgHTAKZBZoPJ5LNDGhBpXGolcwOsrtcA4TNB3bNDGb/+sVin9AoGe6HX5InEvN/TkP+5XQwM/sRsBzqWB4QuKGjvC6HQ4QdDvKWBZYMwmAuHmFUCYNbqibX3fD5O7qolEZQQ0FBwgKDqgJBGiphEDwNUEgJbBFIQqCAgYOCB4IzCnE6GyhYFGoQnDABYzGAAQ1UAAo2NBoQSBnOB0t/Gjo2EABIPCoGe6HX4QzTGRIAEqtVF4QEBBQc4oE4y/J5PCvIxeABk/oADBvO73eXTyAyZMwM/Awd5vIOFGslAr2Av4PLNcU/jmA6HX5I1KasFcn8dTIOd5PJ4SZGGiNhAAIyNn0ckU+ZYe7AAJpJEYJnNGZk+n9kw9cBAcwGoN5aZg1JJJQABm8/oEjoDKC5ALCrUwqh/NrvQ6HDGp04n9doEdoE/sQJBZQZhCqgABGZk6zw0K/1dnVAoNAFwOlCYL1FubJBy4GCGh1AnOX4XC3YzHFYOeCgdV5PQ5OdD4rKBqqYNGYlbv+X3edGY3CGgKMDAAO7JAJgDAClcr2BEYgADaIZ0DL4uXGbDuB6HX5I1GsP+sNhOgWXIhBmWd4Od5PK4TwFGIJoBAYI2BAD0/jlcQoO7AAJaEGQQADGr0/sjNEvOdAoZmDGgw2ZsVAkeAZpQACGZI2VsU/kVGn1bZoPJZogpGGhA4GfRYwBoGC1mlBQbNFFoo0JNxAGCEod/wM6oFAn9iv/J6/Kzo1Ey9/MZQAKCg4GCFgTDEvPCSwI0BC5I0RN4ocEYYPQ5OdHgeXSwTFKGaJyKFYPC3f+MIdbpzFLAD4zB/1OqtbqtOGgYArGAIADGl9UAAI0wGQN5GoQ0vvIABGoI0uGYQABqo0zNOg0uaQY0/GllOGn40//w="));
|
||||
|
||||
var W=g.getWidth(),H=g.getHeight();
|
||||
var flakes = [];
|
||||
for (var i=0;i<10;i++) {
|
||||
|
|
@ -94,7 +89,6 @@ var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/
|
|||
f.v = f.s * (1+Math.random());
|
||||
flakes.push(f);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
flakes.forEach(f=>{
|
||||
f.y+=f.v;f.r+=f.t;
|
||||
|
|
@ -111,7 +105,6 @@ var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/
|
|||
g.drawString(${JSON.stringify(line4)},x,y+=10);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
g.clear();
|
||||
setInterval(draw,50);
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
0.01: First version
|
||||
0.02: Add widget
|
||||
|
||||
0.03: Bangle.js 2 support
|
||||
|
|
|
|||
|
|
@ -32,17 +32,16 @@ function formatTime(t) {
|
|||
}
|
||||
|
||||
function showMainMenu() {
|
||||
const menu = {
|
||||
"": {"title": "Quiet Mode"},
|
||||
"Current Mode": {
|
||||
value: (require("Storage").readJSON("setting.json", 1) || {}).quiet|0,
|
||||
format: v => modeNames[v],
|
||||
onchange: function(v) {
|
||||
if (v<0) {v = 2;}
|
||||
if (v>2) {v = 0;}
|
||||
require("qmsched").setMode(v);
|
||||
this.value = v;
|
||||
},
|
||||
let menu = {"": {"title": "Quiet Mode"}};
|
||||
// "Current Mode""Silent" won't fit on Bangle.js 2
|
||||
menu["Current" + ((process.env.HWVERSION===2)?"":" Mode")]= {
|
||||
value: (require("Storage").readJSON("setting.json", 1) || {}).quiet|0,
|
||||
format: v => modeNames[v],
|
||||
onchange: function(v) {
|
||||
if (v<0) {v = 2;}
|
||||
if (v>2) {v = 0;}
|
||||
require("qmsched").setMode(v);
|
||||
this.value = v;
|
||||
},
|
||||
};
|
||||
scheds.sort((a, b) => (a.hr-b.hr));
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -0,0 +1 @@
|
|||
0.01: App is created with gradient background.
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# School Calendar
|
||||
|
||||
School Calendar is a calendar that you can see your upcoming classes or schedule.
|
||||
|
||||
## Usage:
|
||||
Enter your calendar events on the customizer then upload. (all day events are not supported yet)
|
||||
|
||||
Once uploaded on the watch when in the table mode you can use BTN1 and BTN3 to scroll up and down on the list. (The red rectangle indicates your current position on the table and your yellow rectangle indicates your current schedule item or your next schedule item.)
|
||||
|
||||
If you press BTN2 it will go into detail mode, and you can see additional information about your schedule item. Also, in this mode you can scroll up and down with BTN1 and BTN3 to move around in the table. To exit detail mode press BTN2 again.
|
||||
|
||||
## Screenshots:
|
||||

|
||||

|
||||
|
||||
## Updates Coming Soon:
|
||||
- [ ] Notifications
|
||||
- [ ] All Day Events
|
||||
- [ ] Improved Rendering Screen
|
||||
- [ ] Better Graphics
|
||||
- [ ] Scrolling Table
|
||||
- [ ] Bangle.js V2 Compatibility
|
||||
- [ ] Full Calendar (Calendar that does not have repeating weekly events.)
|
||||
|
||||
## Creator
|
||||
Ronin0000
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwyBC/AH4A/AH4A/AH4A/AH4A/AH4A80s0AIIh/L/5f/EP4ATscsAIo9DBY4BVEJZf/L/5fRznzAIJfdEJZfpymyAJmSBpwPLBZRfqIYYBwL9OMuIBzL9VRAMRTDCJhfymBpkL+GEmABzL9UQAJelinOrPWzQDBymSCpe96+c6YnNL9N794tBAYoFD5152u21t1AoP332MuIPDVIJxB88c///AoODD4gFBLoYJBL9YBT888M4IHDNYPvvoDDznzD5pfq54BT737L4QHCVYQFCL4XTD5pfpueuAKOtqpRBxlRB5JfDEJpfqxwBIG4OOwkw/4AC+++xlxC5ZfBymyDoYlHAIJf0AImEiBbB0s0MIOtuoTJL4glML9NrtoBTLoJTBBpJfCyQfNL9VNAKZfB88cBpJfED5hfslgzEAoT3BxlxBYd795RB2uWC4tjAoQNBC4olFCIZfpFoIBJe4JJB+++AYe964XLL4YPLAIJfqhgBNueuAIJnBCp4BPL9Na9YBBsQBCAoY3BA4YBRC4INPL9oBS5YXWDoxfqJIIByL9NS1QBzL9WKAIgzBAooHFAMBfpMJABqLtYA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ALA"))
|
||||
|
|
@ -0,0 +1,416 @@
|
|||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
html, body, #map {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
#controls {
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
border: 1px solid black;
|
||||
position:absolute;
|
||||
right:0px;bottom:0px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
<link href='fullcalendar/main.css' rel='stylesheet' />
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<p>Create your events on the week shown. Keep in note that your events repeat weekly.</p>
|
||||
<p>One you have created your events, Click <button id="upload" class="btn btn-primary">Upload</button>.</p>
|
||||
<p>All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.</p>
|
||||
</div>
|
||||
|
||||
<script src='fullcalendar/main.js'></script>
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
|
||||
<script>
|
||||
var calendar;
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
initialView: 'timeGridWeek',
|
||||
headerToolbar: {
|
||||
left: '',
|
||||
center: 'title',
|
||||
right: 'timeGridWeek,listWeek'
|
||||
},
|
||||
navLinks: true, // can click day/week names to navigate views
|
||||
editable: true,
|
||||
selectable: true,
|
||||
selectMirror: true,
|
||||
nowIndicator: true,
|
||||
editable: true,
|
||||
height: 600,
|
||||
initialDate: '2018-06-03', // will be parsed as local
|
||||
select: function(arg) {
|
||||
var title = prompt('Event Title:');
|
||||
if (title) {
|
||||
calendar.addEvent({
|
||||
title: title,
|
||||
start: arg.start,
|
||||
end: arg.end,
|
||||
allDay: arg.allDay
|
||||
})
|
||||
}
|
||||
calendar.unselect()
|
||||
|
||||
},
|
||||
eventClick: function(arg) {
|
||||
if (confirm('Are you sure you want to delete this event?')) {
|
||||
arg.event.remove()
|
||||
}
|
||||
},
|
||||
});
|
||||
calendar.render();
|
||||
});
|
||||
|
||||
// When the 'upload' button is clicked...
|
||||
document.getElementById("upload").addEventListener("click", function () {
|
||||
//Cacultate data:
|
||||
var calendarEvents = calendar.getEvents();
|
||||
let schedule = []
|
||||
//--------------------
|
||||
for(i=0;i<calendarEvents.length;i++){
|
||||
var calendarEntry = {}
|
||||
calendarEntry['cn'] = calendarEvents[i].title;
|
||||
calendarEntry['dow'] = calendarEvents[i].start.getDate()-3;
|
||||
calendarEntry['sh'] = calendarEvents[i].start.getHours();
|
||||
calendarEntry['sm'] = calendarEvents[i].start.getMinutes();
|
||||
calendarEntry['eh'] = calendarEvents[i].end.getHours();
|
||||
calendarEntry['em'] = calendarEvents[i].end.getMinutes();
|
||||
schedule.push(calendarEntry)
|
||||
}
|
||||
// build the app's text using a templated String
|
||||
var app = `
|
||||
require("Font8x12").add(Graphics);
|
||||
require("Font7x11Numeric7Seg", 2).add(Graphics);
|
||||
var file = require("Storage").open("calendarItems.csv","w");
|
||||
let nIntervId;
|
||||
function redrawScreen() {
|
||||
layout.render(layout.background);
|
||||
layout.render(layout.buttons);
|
||||
draw();
|
||||
}
|
||||
function updateDay(ffunction,day){
|
||||
if(ffunction == 1){
|
||||
switch (day) {
|
||||
case 0:
|
||||
return "Sunday";
|
||||
case 1:
|
||||
day = "Monday";
|
||||
break;
|
||||
case 2:
|
||||
day = "Tuesday";
|
||||
break;
|
||||
case 3:
|
||||
day = "Wednesday";
|
||||
break;
|
||||
case 4:
|
||||
day = "Thursday";
|
||||
break;
|
||||
case 5:
|
||||
day = "Friday";
|
||||
break;
|
||||
case 6:
|
||||
day = "Saturday";
|
||||
}
|
||||
return day;
|
||||
}else if(ffunction == 2){
|
||||
switch (day) {
|
||||
case 0:
|
||||
return "Sun";
|
||||
case 1:
|
||||
day = "Mon";
|
||||
break;
|
||||
case 2:
|
||||
day = "Tue";
|
||||
break;
|
||||
case 3:
|
||||
day = "Wed";
|
||||
break;
|
||||
case 4:
|
||||
day = "Thu";
|
||||
break;
|
||||
case 5:
|
||||
day = "Fri";
|
||||
break;
|
||||
case 6:
|
||||
day = "Sat";
|
||||
}
|
||||
return day;
|
||||
}else if(ffunction == 3){
|
||||
switch (day) {
|
||||
case 0:
|
||||
return "S";
|
||||
case 1:
|
||||
day = "M";
|
||||
break;
|
||||
case 2:
|
||||
day = "T";
|
||||
break;
|
||||
case 3:
|
||||
day = "W";
|
||||
break;
|
||||
case 4:
|
||||
day = "R";
|
||||
break;
|
||||
case 5:
|
||||
day = "F";
|
||||
break;
|
||||
case 6:
|
||||
day = "S";
|
||||
}
|
||||
return day;
|
||||
}
|
||||
}
|
||||
function getScheduleTable() {
|
||||
let schedule = ${JSON.stringify(schedule)};
|
||||
logDebug(JSON.stringify(schedule));
|
||||
return schedule;
|
||||
}
|
||||
|
||||
function findNextScheduleIndex() {
|
||||
var schedule = getScheduleTable();
|
||||
var currentDate = new Date();
|
||||
var minuteOfWeek = (currentDate.getDay()*3600)+(currentDate.getHours()*60)+currentDate.getMinutes();
|
||||
//var minuteOfWeek = (4*3600)+(16*60)+0;
|
||||
var currentPosition;
|
||||
for(currentPosition = 0;currentPosition < schedule.length; currentPosition++){
|
||||
var scheduleItemStartMinuteOfWeek = schedule[currentPosition].dow*3600 + schedule[currentPosition].eh*60+schedule[currentPosition].em;
|
||||
if(scheduleItemStartMinuteOfWeek > minuteOfWeek) {
|
||||
return currentPosition;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
function getUpArrow() {return require("heatshrink").decompress(atob("hkOyANKmv9AIIjRCoYZRlvdAI8U3YVK3oBJC4Mc7YVRC4sc7gVCzoBNC4oZDGowXGR58lvoBFC9FcAIoXongBFC58dngBFC6EcAIoPHA"));}
|
||||
function getDownArrow() {return require("heatshrink").decompress(atob("hkOyALImv9AIojPmvdAIoXPlvdAIoXQ3oBFC9GdAIoXnkt9AIoPPAI8U3cc7cc7gBBDIVcAJYXFGYwXOLpU8AI4XBO5sdjgBFR54ZFBpIA=="));}
|
||||
function getMenuIcon() {return require("heatshrink").decompress(atob("iEQyBC/AEU+rwBEn02js17st3stvklrkljkc/cc3cUzYBBD5AdUD4oA/P/4A/P/4A/ADoA=="));}
|
||||
function getDotIcon() {return require("heatshrink").decompress(atob("iEQyBC/AA0t3oBBA4ndAIIPGA4gAFkt9lt9AYIHEzoBBBIwRED41cks8AYIJGA44RGP8xtGP44RJBYh1CAIIHHBJJ/KroBBPoqBFB4YRDAA8dngHHBJKdq3oBDBI4RNP4l9AIYHHBJJBJks8AIIHTAH4ABA="));}
|
||||
var currentPositionTable = 0;
|
||||
var numberOfItemsShown = 8;
|
||||
//Table Positions:
|
||||
var rectStart = 45;
|
||||
var rectEnd = 65;
|
||||
var rectStartX = 10;
|
||||
var rectEndX = 210;
|
||||
//Scences:
|
||||
LIST = 1;
|
||||
INFORMATION = 2;
|
||||
currentStage = LIST;
|
||||
function splitter(str, l){
|
||||
var strs = [];
|
||||
while(str.length > l){
|
||||
var pos = str.substring(0, l).lastIndexOf(' ');
|
||||
pos = pos <= 0 ? l : pos;
|
||||
strs.push(str.substring(0, pos));
|
||||
var i = str.indexOf(' ', pos)+1;
|
||||
if(i < pos || i > pos+l)
|
||||
i = pos;
|
||||
str = str.substring(i);
|
||||
}
|
||||
strs.push(str);
|
||||
return strs;
|
||||
}
|
||||
function updateMinutesToCurrentTime(currentMinuteFunction) {
|
||||
if (currentMinuteFunction<10){
|
||||
currentMinuteUpdatedFunction = "0"+currentMinuteFunction;
|
||||
}else{
|
||||
currentMinuteUpdatedFunction = currentMinuteFunction;
|
||||
}
|
||||
return currentMinuteUpdatedFunction;
|
||||
}
|
||||
function renderBackground(l) {
|
||||
g.clearRect(0,0,240,20);
|
||||
g.drawImage(getBackgroundImage(),110,130,{scale:9,rotate:0});
|
||||
}
|
||||
function renderTable(l) {
|
||||
var foundNumber = findNextScheduleIndex();
|
||||
var yellowIndex = 3;
|
||||
if (foundNumber < 3) { yellowIndex = foundNumber; }
|
||||
for(var x = 0;x<=numberOfItemsShown;x++){
|
||||
g.setColor(255,255,255);
|
||||
g.drawRect(rectStartX,rectStart+(x*20),rectEndX,rectEnd+(20*x));
|
||||
}
|
||||
g.setColor(255,205,0);
|
||||
g.drawRect(rectStartX,rectStart+(yellowIndex*20),rectEndX,rectEnd+(20*yellowIndex));
|
||||
g.setColor(255,0,0);
|
||||
g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable));
|
||||
}
|
||||
function renderTableText(l) {
|
||||
var foundSchedule = getScheduleTable();
|
||||
var foundNumber = findNextScheduleIndex();
|
||||
var startNumber = foundNumber - 2;
|
||||
if (startNumber < 0) { startNumber = 0; }
|
||||
var endNumber = startNumber + 8 - (foundNumber - startNumber);
|
||||
if (endNumber > foundSchedule.length-1) { endNumber = foundSchedule.length-1; }
|
||||
var scheduleHourUpdated;
|
||||
var scheduleMinuteUpdated;
|
||||
for(var currentNumber = startNumber; currentNumber<=endNumber; currentNumber++){
|
||||
scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[currentNumber].sm);
|
||||
scheduleHourUpdatedStart = foundSchedule[currentNumber].sh;
|
||||
scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[currentNumber].em);
|
||||
scheduleHourUpdatedEnd = foundSchedule[currentNumber].eh;
|
||||
scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20);
|
||||
if(foundSchedule[currentNumber].cn.length >= 15){
|
||||
scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20)+"...";
|
||||
}
|
||||
schduleDay = updateDay(3,foundSchedule[currentNumber].dow);
|
||||
g.setFont("8x12");
|
||||
g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(currentNumber*20));
|
||||
}
|
||||
}
|
||||
function buttonsF(l){
|
||||
if(currentStage == LIST){
|
||||
g.drawImage(getDotIcon(),223.5,115);
|
||||
}else{
|
||||
g.drawImage(getMenuIcon(),223.5,115);
|
||||
}
|
||||
g.drawImage(getUpArrow(),225,30);
|
||||
g.drawImage(getDownArrow(),225,215);
|
||||
}
|
||||
function draw() {
|
||||
var currentDate = new Date();
|
||||
var currentDayOfWeek = currentDate.getDay();
|
||||
var currentHour = currentDate.getHours();
|
||||
var currentMinute = currentDate.getMinutes();
|
||||
var currentMinuteUpdated = updateMinutesToCurrentTime(currentMinute);
|
||||
if (layout) {
|
||||
if(currentStage == LIST){
|
||||
layout.time.label = currentHour+":"+currentMinuteUpdated;
|
||||
layout.time.x = 147;
|
||||
layout.time.y = 10;
|
||||
layout.render(layout.table);
|
||||
layout.render(layout.tableText);
|
||||
logDebug("Rendered"+currentPositionTable);
|
||||
}else{
|
||||
layout.time.label = currentHour+":"+currentMinuteUpdated;
|
||||
layout.time.x = 147;
|
||||
layout.time.y = 10;
|
||||
layout.render(layout.info);
|
||||
logDebug("Rendered"+currentPositionTable);
|
||||
}
|
||||
g.clearRect(150,0,220,35);
|
||||
layout.render(layout.time);
|
||||
}
|
||||
}
|
||||
function RedRectDown() {
|
||||
if(currentPositionTable > 0){
|
||||
currentPositionTable -= 1;
|
||||
if(currentStage == INFORMATION){
|
||||
redrawScreen();
|
||||
}else{
|
||||
draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
function RedRectUp() {
|
||||
if(currentPositionTable < numberOfItemsShown){
|
||||
currentPositionTable += 1;
|
||||
if(currentStage == INFORMATION){
|
||||
redrawScreen();
|
||||
}else{
|
||||
draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
function renderMiniBackground(l){
|
||||
for(var i = 233;i<=240;i++){
|
||||
g.drawImage(getBackgroundImage(),i,123,{scale:10,rotate:0});
|
||||
}
|
||||
}
|
||||
function renderLoading(l){
|
||||
g.setFont("8x12");
|
||||
g.drawString("Loading...",240/2-20,240/2-20);
|
||||
}
|
||||
function renderInformation(l){
|
||||
var foundNumber = findNextScheduleIndex();
|
||||
var foundSchedule = getScheduleTable();
|
||||
var startNumber = foundNumber - 2;
|
||||
if (startNumber < 0) { startNumber = 0; }
|
||||
if ((startNumber+currentPositionTable) <= foundSchedule.length-1) {
|
||||
scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm);
|
||||
scheduleHourUpdatedStart = foundSchedule[foundNumber].sh;
|
||||
scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[foundNumber].em);
|
||||
scheduleHourUpdatedEnd = foundSchedule[foundNumber].eh;
|
||||
scheduleDay = updateDay(1,foundSchedule[(startNumber+currentPositionTable)].dow);
|
||||
g.setColor(255,255,255);
|
||||
g.setFont("8x12",2);
|
||||
var splitClassNames = splitter(foundSchedule[(startNumber+currentPositionTable)].cn, 15);
|
||||
var currentY = 5;
|
||||
for (var j=0; j < splitClassNames.length; j++) {
|
||||
g.drawString(splitClassNames[j],13,currentY+50);
|
||||
currentY = currentY + 25;
|
||||
}
|
||||
g.setFont("8x12");
|
||||
g.drawString(schduleDay,13,currentY+50);
|
||||
g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50);
|
||||
}
|
||||
}
|
||||
var Layout = require("Layout");
|
||||
var layout = new Layout(
|
||||
{type:"h", c: [
|
||||
{type:"custom", render:renderTableText, id:"tableText"},
|
||||
{type:"custom", render:buttonsF, id:"buttons"},
|
||||
{type:"custom", render:renderBackground, id:"background"},
|
||||
{type:"custom", render:renderTable, id:"table"},
|
||||
{type:"custom", render:renderMiniBackground, id:"miniBackground"},
|
||||
{type:"custom", render:renderLoading, id:"loading"},
|
||||
{type:"custom", render:renderInformation, id:"info"},
|
||||
{type:"txt", font:"7x11Numeric7Seg:2", label:"00:00", id:"time"},
|
||||
]},
|
||||
{type:"v", c:[
|
||||
]},
|
||||
{btns:[
|
||||
{label:"", cb: RedRectUp()},
|
||||
{label:"", cb: l=>print("Two")},
|
||||
{label:"", cb: RedRectDown()}
|
||||
]});
|
||||
function getBackgroundImage() {return require("heatshrink").decompress(atob("j0ZyEKIf4A4gIB6gQB6gYB6ggB6goB6gwB6g4B6hAABAYIBHBZIVLAK8IhIBXgAThhQB6hYB6hgB6hoB6hwB6h4B6iAB6iIB6iQBHiAJOB54XSiYB6igB6ioB6iwB6i4B5A="));}
|
||||
function logDebug(message) {console.log(message);}
|
||||
function changeScene(){
|
||||
layout.render(layout.buttons);
|
||||
if(currentStage == INFORMATION){
|
||||
currentStage = LIST;
|
||||
nIntervId = setInterval(redrawScreen, 100000);
|
||||
}else if(currentStage == LIST){
|
||||
currentStage = INFORMATION;
|
||||
clearInterval();
|
||||
}
|
||||
layout.render(layout.background);
|
||||
layout.render(layout.buttons);
|
||||
draw();
|
||||
}
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
setInterval(draw, 15000);
|
||||
setWatch(RedRectUp, BTN3, { repeat:true, edge:'rising', debounce : 50 });
|
||||
setWatch(RedRectDown, BTN1, { repeat:true, edge:'rising', debounce : 50 });
|
||||
setWatch(changeScene, BTN2, { repeat:true, edge:'rising', debounce : 50 });
|
||||
layout.update();
|
||||
layout.render(layout.loading);
|
||||
layout.render(layout.background);
|
||||
layout.render(layout.buttons);
|
||||
draw();
|
||||
file.write(JSON.stringify(schedule));
|
||||
`;
|
||||
// send finished app (in addition to contents of app.json)
|
||||
sendCustomizedApp({
|
||||
storage: [
|
||||
{ name: "schoolCalendar.app.js", url: "app.js", content: app },
|
||||
]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<div id='calendar'></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Adam Shaw
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
# FullCalendar Interaction Plugin
|
||||
|
||||
Provides functionality for event drag-n-drop, resizing, dateClick, and selectable actions
|
||||
|
||||
[View the docs »](https://fullcalendar.io/docs/editable)
|
||||
|
||||
This package was created from the [FullCalendar monorepo »](https://github.com/fullcalendar/fullcalendar)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "@fullcalendar/interaction",
|
||||
"version": "5.9.0",
|
||||
"title": "FullCalendar Interaction Plugin",
|
||||
"description": "Provides functionality for event drag-n-drop, resizing, dateClick, and selectable actions",
|
||||
"docs": "https://fullcalendar.io/docs/editable",
|
||||
"dependencies": {
|
||||
"@fullcalendar/common": "workspace:~5.9.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"main": "main.cjs.js",
|
||||
"module": "main.js",
|
||||
"types": "main.d.ts",
|
||||
"jsdelivr": "main.global.min.js",
|
||||
"browserGlobal": "FullCalendarInteraction",
|
||||
"homepage": "https://fullcalendar.io/",
|
||||
"bugs": "https://fullcalendar.io/reporting-bugs",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/fullcalendar/fullcalendar.git",
|
||||
"homepage": "https://github.com/fullcalendar/fullcalendar"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "Adam Shaw",
|
||||
"email": "arshaw@arshaw.com",
|
||||
"url": "http://arshaw.com/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fullcalendar/core-preact": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "tsc"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../common" }
|
||||
]
|
||||
}
|
||||