Merge branch 'espruino:master' into master
18
README.md
|
@ -226,10 +226,8 @@ and which gives information about the app for the Launcher.
|
|||
"name":"Short Name", // for Bangle.js menu
|
||||
"icon":"*myappid", // for Bangle.js menu
|
||||
"src":"-myappid", // source file
|
||||
"type":"widget/clock/app/bootloader", // optional, default "app"
|
||||
// if this is 'widget' then it's not displayed in the menu
|
||||
// if it's 'clock' then it'll be loaded by default at boot time
|
||||
// if this is 'bootloader' then it's code that is run at boot time, but is not in a menu
|
||||
"type":"widget/clock/app/bootloader/...", // optional, default "app"
|
||||
// see 'type' in 'metadata.json format' below for more options/info
|
||||
"version":"1.23",
|
||||
// added by BangleApps loader on upload based on metadata.json
|
||||
"files:"file1,file2,file3",
|
||||
|
@ -257,12 +255,18 @@ and which gives information about the app for the Launcher.
|
|||
// 'app' - an application
|
||||
// 'clock' - a clock - required for clocks to automatically start
|
||||
// 'widget' - a widget
|
||||
// 'launch' - replacement launcher app
|
||||
// 'bootloader' - code that runs at startup only
|
||||
// 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js'
|
||||
// 'RAM' - code that runs and doesn't upload anything to storage
|
||||
// 'launch' - replacement 'Launcher'
|
||||
// 'textinput' - provides a 'textinput' library that allows text to be input on the Bangle
|
||||
// 'scheduler' - provides 'sched' library and boot code for scheduling alarms/timers
|
||||
// (currently only 'sched' app)
|
||||
// 'notify' - provides 'notify' library for showing notifications
|
||||
// 'locale' - provides 'locale' library for language-specific date/distance/etc
|
||||
// (a version of 'locale' is included in the firmware)
|
||||
"tags": "", // comma separated tag list for searching
|
||||
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
|
||||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on
|
||||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above)
|
||||
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
|
||||
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
|
||||
"readme": "README.md", // if supplied, a link to a markdown-style text file
|
||||
|
|
|
@ -1 +1 @@
|
|||
theme: jekyll-theme-minimal
|
||||
theme: jekyll-theme-slate
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Use the new multiplatform 'Layout' library
|
||||
0.03: Exit as first menu option, dont show decimal places for seconds
|
||||
0.04: Localisation, change Exit->Back to allow back-arrow to appear on 2v13 firmware
|
||||
|
|
|
@ -7,21 +7,21 @@ function getFileName(n) {
|
|||
|
||||
function showMenu() {
|
||||
var menu = {
|
||||
"" : { title : "Accel Logger" },
|
||||
"Exit" : function() {
|
||||
"" : { title : /*LANG*/"Accel Logger" },
|
||||
"< Back" : function() {
|
||||
load();
|
||||
},
|
||||
"File No" : {
|
||||
/*LANG*/"File No" : {
|
||||
value : fileNumber,
|
||||
min : 0,
|
||||
max : MAXLOGS,
|
||||
onchange : v => { fileNumber=v; }
|
||||
},
|
||||
"Start" : function() {
|
||||
/*LANG*/"Start" : function() {
|
||||
E.showMenu();
|
||||
startRecord();
|
||||
},
|
||||
"View Logs" : function() {
|
||||
/*LANG*/"View Logs" : function() {
|
||||
viewLogs();
|
||||
},
|
||||
};
|
||||
|
@ -37,29 +37,29 @@ function viewLog(n) {
|
|||
if (ll) length = Math.round( (ll.split(",")[0]|0)/1000 );
|
||||
|
||||
var menu = {
|
||||
"" : { title : "Log "+n }
|
||||
"" : { title : "Log "+n },
|
||||
"< Back" : () => { viewLogs(); }
|
||||
};
|
||||
menu[records+" Records"] = "";
|
||||
menu[length+" Seconds"] = "";
|
||||
menu["DELETE"] = function() {
|
||||
E.showPrompt("Delete Log "+n).then(ok=>{
|
||||
menu[records+/*LANG*/" Records"] = "";
|
||||
menu[length+/*LANG*/" Seconds"] = "";
|
||||
menu[/*LANG*/"DELETE"] = function() {
|
||||
E.showPrompt(/*LANG*/"Delete Log "+n).then(ok=>{
|
||||
if (ok) {
|
||||
E.showMessage("Erasing...");
|
||||
E.showMessage(/*LANG*/"Erasing...");
|
||||
f.erase();
|
||||
viewLogs();
|
||||
} else viewLog(n);
|
||||
});
|
||||
};
|
||||
menu["< Back"] = function() {
|
||||
viewLogs();
|
||||
};
|
||||
|
||||
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function viewLogs() {
|
||||
var menu = {
|
||||
"" : { title : "Logs" }
|
||||
"" : { title : /*LANG*/"Logs" },
|
||||
"< Back" : () => { showMenu(); }
|
||||
};
|
||||
|
||||
var hadLogs = false;
|
||||
|
@ -67,14 +67,13 @@ function viewLogs() {
|
|||
var f = require("Storage").open(getFileName(i), "r");
|
||||
if (f.readLine()!==undefined) {
|
||||
(function(i) {
|
||||
menu["Log "+i] = () => viewLog(i);
|
||||
menu[/*LANG*/"Log "+i] = () => viewLog(i);
|
||||
})(i);
|
||||
hadLogs = true;
|
||||
}
|
||||
}
|
||||
if (!hadLogs)
|
||||
menu["No Logs Found"] = function(){};
|
||||
menu["< Back"] = function() { showMenu(); };
|
||||
menu[/*LANG*/"No Logs Found"] = function(){};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
|
@ -83,7 +82,7 @@ function startRecord(force) {
|
|||
// check for existing file
|
||||
var f = require("Storage").open(getFileName(fileNumber), "r");
|
||||
if (f.readLine()!==undefined)
|
||||
return E.showPrompt("Overwrite Log "+fileNumber+"?").then(ok=>{
|
||||
return E.showPrompt(/*LANG*/"Overwrite Log "+fileNumber+"?").then(ok=>{
|
||||
if (ok) startRecord(true); else showMenu();
|
||||
});
|
||||
}
|
||||
|
@ -93,14 +92,14 @@ function startRecord(force) {
|
|||
|
||||
var Layout = require("Layout");
|
||||
var layout = new Layout({ type: "v", c: [
|
||||
{type:"txt", font:"6x8", label:"Samples", pad:2},
|
||||
{type:"txt", font:"6x8", label:/*LANG*/"Samples", pad:2},
|
||||
{type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg},
|
||||
{type:"txt", font:"6x8", label:"Time", pad:2},
|
||||
{type:"txt", font:"6x8", label:/*LANG*/"Time", pad:2},
|
||||
{type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg},
|
||||
{type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:1},
|
||||
{type:"txt", font:"6x8:2", label:/*LANG*/"RECORDING", bgCol:"#f00", pad:5, fillx:1},
|
||||
]
|
||||
},{btns:[ // Buttons...
|
||||
{label:"STOP", cb:()=>{
|
||||
{label:/*LANG*/"STOP", cb:()=>{
|
||||
Bangle.removeListener('accel', accelHandler);
|
||||
showMenu();
|
||||
}}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "accellog",
|
||||
"name": "Acceleration Logger",
|
||||
"shortName": "Accel Log",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC",
|
||||
"icon": "app.png",
|
||||
"tags": "outdoor",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Fix the settings bug and some tweaking
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
A reminder to take short walks for the ones with a sedentary lifestyle.
|
||||
The alert will popup only if you didn't take your short walk yet
|
||||
|
||||
Differents settings can be personnalized:
|
||||
Different settings can be personalized:
|
||||
- Enable : Enable/Disable the app
|
||||
- Start hour: Hour to start the reminder
|
||||
- End hour: Hour to end the reminder
|
||||
- Max innactivity: Maximum innactivity time to allow before the alert. From 15 min to 60 min
|
||||
- Max inactivity: Maximum inactivity time to allow before the alert. From 15 to 60 min
|
||||
- Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 15 min
|
||||
- Min steps: Minimal amount of steps to count as an activity
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ function drawAlert(){
|
|||
});
|
||||
|
||||
Bangle.buzz(400);
|
||||
setTimeout(load, 10000);
|
||||
setTimeout(load, 20000);
|
||||
}
|
||||
|
||||
function run(){
|
||||
|
|
|
@ -6,17 +6,17 @@ exports.loadSettings = function() {
|
|||
maxInnactivityMin: 30,
|
||||
dismissDelayMin: 15,
|
||||
minSteps: 50
|
||||
}, require("Storage").readJSON("ar.settings.json", true) || {});
|
||||
}, require("Storage").readJSON("activityreminder.s.json", true) || {});
|
||||
};
|
||||
|
||||
exports.writeSettings = function(settings){
|
||||
require("Storage").writeJSON("ar.settings.json", settings);
|
||||
require("Storage").writeJSON("activityreminder.s.json", settings);
|
||||
};
|
||||
|
||||
exports.saveStepsArray = function(stepsArray) {
|
||||
require("Storage").writeJSON("ar.stepsarray.json", stepsArray);
|
||||
require("Storage").writeJSON("activityreminder.sa.json", stepsArray);
|
||||
};
|
||||
|
||||
exports.loadStepsArray = function(){
|
||||
return require("Storage").readJSON("ar.stepsarray.json") || [];
|
||||
return require("Storage").readJSON("activityreminder.sa.json") || [];
|
||||
};
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Activity Reminder",
|
||||
"shortName":"Activity Reminder",
|
||||
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
"tags": "tool,activity",
|
||||
|
@ -17,6 +17,7 @@
|
|||
{"name": "activityreminder.img", "url": "app-icon.js", "evaluate": true}
|
||||
],
|
||||
"data": [
|
||||
{"name": "ar.settings.json", "name": "ar.stepsarray.json"}
|
||||
{"name": "activityreminder.s.json"},
|
||||
{"name": "activityreminder.sa.json"}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"" : { "title" : "Activity Reminder" },
|
||||
"< Back" : () => back(),
|
||||
'Enable': {
|
||||
value: !!settings.enabled,
|
||||
value: settings.enabled,
|
||||
format: v => v?"Yes":"No",
|
||||
onchange: v => {
|
||||
settings.enabled = v;
|
||||
|
@ -15,7 +15,7 @@
|
|||
}
|
||||
},
|
||||
'Start hour': {
|
||||
value: 9|settings.startHour,
|
||||
value: settings.startHour,
|
||||
min: 0, max: 24,
|
||||
onchange: v => {
|
||||
settings.startHour = v;
|
||||
|
@ -23,31 +23,37 @@
|
|||
}
|
||||
},
|
||||
'End hour': {
|
||||
value: 20|settings.endHour,
|
||||
value: settings.endHour,
|
||||
min: 0, max: 24,
|
||||
onchange: v => {
|
||||
settings.endHour = v;
|
||||
require("activityreminder").writeSettings(settings);
|
||||
}
|
||||
},
|
||||
'Max innactivity': {
|
||||
value: 30|settings.maxInnactivityMin,
|
||||
min: 15, max: 60,
|
||||
'Max inactivity': {
|
||||
value: settings.maxInnactivityMin,
|
||||
min: 15, max: 120,
|
||||
onchange: v => {
|
||||
settings.maxInnactivityMin = v;
|
||||
require("activityreminder").writeSettings(settings);
|
||||
},
|
||||
format: x => {
|
||||
return x + " min";
|
||||
}
|
||||
},
|
||||
'Dismiss delay': {
|
||||
value: 10|settings.dismissDelayMin,
|
||||
value: settings.dismissDelayMin,
|
||||
min: 5, max: 15,
|
||||
onchange: v => {
|
||||
settings.dismissDelayMin = v;
|
||||
require("activityreminder").writeSettings(settings);
|
||||
},
|
||||
format: x => {
|
||||
return x + " min";
|
||||
}
|
||||
},
|
||||
'Min steps': {
|
||||
value: 50|settings.minSteps,
|
||||
value: settings.minSteps,
|
||||
min: 10, max: 500,
|
||||
onchange: v => {
|
||||
settings.minSteps = v;
|
||||
|
|
|
@ -22,3 +22,6 @@
|
|||
0.21: Fix time reset after a day of week change (#1676)
|
||||
0.22: Refactor some methods to scheduling library
|
||||
0.23: Fix regression with Days of Week (#1735)
|
||||
0.24: Automatically save the alarm/timer when the user returns to the main menu using the back arrow
|
||||
Add "Enable All", "Disable All" and "Remove All" actions
|
||||
0.25: Fix redrawing selected Alarm/Timer entry inside edit submenu
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
Default Alarm & Timer
|
||||
======================
|
||||
Alarms & Timers
|
||||
===============
|
||||
|
||||
This allows you to add/modify any running timers.
|
||||
This app allows you to add/modify any alarms and timers.
|
||||
|
||||
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.
|
||||
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched)
|
||||
to handle the alarm scheduling in an efficient way that can work alongside other apps.
|
||||
|
|
|
@ -47,11 +47,21 @@ function showMainMenu() {
|
|||
menu[txt] = {
|
||||
value : "\0"+atob(alarm.on?"EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g":"EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"),
|
||||
onchange : function() {
|
||||
if (alarm.timer) editTimer(idx, alarm);
|
||||
else editAlarm(idx, alarm);
|
||||
setTimeout(alarm.timer ? editTimer : editAlarm, 10, idx, alarm);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
if (alarms.some(e => !e.on)) {
|
||||
menu[/*LANG*/"Enable All"] = () => enableAll(true);
|
||||
}
|
||||
if (alarms.some(e => e.on)) {
|
||||
menu[/*LANG*/"Disable All"] = () => enableAll(false);
|
||||
}
|
||||
if (alarms.length > 0) {
|
||||
menu[/*LANG*/"Delete All"] = () => deleteAll();
|
||||
}
|
||||
|
||||
if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
@ -81,7 +91,10 @@ function editAlarm(alarmIndex, alarm) {
|
|||
|
||||
const menu = {
|
||||
'': { 'title': /*LANG*/'Alarm' },
|
||||
/*LANG*/'< Back' : () => showMainMenu(),
|
||||
/*LANG*/'< Back': () => {
|
||||
saveAlarm(newAlarm, alarmIndex, a, t);
|
||||
showMainMenu();
|
||||
},
|
||||
/*LANG*/'Hours': {
|
||||
value: t.hrs, min : 0, max : 23, wrap : true,
|
||||
onchange: v => t.hrs=v
|
||||
|
@ -102,7 +115,7 @@ function editAlarm(alarmIndex, alarm) {
|
|||
},
|
||||
/*LANG*/'Days': {
|
||||
value: "SMTWTFS".split("").map((d,n)=>a.dow&(1<<n)?d:".").join(""),
|
||||
onchange: () => editDOW(a.dow, d => {
|
||||
onchange: () => setTimeout(editDOW, 100, a.dow, d => {
|
||||
a.dow = d;
|
||||
a.t = require("sched").encodeTime(t);
|
||||
editAlarm(alarmIndex, a);
|
||||
|
@ -115,24 +128,33 @@ function editAlarm(alarmIndex, alarm) {
|
|||
onchange: v => a.as = v
|
||||
}
|
||||
};
|
||||
menu[/*LANG*/"Save"] = function() {
|
||||
a.t = require("sched").encodeTime(t);
|
||||
a.last = (a.t < getCurrentTime()) ? (new Date()).getDate() : 0;
|
||||
if (newAlarm) alarms.push(a);
|
||||
else alarms[alarmIndex] = a;
|
||||
saveAndReload();
|
||||
showMainMenu();
|
||||
};
|
||||
|
||||
menu[/*LANG*/"Cancel"] = () => showMainMenu();
|
||||
|
||||
if (!newAlarm) {
|
||||
menu[/*LANG*/"Delete"] = function() {
|
||||
alarms.splice(alarmIndex,1);
|
||||
menu[/*LANG*/"Delete"] = function () {
|
||||
alarms.splice(alarmIndex, 1);
|
||||
saveAndReload();
|
||||
showMainMenu();
|
||||
};
|
||||
}
|
||||
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
function saveAlarm(newAlarm, alarmIndex, a, t) {
|
||||
a.t = require("sched").encodeTime(t);
|
||||
a.last = (a.t < getCurrentTime()) ? (new Date()).getDate() : 0;
|
||||
|
||||
if (newAlarm) {
|
||||
alarms.push(a);
|
||||
} else {
|
||||
alarms[alarmIndex] = a;
|
||||
}
|
||||
|
||||
saveAndReload();
|
||||
}
|
||||
|
||||
function editTimer(alarmIndex, alarm) {
|
||||
let newAlarm = alarmIndex < 0;
|
||||
let a = require("sched").newDefaultTimer();
|
||||
|
@ -142,7 +164,10 @@ function editTimer(alarmIndex, alarm) {
|
|||
|
||||
const menu = {
|
||||
'': { 'title': /*LANG*/'Timer' },
|
||||
/*LANG*/'< Back' : () => showMainMenu(),
|
||||
/*LANG*/'< Back': () => {
|
||||
saveTimer(newAlarm, alarmIndex, a, t);
|
||||
showMainMenu();
|
||||
},
|
||||
/*LANG*/'Hours': {
|
||||
value: t.hrs, min : 0, max : 23, wrap : true,
|
||||
onchange: v => t.hrs=v
|
||||
|
@ -158,15 +183,9 @@ function editTimer(alarmIndex, alarm) {
|
|||
},
|
||||
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
|
||||
};
|
||||
menu[/*LANG*/"Save"] = function() {
|
||||
a.timer = require("sched").encodeTime(t);
|
||||
a.t = getCurrentTime() + a.timer;
|
||||
a.last = 0;
|
||||
if (newAlarm) alarms.push(a);
|
||||
else alarms[alarmIndex] = a;
|
||||
saveAndReload();
|
||||
showMainMenu();
|
||||
};
|
||||
|
||||
menu[/*LANG*/"Cancel"] = () => showMainMenu();
|
||||
|
||||
if (!newAlarm) {
|
||||
menu[/*LANG*/"Delete"] = function() {
|
||||
alarms.splice(alarmIndex,1);
|
||||
|
@ -177,4 +196,44 @@ function editTimer(alarmIndex, alarm) {
|
|||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
function saveTimer(newAlarm, alarmIndex, a, t) {
|
||||
a.timer = require("sched").encodeTime(t);
|
||||
a.t = getCurrentTime() + a.timer;
|
||||
a.last = 0;
|
||||
|
||||
if (newAlarm) {
|
||||
alarms.push(a);
|
||||
} else {
|
||||
alarms[alarmIndex] = a;
|
||||
}
|
||||
|
||||
saveAndReload();
|
||||
}
|
||||
|
||||
function enableAll(on) {
|
||||
E.showPrompt(/*LANG*/"Are you sure?", {
|
||||
title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All"
|
||||
}).then((confirm) => {
|
||||
if (confirm) {
|
||||
alarms.forEach(alarm => alarm.on = on);
|
||||
saveAndReload();
|
||||
}
|
||||
|
||||
showMainMenu();
|
||||
});
|
||||
}
|
||||
|
||||
function deleteAll() {
|
||||
E.showPrompt(/*LANG*/"Are you sure?", {
|
||||
title: /*LANG*/"Delete All"
|
||||
}).then((confirm) => {
|
||||
if (confirm) {
|
||||
alarms = [];
|
||||
saveAndReload();
|
||||
}
|
||||
|
||||
showMainMenu();
|
||||
});
|
||||
}
|
||||
|
||||
showMainMenu();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "alarm",
|
||||
"name": "Alarms & Timers",
|
||||
"shortName": "Alarms",
|
||||
"version": "0.23",
|
||||
"version": "0.25",
|
||||
"description": "Set alarms and timers on your Bangle",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,alarm,widget",
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
0.05: Fix handling of message actions
|
||||
0.06: Option to keep messages after a disconnect (default false) (fix #1186)
|
||||
0.07: Include charging state in battery updates to phone
|
||||
0.08: Handling of alarms
|
||||
|
|
|
@ -21,6 +21,7 @@ of Gadgetbridge - making your phone make noise so you can find it.
|
|||
* `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js
|
||||
keep any messages it has received, or should it delete them?
|
||||
* `Messages` - launches the messages app, showing a list of messages
|
||||
* `Alarms` - opens a submenu where you can set default settings for alarms such as vibration pattern, repeat, and auto snooze
|
||||
|
||||
## How it works
|
||||
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
}
|
||||
|
||||
var settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||
//default alarm settings
|
||||
if (settings.rp == undefined) settings.rp = true;
|
||||
if (settings.as == undefined) settings.as = true;
|
||||
if (settings.vibrate == undefined) settings.vibrate = "..";
|
||||
require('Storage').writeJSON("android.settings.json", settings);
|
||||
var _GB = global.GB;
|
||||
global.GB = (event) => {
|
||||
// feed a copy to other handlers if there were any
|
||||
|
@ -44,6 +49,40 @@
|
|||
title:event.name||"Call", body:"Incoming call\n"+event.number});
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
"alarm" : function() {
|
||||
//wipe existing GB alarms
|
||||
var sched;
|
||||
try { sched = require("sched"); } catch (e) {}
|
||||
if (!sched) return; // alarms may not be installed
|
||||
var gbalarms = sched.getAlarms().filter(a=>a.appid=="gbalarms");
|
||||
for (var i = 0; i < gbalarms.length; i++)
|
||||
sched.setAlarm(gbalarms[i].id, undefined);
|
||||
var alarms = sched.getAlarms();
|
||||
var time = new Date();
|
||||
var currentTime = time.getHours() * 3600000 +
|
||||
time.getMinutes() * 60000 +
|
||||
time.getSeconds() * 1000;
|
||||
for (var j = 0; j < event.d.length; j++) {
|
||||
// prevents all alarms from going off at once??
|
||||
var dow = event.d[j].rep;
|
||||
if (!dow) dow = 127; //if no DOW selected, set alarm to all DOW
|
||||
var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
|
||||
var a = {
|
||||
id : "gb"+j,
|
||||
appid : "gbalarms",
|
||||
on : true,
|
||||
t : event.d[j].h * 3600000 + event.d[j].m * 60000,
|
||||
dow : ((dow&63)<<1) | (dow>>6), // Gadgetbridge sends DOW in a different format
|
||||
last : last,
|
||||
rp : settings.rp,
|
||||
as : settings.as,
|
||||
vibrate : settings.vibrate
|
||||
};
|
||||
alarms.push(a);
|
||||
}
|
||||
sched.setAlarms(alarms);
|
||||
sched.reload();
|
||||
},
|
||||
};
|
||||
var h = HANDLERS[event.t];
|
||||
if (h) h(); else console.log("GB Unknown",event);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
"" : { "title" : "Android" },
|
||||
"< Back" : back,
|
||||
/*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
|
||||
"Find Phone" : () => E.showMenu({
|
||||
"" : { "title" : "Find Phone" },
|
||||
/*LANG*/"Find Phone" : () => E.showMenu({
|
||||
"" : { "title" : /*LANG*/"Find Phone" },
|
||||
"< Back" : ()=>E.showMenu(mainmenu),
|
||||
/*LANG*/"On" : _=>gb({t:"findPhone",n:true}),
|
||||
/*LANG*/"Off" : _=>gb({t:"findPhone",n:false}),
|
||||
|
@ -24,7 +24,28 @@
|
|||
updateSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/"Messages" : ()=>load("messages.app.js")
|
||||
/*LANG*/"Messages" : ()=>load("messages.app.js"),
|
||||
/*LANG*/"Alarms" : () => E.showMenu({
|
||||
"" : { "title" : /*LANG*/"Alarms" },
|
||||
"< Back" : ()=>E.showMenu(mainmenu),
|
||||
/*LANG*/"Vibrate": require("buzz_menu").pattern(settings.vibrate, v => {settings.vibrate = v; updateSettings();}),
|
||||
/*LANG*/"Repeat": {
|
||||
value: settings.rp,
|
||||
format : v=>v?/*LANG*/"Yes":/*LANG*/"No",
|
||||
onchange: v => {
|
||||
settings.rp = v;
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/"Auto snooze": {
|
||||
value: settings.as,
|
||||
format : v=>v?/*LANG*/"Yes":/*LANG*/"No",
|
||||
onchange: v => {
|
||||
settings.as = v;
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
})
|
||||
};
|
||||
E.showMenu(mainmenu);
|
||||
})
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwcCkGSpEgwQCChICFkgCBgkQoMEyFJAoICByVBkgLBkkSpIaDEwWShEkFgcIBAIdCEYQCBAoQdBAoYsBC4Q7BpICBEYQCDF4Q7CEYYCCEYUSKYYUDyRlCJQQIBNYYvBMoQCBkgjBFgxxCL4REDFgaPEHYgmCIgosCNYZEEDoZ0CNwY7CIIYgDEYtB9+e/dg/4AB2EJkYEB/mC/fn33Ivvz598v4MB/0BgoRCyVHvmW7Mg2EA8uD/EAh/IkGP/8AgVLtkA5El+FJvoRBgmf4Mkh0HkEQo9kyEfkeQofsgf4kmPCIP+h/gwULkkCncEu/ZsmRI4cEv0H8ESpdgEwMjwXI9kTCIOANYkSEYOCncF+UAjuR/ED+FBg/3/f8RgNgiVPkYdBtkT/Egv0Il+AoMfI4PgyX7vkW799F4Nl//4//woH/+0Ztvx7Fs335sk//5EB/IRBhACB77CBpEkgEIgGQoDRBgEggVBgDdBgGAgPv317ku+5cj334t+OSoI+B8gCBtlx7dkuFfgvx4N8yPbvgOB8ACBR4MA9mf4Egz3IgeChEDwDOBx/AjuCoN8y/JgkX4ME2FBjuQn65BgMtwELkGOEYOO4Mh2EJh+Sh/jOIMd+3fskRcwMTEwOWo98gCSBwFJkm2pfgx3II4PBk++/aABhEfwEInpZBvkX7MkJQMl2FHfANBjgCBlmQhHsgwjB33IkeyBAOChMcEwM9+/ZsBHBboMJtv2hd9+FHZANBVoM7kGC/fv2FJ9+GEYOAh//+UIaIMBkkQpEAHwIIBoMgiFJBANJEAMIkGShEkwQIChIIBhIIBhIaCkmQpIFCgmSEwYpDEYwCCpAICBwUEiQdFEwIICyAIDHwQ7CEYYpCEYWSpA7FDocSEwojBCgIaDIgYCBNwR0BNYYjFEwZTDLgQjGOgYvBEYQ7ENYlJFgQCCDohuGTYpBFkhoCSoQICEYIA="))
|
After Width: | Height: | Size: 4.0 KiB |
|
@ -0,0 +1,14 @@
|
|||
{ "id": "bradbury",
|
||||
"name": "Bradbury Watch",
|
||||
"shortName":"Bradbury",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"version":"0.01",
|
||||
"description": "A watch face based on the classic Seiko model worn by one of my favorite authors. I didn't follow the original lcd layout exactly, opting for larger font for more easily readable time, and adding date, battery level, and step count; read from the device. Tapping the screen toggles visibility of widgets.",
|
||||
"type": "clock",
|
||||
"supports":["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"bradbury.app.js","url":"app.js"},
|
||||
{"name":"bradbury.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 7.4 KiB |
|
@ -1,3 +1,3 @@
|
|||
0.01: Initial upload
|
||||
0.2: Added scrollable calendar and swipe gestures
|
||||
0.3: Configurable drag gestures
|
||||
0.02: Added scrollable calendar and swipe gestures
|
||||
0.03: Configurable drag gestures
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "clockcal",
|
||||
"name": "Clock & Calendar",
|
||||
"version": "0.3",
|
||||
"version": "0.03",
|
||||
"description": "Clock with Calendar",
|
||||
"readme":"README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
0.24: Added previews to the customizer.
|
||||
0.25: Fixed a bug that would let widgets change the color of the clock.
|
||||
0.26: Time formatted to locale
|
||||
0.27: Fixed the timing code, which sometimes did not update for one minute
|
||||
|
|
|
@ -7,6 +7,13 @@ if (settings.fontIndex==undefined) {
|
|||
require('Storage').writeJSON("myapp.json", settings);
|
||||
}
|
||||
|
||||
function queueDraw() {
|
||||
setTimeout(function() {
|
||||
draw();
|
||||
queueDraw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function draw() {
|
||||
var date = new Date();
|
||||
// Draw day of the week
|
||||
|
@ -24,7 +31,5 @@ Bangle.setUI("clock");
|
|||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
queueDraw();
|
||||
draw();
|
||||
setTimeout(function() {
|
||||
setInterval(draw,60000);
|
||||
}, 60000 - Date.now() % 60000);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "contourclock",
|
||||
"name": "Contour Clock",
|
||||
"shortName" : "Contour Clock",
|
||||
"version":"0.26",
|
||||
"version":"0.27",
|
||||
"icon": "app.png",
|
||||
"description": "A Minimalist clockface with large Digits. Now with more fonts!",
|
||||
"screenshots" : [{"url":"cc-screenshot-1.png"},{"url":"cc-screenshot-2.png"}],
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"screenshots": [{"url":"app.png"}],
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
"tags": "game",
|
||||
"tags": "tool",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "gpsrec",
|
||||
"name": "GPS Recorder",
|
||||
"version": "0.28",
|
||||
"description": "Application that allows you to record a GPS track. Can run in background",
|
||||
"description": "(NOT RECOMMENDED) - please use the more flexible 'Recorder' app instead. Application that allows you to record a GPS track. Can run in background",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,outdoors,gps,widget",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
|
|
@ -12,3 +12,4 @@
|
|||
0.11: Pre-minified boot&lib - folds constants and saves RAM
|
||||
0.12: Add setting for Daily Step Goal
|
||||
0.13: Add support for internationalization
|
||||
0.14: Move settings
|
||||
|
|
|
@ -1,14 +1,3 @@
|
|||
function getSettings() {
|
||||
return Object.assign({
|
||||
hrm: 0,
|
||||
stepGoal: 10000
|
||||
}, require("Storage").readJSON("health.json", true) || {});
|
||||
}
|
||||
|
||||
function setSettings(settings) {
|
||||
require("Storage").writeJSON("health.json", settings);
|
||||
}
|
||||
|
||||
function menuMain() {
|
||||
swipe_enabled = false;
|
||||
clearButton();
|
||||
|
@ -18,38 +7,7 @@ function menuMain() {
|
|||
/*LANG*/"Step Counting": () => menuStepCount(),
|
||||
/*LANG*/"Movement": () => menuMovement(),
|
||||
/*LANG*/"Heart Rate": () => menuHRM(),
|
||||
/*LANG*/"Settings": () => menuSettings()
|
||||
});
|
||||
}
|
||||
|
||||
function menuSettings() {
|
||||
swipe_enabled = false;
|
||||
clearButton();
|
||||
let settings = getSettings();
|
||||
|
||||
E.showMenu({
|
||||
"": { title:/*LANG*/"Health Tracking" },
|
||||
/*LANG*/"< Back": () => menuMain(),
|
||||
/*LANG*/"HRM Interval": {
|
||||
value: settings.hrm,
|
||||
min: 0,
|
||||
max: 3,
|
||||
format: v => [ /*LANG*/"Off", /*LANG*/"3 min", /*LANG*/"10 min", /*LANG*/"Always"][v],
|
||||
onchange: v => {
|
||||
settings.hrm = v;
|
||||
setSettings(settings);
|
||||
}
|
||||
},
|
||||
/*LANG*/"Daily Step Goal": {
|
||||
value: settings.stepGoal,
|
||||
min: 0,
|
||||
max: 20000,
|
||||
step: 250,
|
||||
onchange: v => {
|
||||
settings.stepGoal = v;
|
||||
setSettings(settings);
|
||||
}
|
||||
}
|
||||
/*LANG*/"Settings": () => eval(require("Storage").read("health.settings.js"))(()=>menuMain())
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -86,7 +44,6 @@ function menuHRM() {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
function stepsPerHour() {
|
||||
E.showMessage(/*LANG*/"Loading...");
|
||||
let data = new Uint16Array(24);
|
||||
|
@ -176,7 +133,7 @@ var chart_data;
|
|||
var swipe_enabled = false;
|
||||
var btn;
|
||||
|
||||
// find the max value in the array, using a loop due to array size
|
||||
// find the max value in the array, using a loop due to array size
|
||||
function max(arr) {
|
||||
var m = -Infinity;
|
||||
|
||||
|
@ -188,10 +145,10 @@ function max(arr) {
|
|||
// find the end of the data, the array might be for 31 days but only have 2 days of data in it
|
||||
function get_data_length(arr) {
|
||||
var nlen = arr.length;
|
||||
|
||||
|
||||
for(var i = arr.length - 1; i > 0 && arr[i] == 0; i--)
|
||||
nlen--;
|
||||
|
||||
|
||||
return nlen;
|
||||
}
|
||||
|
||||
|
@ -210,10 +167,10 @@ function drawBarChart() {
|
|||
const bar_width = (w - 2) / 9; // we want 9 bars, bar 5 in the centre
|
||||
var bar_top;
|
||||
var bar;
|
||||
|
||||
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(0,24,w,h);
|
||||
|
||||
|
||||
for (bar = 1; bar < 10; bar++) {
|
||||
if (bar == 5) {
|
||||
g.setFont('6x8', 2);
|
||||
|
@ -226,7 +183,7 @@ function drawBarChart() {
|
|||
}
|
||||
|
||||
// draw a fake 0 height bar if chart_index is outside the bounds of the array
|
||||
if ((chart_index + bar - 1) >= 0 && (chart_index + bar - 1) < data_len)
|
||||
if ((chart_index + bar - 1) >= 0 && (chart_index + bar - 1) < data_len)
|
||||
bar_top = bar_bot - 100 * (chart_data[chart_index + bar - 1]) / chart_max_datum;
|
||||
else
|
||||
bar_top = bar_bot;
|
||||
|
@ -256,7 +213,7 @@ Bangle.on('swipe', dir => {
|
|||
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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "health",
|
||||
"name": "Health Tracking",
|
||||
"version": "0.13",
|
||||
"version": "0.14",
|
||||
"description": "Logs health data and provides an app to view it",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,health",
|
||||
|
@ -12,6 +12,8 @@
|
|||
{"name":"health.app.js","url":"app.js"},
|
||||
{"name":"health.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"health.boot.js","url":"boot.min.js"},
|
||||
{"name":"health","url":"lib.min.js"}
|
||||
]
|
||||
{"name":"health","url":"lib.min.js"},
|
||||
{"name":"health.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [{"name":"health.json"}]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
(function (back) {
|
||||
var settings = Object.assign({
|
||||
hrm: 0,
|
||||
stepGoal: 10000
|
||||
}, require("Storage").readJSON("health.json", true) || {});
|
||||
|
||||
E.showMenu({
|
||||
"": { title: /*LANG*/"Health Tracking" },
|
||||
|
||||
/*LANG*/"< Back": () => back(),
|
||||
|
||||
/*LANG*/"HRM Interval": {
|
||||
value: settings.hrm,
|
||||
min: 0,
|
||||
max: 3,
|
||||
format: v => [
|
||||
/*LANG*/"Off",
|
||||
/*LANG*/"3 min",
|
||||
/*LANG*/"10 min",
|
||||
/*LANG*/"Always"
|
||||
][v],
|
||||
onchange: v => {
|
||||
settings.hrm = v;
|
||||
setSettings(settings);
|
||||
}
|
||||
},
|
||||
|
||||
/*LANG*/"Daily Step Goal": {
|
||||
value: settings.stepGoal,
|
||||
min: 0,
|
||||
max: 20000,
|
||||
step: 250,
|
||||
onchange: v => {
|
||||
settings.stepGoal = v;
|
||||
setSettings(settings);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function setSettings(settings) {
|
||||
require("Storage").writeJSON("health.json", settings);
|
||||
}
|
||||
})
|
|
@ -1 +1 @@
|
|||
1.0: Initial release.
|
||||
0.01: Initial release.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "heartzone",
|
||||
"name": "HeartZone",
|
||||
"version": "1.0",
|
||||
"version": "0.01",
|
||||
"description": "Exercise app for keeping your heart rate in the aerobic zone. Buzzes the watch at configurable intervals when your heart rate is outside of configured limits.",
|
||||
"readme":"README.md",
|
||||
"screenshots": [
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "HRM Accelerometer event recorder",
|
||||
"shortName": "HRM ACC recorder",
|
||||
"version": "0.01",
|
||||
"type": "ram",
|
||||
"type": "RAM",
|
||||
"description": "Record HRM and accelerometer events in high resolution to CSV files in your browser",
|
||||
"icon": "app.png",
|
||||
"tags": "debug",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Now keeps user input trace intact by changing how the screen is updated.
|
||||
0.03: Positioning of marker now takes the height of the widget field into account.
|
||||
0.04: Fix issue if going back without typing.
|
||||
|
|
|
@ -4,6 +4,7 @@ A library that provides the ability to input text by swiping PalmOS Graffiti-sty
|
|||
|
||||
To get a legend of available characters, just tap the screen.
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
|
|
After Width: | Height: | Size: 14 KiB |
|
@ -82,6 +82,24 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) );
|
|||
g.drawString(l.join("\n"),R.x+4,R.y+4);
|
||||
}
|
||||
|
||||
/*
|
||||
// This draws a big image to use in the README
|
||||
(function() {
|
||||
E.defrag();
|
||||
var b = Graphics.createArrayBuffer(500,420,1,{msb:true});
|
||||
var n=0;
|
||||
exports.getStrokes((id,s) => {
|
||||
var x = n%6;
|
||||
var y = (n-x)/6;
|
||||
s = b.transformVertices(s, {scale:0.55, x:x*85-20, y:y*85-20});
|
||||
b.fillCircle(s[0],s[1],3);
|
||||
b.drawPoly(s);
|
||||
n++;
|
||||
});
|
||||
b.dump();
|
||||
})()
|
||||
*/
|
||||
|
||||
function show() {
|
||||
g.reset();
|
||||
g.clearRect(R).setColor("#f00");
|
||||
|
@ -94,7 +112,6 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) );
|
|||
g.drawPoly(s);
|
||||
n++;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function strokeHandler(o) {
|
||||
|
@ -130,7 +147,7 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) );
|
|||
show();
|
||||
}, back:()=>{
|
||||
Bangle.removeListener("stroke", strokeHandler);
|
||||
clearInterval(flashInterval);
|
||||
if (flashInterval) clearInterval(flashInterval);
|
||||
Bangle.setUI();
|
||||
g.clearRect(Bangle.appRect);
|
||||
resolve(text);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{ "id": "kbswipe",
|
||||
"name": "Swipe keyboard",
|
||||
"version":"0.03",
|
||||
"version":"0.04",
|
||||
"description": "A library for text input via PalmOS style swipe gestures (beta!)",
|
||||
"icon": "app.png",
|
||||
"type":"textinput",
|
||||
"tags": "keyboard",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"textinput","url":"lib.js"}
|
||||
|
|
|
@ -1,25 +1,34 @@
|
|||
# Languages (locale)
|
||||
Languages (locale)
|
||||
==================
|
||||
|
||||
Country-specific app internationalisation.
|
||||
|
||||
This is not an app, but instead it is a library that can be used by
|
||||
other applications or widgets to display messages.
|
||||
other applications or widgets to provide locale-friendly
|
||||
|
||||
## Usage
|
||||
- Dates
|
||||
- Time (12h/24h)
|
||||
- Days of the Week
|
||||
- Months
|
||||
- Currency values
|
||||
- Distances/Lengths/Speed (metric/imperial)
|
||||
- Temperature (°C/°F)
|
||||
|
||||
Some menus that pop up are translated automatically, but if you're
|
||||
writing an application you can use the `locale` library to
|
||||
Usage
|
||||
-----
|
||||
|
||||
If you're writing an application you can use the `locale` library to
|
||||
do all the translation for you.
|
||||
|
||||
See https://www.espruino.com/Bangle.js+Locale for full examples.
|
||||
|
||||
```JS
|
||||
// Date to date string (long)
|
||||
>require('locale').date(new Date())
|
||||
>require("locale").date(new Date())
|
||||
="Donnerstag, 02. April 2020"
|
||||
|
||||
// Date to date string (short)
|
||||
>require('locale').date(new Date(),1)
|
||||
>require("locale").date(new Date(), 1)
|
||||
="02.04.2020"
|
||||
```
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"version": "0.02",
|
||||
"description": "Replace Bangle.js 2's menus with a version that contains smaller text",
|
||||
"icon": "app.png",
|
||||
"type": "boot",
|
||||
"type": "bootloader",
|
||||
"tags": "system",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{"url":"screenshot_b1_dark.png"},{"url":"screenshot_b1_edit.png"},{"url":"screenshot_b1_light.png"},
|
||||
{"url":"screenshot_b2_dark.png"},{"url":"screenshot_b2_edit.png"},{"url":"screenshot_b2_light.png"}
|
||||
],
|
||||
"type": "boot",
|
||||
"type": "bootloader",
|
||||
"tags": "system",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
|
|
|
@ -47,3 +47,5 @@
|
|||
0.32: Added an option to allow quiet mode to override message auto-open
|
||||
0.33: Timeout from the message list screen if the message being displayed is removed and there is a timer going
|
||||
0.34: Don't buzz for 'map' update messages
|
||||
0.35: Reset graphics colors before rendering a message (possibly fix #1752)
|
||||
0.36: Ensure a new message plus an almost immediate deletion of that message doesn't load the messages app (fix #1362)
|
||||
|
|
|
@ -89,7 +89,7 @@ function getNegImage() {
|
|||
function getMessageImage(msg) {
|
||||
if (msg.img) return atob(msg.img);
|
||||
var s = (msg.src||"").toLowerCase();
|
||||
if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA=");
|
||||
if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA=");
|
||||
if (s=="bibel") return atob("GBgBAAAAA//wD//4D//4H//4H/f4H/f4H+P4H4D4H4D4H/f4H/f4H/f4H/f4H/f4H//4H//4H//4GAAAEAAAEAAACAAAB//4AAAA");
|
||||
if (s=="calendar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA==");
|
||||
if (s=="corona-warn") return atob("GBgBAAAAABwAAP+AAf/gA//wB/PwD/PgDzvAHzuAP8EAP8AAPAAAPMAAP8AAH8AAHzsADzuAB/PAB/PgA//wAP/gAH+AAAwAAAAA");
|
||||
|
@ -193,7 +193,7 @@ function showMapMessage(msg) {
|
|||
]},
|
||||
{type:"txt", font:"6x8:2", label:eta }
|
||||
]});
|
||||
g.clearRect(Bangle.appRect);
|
||||
g.reset().clearRect(Bangle.appRect);
|
||||
layout.render();
|
||||
Bangle.setUI("updown",function() {
|
||||
// any input to mark as not new and return to menu
|
||||
|
@ -268,7 +268,7 @@ function showMusicMessage(msg) {
|
|||
]}:{},
|
||||
{type:"txt", font:"6x8:2", label:msg.dur?fmtTime(msg.dur):"--:--" }
|
||||
]});
|
||||
g.clearRect(Bangle.appRect);
|
||||
g.reset().clearRect(Bangle.appRect);
|
||||
layout.render();
|
||||
|
||||
updateLabelsInterval = setInterval(function() {
|
||||
|
@ -434,7 +434,7 @@ function showMessage(msgid) {
|
|||
} },
|
||||
{type:"h",fillx:1, c: buttons}
|
||||
]});
|
||||
g.clearRect(Bangle.appRect);
|
||||
g.reset().clearRect(Bangle.appRect);
|
||||
layout.render();
|
||||
// ensure button-press on Bangle.js 2 takes us back
|
||||
if (process.env.HWVERSION>1) Bangle.btnWatches = [
|
||||
|
|
|
@ -43,6 +43,9 @@ exports.pushMessage = function(event) {
|
|||
// if we've removed the last new message, hide the widget
|
||||
if (event.t=="remove" && !messages.some(m=>m.new)) {
|
||||
if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.hide();
|
||||
// if no new messages now, make sure we don't load the messages app
|
||||
if (exports.messageTimeout && !messages.some(m=>m.new))
|
||||
clearTimeout(exports.messageTimeout);
|
||||
}
|
||||
// ok, saved now
|
||||
if (event.id=="music" && Bangle.CLOCK && messages[mIdx].new && openMusic()) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "messages",
|
||||
"name": "Messages",
|
||||
"version": "0.34",
|
||||
"version": "0.36",
|
||||
"description": "App to display notifications from iOS and Gadgetbridge/Android",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"icon": "mmind.png",
|
||||
"version":"0.01",
|
||||
"description": "This is the classic game for masterminds",
|
||||
"type": "game",
|
||||
"type": "app",
|
||||
"tags": "mastermind, game, classic",
|
||||
"readme":"README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"version": "0.01",
|
||||
"description": "A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.",
|
||||
"icon": "mystic-dock.png",
|
||||
"type": "dock",
|
||||
"type": "app",
|
||||
"tags": "dock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Fix true wind computation, add swipe gesture to pause GPS
|
||||
|
|
|
@ -14,7 +14,9 @@ additionally displayed in red. In this mode, the speed over ground in knots is
|
|||
|
||||
## Controls
|
||||
|
||||
There are no controls in the main app, but there are two settings in the settings app that can be changed:
|
||||
In the main app, when true wind mode is enabled (see below), swiping left on the screen will temporarily disable GPS (to preserve battery); a small
|
||||
red satellite symbol will appear on the bottom right. Swiping right will turn GPS back on.
|
||||
The settings app provides the following two settings:
|
||||
|
||||
* True wind: enables or disables true wind calculations; enabling this will turn on GPS inside the app
|
||||
* Mounting angle: mounting relative to the boat of the wind instrument (in degrees)
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
OW_CHAR_UUID = '0000cc91-0000-1000-8000-00805f9b34fb';
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
gatt = {};
|
||||
cx = g.getWidth()/2;
|
||||
cy = 24+(g.getHeight()-24)/2;
|
||||
w = (g.getWidth()-24)/2;
|
||||
|
||||
gps_course = { spd: 0 };
|
||||
var gatt = {};
|
||||
var cx = g.getWidth()/2;
|
||||
var cy = 24+(g.getHeight()-24)/2;
|
||||
var w = (g.getWidth()-24)/2;
|
||||
var y1 = 24;
|
||||
var y2 = g.getHeight()-1;
|
||||
var gps_course = { spd: 0 };
|
||||
var course_marker_len = g.getWidth()/4;
|
||||
|
||||
var settings = require("Storage").readJSON('openwindsettings.json', 1) || {};
|
||||
|
||||
i = 0;
|
||||
hullpoly = [];
|
||||
var pause_gps = false;
|
||||
|
||||
var i = 0;
|
||||
var hullpoly = [];
|
||||
for (y=-1; y<=1; y+=0.1) {
|
||||
hullpoly[i++] = cx - (y<0 ? 1+y*0.15 : (Math.sqrt(1-0.7*y*y)-Math.sqrt(0.3))/(1-Math.sqrt(0.3)))*w*0.3;
|
||||
hullpoly[i++] = cy - y*w*0.7;
|
||||
|
@ -22,21 +26,22 @@ for (y=1; y>=-1; y-=0.1) {
|
|||
|
||||
function wind_updated(ev) {
|
||||
if (ev.target.uuid == "0xcc91") {
|
||||
awa = settings.mount_angle-ev.target.value.getInt16(1, true)*0.1;
|
||||
awa = settings.mount_angle+ev.target.value.getInt16(1, true)*0.1;
|
||||
if (awa<0) awa += 360;
|
||||
aws = ev.target.value.getInt16(3, true)*0.01;
|
||||
// console.log(awa, aws);
|
||||
//console.log(awa, aws);
|
||||
if (gps_course.spd > 0) {
|
||||
wv = { // wind vector (in fixed reference frame)
|
||||
lon: Math.sin(Math.PI*(gps_course.course+awa)/180)*aws,
|
||||
lat: Math.cos(Math.PI*(gps_course.course+awa)/180)*aws
|
||||
wv = { // wind vector (in "earth" reference frame)
|
||||
vlon: Math.sin(Math.PI*(gps_course.course+(awa+180))/180)*aws,
|
||||
vlat: Math.cos(Math.PI*(gps_course.course+(awa+180))/180)*aws
|
||||
};
|
||||
twv = { lon: wv.lon+gps_course.lon, lat: wv.lat+gps_course.lat };
|
||||
tws = Math.sqrt(Math.pow(twv.lon,2)+Math.pow(twv.lat, 2));
|
||||
twa = Math.atan2(twv.lat, twv.lon)*180/Math.PI-gps_course.course;
|
||||
twv = { vlon: wv.vlon+gps_course.vlon, vlat: wv.vlat+gps_course.vlat };
|
||||
tws = Math.sqrt(Math.pow(twv.vlon,2)+Math.pow(twv.vlat, 2));
|
||||
twa = 180+Math.atan2(twv.vlon, twv.vlat)*180/Math.PI-gps_course.course;
|
||||
if (twa<0) twa += 360;
|
||||
if (twa>360) twa -=360;
|
||||
}
|
||||
else {
|
||||
else {
|
||||
tws = -1;
|
||||
twa = 0;
|
||||
}
|
||||
|
@ -57,34 +62,37 @@ function draw_compass(awa, aws, twa, tws) {
|
|||
a = i*Math.PI/2+Math.PI/4;
|
||||
g.drawLineAA(cx+Math.cos(a)*w*0.85, cy+Math.sin(a)*w*0.85, cx+Math.cos(a)*w*0.99, cy+Math.sin(a)*w*0.99);
|
||||
}
|
||||
g.setColor(0, 1, 0).fillCircle(cx+Math.sin(Math.PI*awa/180)*w*0.9, cy+Math.cos(Math.PI*awa/180)*w*0.9, w*0.1);
|
||||
g.setColor(0, 1, 0).fillCircle(cx+Math.sin(Math.PI*awa/180)*w*0.9, cy-Math.cos(Math.PI*awa/180)*w*0.9, w*0.1);
|
||||
if (tws>0) g.setColor(1, 0, 0).fillCircle(cx+Math.sin(Math.PI*twa/180)*w*0.9, cy+Math.cos(Math.PI*twa/180)*w*0.9, w*0.1);
|
||||
g.setColor(0, 1, 0).setFont("7x11Numeric7Seg",w*0.06);
|
||||
g.setFontAlign(0, 0, 0).drawString(aws.toFixed(1), cx, cy-0.32*w);
|
||||
if (tws>0) g.setColor(1, 0, 0).drawString(tws.toFixed(1), cx, cy+0.32*w);
|
||||
if (settings.truewind && typeof gps_course.spd!=='undefined') {
|
||||
spd = gps_course.spd/1.852;
|
||||
g.setColor(g.theme.fg).setFont("7x11Numeric7Seg", w*0.03).setFontAlign(-1, 1, 0).drawString(spd.toFixed(1), 1, g.getHeight()-1);
|
||||
if (!pause_gps) {
|
||||
if (tws>0) g.setColor(1, 0, 0).drawString(tws.toFixed(1), cx, cy+0.32*w);
|
||||
if (settings.truewind && gps_course.spd!=-1) {
|
||||
spd = gps_course.spd/1.852;
|
||||
g.setColor(g.theme.fg).setFont("7x11Numeric7Seg", w*0.03).setFontAlign(-1, 1, 0).drawString(spd.toFixed(1), 1, g.getHeight()-1);
|
||||
}
|
||||
}
|
||||
if (pause_gps) g.setColor("#f00").drawImage(atob("DAwBEAKARAKQE4DwHkPqPRGKAEAA"),g.getWidth()-15, g.getHeight()-15);
|
||||
}
|
||||
|
||||
function parseDevice(d) {
|
||||
device = d;
|
||||
console.log("Found device");
|
||||
device.gatt.connect().then(function(ga) {
|
||||
console.log("Connected");
|
||||
gatt = ga;
|
||||
return ga.getPrimaryService("cc90");
|
||||
}).then(function(s) {
|
||||
return s.getCharacteristic("cc91");
|
||||
}).then(function(c) {
|
||||
c.on('characteristicvaluechanged', (event)=>wind_updated(event));
|
||||
return c.startNotifications();
|
||||
}).then(function() {
|
||||
console.log("Done!");
|
||||
}).catch(function(e) {
|
||||
console.log("ERROR"+e);
|
||||
});}
|
||||
device.gatt.connect().then(function(ga) {
|
||||
console.log("Connected");
|
||||
gatt = ga;
|
||||
return ga.getPrimaryService("cc90");
|
||||
}).then(function(s) {
|
||||
return s.getCharacteristic("cc91");
|
||||
}).then(function(c) {
|
||||
c.on('characteristicvaluechanged', (event)=>wind_updated(event));
|
||||
return c.startNotifications();
|
||||
}).then(function() {
|
||||
console.log("Done!");
|
||||
}).catch(function(e) {
|
||||
console.log("ERROR"+e);
|
||||
});}
|
||||
|
||||
function connection_setup() {
|
||||
NRF.setScan();
|
||||
|
@ -96,8 +104,10 @@ if (settings.truewind) {
|
|||
Bangle.on('GPS',function(fix) {
|
||||
if (fix.fix && fix.satellites>3 && fix.speed>2) { // only uses fixes w/ more than 3 sats and speed > 2kph
|
||||
gps_course =
|
||||
{ lon: Math.sin(Math.PI*fix.course/180)*fix.speed/1.852,
|
||||
lat: Math.cos(Math.PI*fix.course/180)*fix.speed/1.852,
|
||||
{ vlon: Math.sin(Math.PI*fix.course/180)*fix.speed/1.852,
|
||||
vlat: Math.cos(Math.PI*fix.course/180)*fix.speed/1.852,
|
||||
lat: fix.lat,
|
||||
lon: fix.lon,
|
||||
spd: fix.speed,
|
||||
course: fix.course
|
||||
};
|
||||
|
@ -107,6 +117,20 @@ if (settings.truewind) {
|
|||
Bangle.setGPSPower(1, "app");
|
||||
}
|
||||
|
||||
if (settings.truewind) {
|
||||
Bangle.on("swipe", (d)=>{
|
||||
if (d==-1 && !pause_gps) {
|
||||
pause_gps = true;
|
||||
Bangle.setGPSPower(0);
|
||||
draw_compass(0, 0, 0, 0);
|
||||
}
|
||||
else if (d==1 && pause_gps) {
|
||||
pause_gps = false;
|
||||
Bangle.setGPSPower(1, "app");
|
||||
draw_compass(0, 0, 0, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
draw_compass(0, 0, 0, 0);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "openwind",
|
||||
"name": "OpenWind",
|
||||
"shortName":"OpenWind",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "OpenWind",
|
||||
"icon": "openwind.png",
|
||||
"readme": "README.md",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"version": "0.02",
|
||||
"description": "Replace the built in menu function. Supports Bangle.js 1 and Bangle.js 2.",
|
||||
"icon": "icon.png",
|
||||
"type": "boot",
|
||||
"type": "bootloader",
|
||||
"tags": "system",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: Initial version
|
||||
0.02: Moved settings from launcher to settings->apps menu
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
require("heatshrink").decompress(atob("kMigILIgPAAYMD/ADBwcGhkAwM5wcA/+2//Av/Rn/giFoyFggkUrFggEKlAkCiApCx+AAYNGoADBkU4AYMQj4DBvEICANkAoIPBgE2B4MAiMAH4MAwECAYNALYUgBIISCHYMYAoQWBAIMEgAYBAIMBwEDDQNgDwUf/4eBg4DCAA4"))
|
|
@ -1,14 +1,15 @@
|
|||
{ "id": "quicklaunch",
|
||||
{
|
||||
"id": "quicklaunch",
|
||||
"name": "Quick Launch",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Tap or swipe left/right/up/down on your clock face to launch up to five apps of your choice.",
|
||||
"version":"0.02",
|
||||
"description": "Tap or swipe left/right/up/down on your clock face to launch up to five apps of your choice. Configurations can be accessed through Settings->Apps.",
|
||||
"type": "bootloader",
|
||||
"tags": "tools, system",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"quicklaunch.app.js","url":"app.js"},
|
||||
{"name":"quicklaunch.boot.js","url":"boot.js"},
|
||||
{"name":"quicklaunch.img","url":"app-icon.js","evaluate":true}
|
||||
{"name":"quicklaunch.settings.js","url":"settings.js"},
|
||||
{"name":"quicklaunch.boot.js","url":"boot.js"}
|
||||
],
|
||||
"data": [{"name":"quicklaunch.json"}]
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
(function(back) {
|
||||
var settings = Object.assign(require("Storage").readJSON("quicklaunch.json", true) || {});
|
||||
|
||||
var apps = require("Storage").list(/\.info$/).map(app=>{var a=require("Storage").readJSON(app,1);return a&&{name:a.name,type:a.type,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="launch" || app.type=="clock" || !app.type));
|
||||
|
@ -118,3 +119,4 @@ apps.forEach((a)=>{
|
|||
});
|
||||
|
||||
showMainMenu();
|
||||
});
|
|
@ -19,3 +19,4 @@
|
|||
0.13: Fix for when widget is used before app
|
||||
0.14: Remove unneeded variable assignment
|
||||
0.15: Show distance more accurately in conjunction with new locale app (fix #1523)
|
||||
0.16: Ability to append to existing track (fix #1712)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "recorder",
|
||||
"name": "Recorder",
|
||||
"shortName": "Recorder",
|
||||
"version": "0.15",
|
||||
"version": "0.16",
|
||||
"description": "Record GPS position, heart rate and more in the background, then download to your PC.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,outdoors,gps,widget",
|
||||
|
|
|
@ -142,7 +142,7 @@
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* eg. foobar.recorder.js
|
||||
(function(recorders) {
|
||||
recorders.foobar = {
|
||||
|
@ -193,7 +193,7 @@
|
|||
settings.record.forEach(r => {
|
||||
var recorder = recorders[r];
|
||||
if (!recorder) {
|
||||
console.log("Recorder for "+E.toJS(r)+"+not found");
|
||||
console.log(/*LANG*/"Recorder for "+E.toJS(r)+/*LANG*/"+not found");
|
||||
return;
|
||||
}
|
||||
var activeRecorder = recorder();
|
||||
|
@ -231,11 +231,11 @@
|
|||
},getRecorders:getRecorders,reload:function() {
|
||||
reload();
|
||||
Bangle.drawWidgets(); // relayout all widgets
|
||||
},setRecording:function(isOn) {
|
||||
},setRecording:function(isOn, forceAppend) {
|
||||
var settings = loadSettings();
|
||||
if (isOn && !settings.recording && !settings.file) {
|
||||
settings.file = "recorder.log0.csv";
|
||||
} else if (isOn && !settings.recording && require("Storage").list(settings.file).length){
|
||||
} else if (isOn && !forceAppend && !settings.recording && require("Storage").list(settings.file).length){
|
||||
var logfiles=require("Storage").list(/recorder.log.*/);
|
||||
var maxNumber=0;
|
||||
for (var c of logfiles){
|
||||
|
@ -246,18 +246,19 @@
|
|||
newFileName="recorder.log" + (maxNumber + 1) + ".csv";
|
||||
updateSettings(settings);
|
||||
}
|
||||
var buttons={Yes:"yes",No:"no"};
|
||||
if (newFileName) buttons["New"] = "new";
|
||||
return E.showPrompt("Overwrite\nLog " + settings.file.match(/\d+/)[0] + "?",{title:"Recorder",buttons:buttons}).then(selection=>{
|
||||
if (selection==="no") return false; // just cancel
|
||||
if (selection==="yes") {
|
||||
var buttons={/*LANG*/"Yes":"overwrite",/*LANG*/"No":"cancel"};
|
||||
if (newFileName) buttons[/*LANG*/"New"] = "new";
|
||||
buttons[/*LANG*/"Append"] = "append";
|
||||
return E.showPrompt(/*LANG*/"Overwrite\nLog " + settings.file.match(/\d+/)[0] + "?",{title:/*LANG*/"Recorder",buttons:buttons}).then(selection=>{
|
||||
if (selection==="cancel") return false; // just cancel
|
||||
if (selection==="overwrite")
|
||||
require("Storage").open(settings.file,"r").erase();
|
||||
}
|
||||
if (selection==="new"){
|
||||
settings.file = newFileName;
|
||||
updateSettings(settings);
|
||||
}
|
||||
return WIDGETS["recorder"].setRecording(1);
|
||||
// if (selection==="append") // we do nothing - all is fine
|
||||
return WIDGETS["recorder"].setRecording(1,true/*force append*/);
|
||||
});
|
||||
}
|
||||
settings.recording = isOn;
|
||||
|
|
|
@ -47,7 +47,7 @@ exports.getTimeToAlarm = function(alarm, time) {
|
|||
/// Force a reload of the current alarms and widget
|
||||
exports.reload = function() {
|
||||
eval(require("Storage").read("sched.boot.js"));
|
||||
if (WIDGETS["alarm"]) {
|
||||
if (global.WIDGETS && WIDGETS["alarm"]) {
|
||||
WIDGETS["alarm"].reload();
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,18 @@
|
|||
# SciCalc
|
||||
|
||||
Simple scientific calculator. I needed one, so I wrote a basic one, no design frills. Input expressions are slightly post processed and then evaluated
|
||||
by the JS interpreter.
|
||||
|
||||
## Usage
|
||||
|
||||
Buttons are arranged on 3 separate screens, swiping left or right switches between them. Swiping down has the same effect as hitting the "=" button.
|
||||
|
||||
## Features
|
||||
|
||||
The calculator supports the following operations:
|
||||
|
||||
* basic arithmetic: +, -, *, /, ^ (raise to a power), +/- (invert sign), 1/x (inverse), use of parentheses
|
||||
* trigonometric fucntions: sin, cos, tan, asin, acos, atan
|
||||
* exponential exp, natural logarithm log, pow function (this one takes 2 comma separated arguments)
|
||||
* Pi is provided as a constant
|
||||
* a memory button "M" stores or recalls the last result (after hitting the "=" button or swiping down)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AJioAaF1wwSFzowRCQUZo4AWjIvVFy4ABF/4vXyGQAYov/R+sZFy8ZF6oAcF/4vvi4AeF/4SCjseAAMdAx8MAAYvVEAQABAx4v/R/TvvF96PUg8cAAMHd9QuCAAIv/R+rvvF96Pvd94vvR97vvF96Pvd94vvR97vsGDwuQGDouSAH4A/AGwA=="))
|
|
@ -0,0 +1,113 @@
|
|||
const W = g.getWidth();
|
||||
const H = g.getHeight();
|
||||
|
||||
const dispH = H/5;
|
||||
const butH = H-dispH;
|
||||
|
||||
const buttons = [[['7', '8', '9'],
|
||||
['4', '5', '6'],
|
||||
['1', '2', '3'],
|
||||
['E', '0', '.']],
|
||||
[['<', 'M', 'C'],
|
||||
['+', '-', '*'],
|
||||
['/', '(', ')'],
|
||||
['^', ',', '=']],
|
||||
[['Sin', 'Cos', 'Tan'],
|
||||
['Asi', 'Aco', 'Ata'],
|
||||
['Pi', '1/x', '+/-'],
|
||||
['Log', 'Exp', 'Pow']
|
||||
]];
|
||||
|
||||
var curPage = 0;
|
||||
var inputStr = '';
|
||||
var memory = '';
|
||||
var qResult = false;
|
||||
|
||||
function drawPage (p) {
|
||||
g.clearRect(0, dispH, W-1, H-1);
|
||||
g.setFont('Vector', butH/5).setFontAlign(0, 0, 0).setColor(g.theme.fg);
|
||||
for (x=0; x<3; ++x)
|
||||
for (y=0; y<4; ++y)
|
||||
g.drawString(buttons[p][y][x], (x+0.5)*W/3, dispH+(y+0.7)*butH/4);
|
||||
g.setColor(0.5, 0.5, 0.5);
|
||||
for (x=1; x<3; ++x) g.drawLine(x*W/3, dispH+0.2*butH/4-2, x*W/3, H-1);
|
||||
for (y=1; y<4; ++y) g.drawLine(0, dispH+(y+0.2)*butH/4, W-1, dispH+(y+0.2)*butH/4);
|
||||
g.setColor(g.theme.fg).drawLine(0, dispH+0.2*butH/4-2, W-1, dispH+0.2*butH/4-2);
|
||||
}
|
||||
|
||||
function updateDisp(s, len) {
|
||||
var fh = butH/5;
|
||||
if (s.toString().length>len) s = s.toString().substr(0,len);
|
||||
g.setFont("Vector", butH/5).setColor(g.theme.fg).setFontAlign(1, 0, 0);
|
||||
while (g.stringWidth(s) > W-1) {
|
||||
fh /= 1.05;
|
||||
g.setFont("Vector", fh);
|
||||
}
|
||||
g.clearRect(0, 0, W-1, dispH-1).drawString(s, W-2, dispH/2);
|
||||
g.setColor(g.theme.fg).drawLine(0, dispH+0.2*butH/4-2, W-1, dispH+0.2*butH/4-2);
|
||||
}
|
||||
|
||||
function processInp (s) {
|
||||
var idx = s.indexOf("^");
|
||||
if (idx > 0) s = "Math.pow(" + s.slice(0,idx) + "," + s.slice(idx+1, s.length) + ")";
|
||||
['Sin', 'Cos', 'Tan', 'Asin', 'Acos', 'Atan', 'Log', 'Exp', 'Pow'].forEach((x) => {
|
||||
var i = s.indexOf(x);
|
||||
while (i>-1) {
|
||||
s = s.slice(0,i)+"Math."+s.slice(i,i+1).toLowerCase()+s.slice(i+1, s.length);
|
||||
i = s.indexOf(x, i+6);
|
||||
}
|
||||
});
|
||||
idx = s.indexOf('Pi');
|
||||
if (idx>-1) s = s.slice(0,idx) + "Math.PI" + s.slice(idx+2, s.length);
|
||||
idx = 0;
|
||||
s.split('').forEach((x)=>{ if (x=='(') idx++; if (x==')') idx-- });
|
||||
s += ')'.repeat(idx);
|
||||
return s;
|
||||
}
|
||||
|
||||
function compute() {
|
||||
var res;
|
||||
console.log(processInp(inputStr));
|
||||
try { res = eval(processInp(inputStr)); }
|
||||
catch(e) { res = "error"; }
|
||||
inputStr = res;
|
||||
qResult = true;
|
||||
updateDisp(inputStr, 19);
|
||||
}
|
||||
|
||||
function touchHandler(e, d) {
|
||||
var x = Math.floor(d.x/(W/3));
|
||||
var y = Math.floor((d.y-dispH-0.2*butH/4)/(butH/4));
|
||||
var c = buttons[curPage][y][x];
|
||||
if (c=="=") { // do the computation
|
||||
compute();
|
||||
return;
|
||||
}
|
||||
else if (c=="<" && inputStr.length>0) inputStr = inputStr.slice(0, -1); // delete last character
|
||||
else if (c=='M' && qResult) memory = inputStr;
|
||||
else if (c=='M') inputStr += memory;
|
||||
else if (c=="C") inputStr = ''; // clear
|
||||
else {
|
||||
if ("Sin Cos Tan Log Exp Pow".indexOf(c)>-1 && c!='E') c += "(";
|
||||
if ("Asi Aco Ata".indexOf(c)>-1) c += "n(";
|
||||
if (c=='1/x') { inputStr = "1/("+inputStr+")"; compute(); return; }
|
||||
if (c=='+/-') { inputStr = "-("+inputStr+")"; compute(); return; }
|
||||
if (qResult && "+-*/^".indexOf(c)==-1) inputStr = c + inputStr + ")";
|
||||
else inputStr += c;
|
||||
}
|
||||
qResult = false;
|
||||
updateDisp(inputStr, 32);
|
||||
}
|
||||
|
||||
function swipeHandler(e,d) {
|
||||
curPage -= e;
|
||||
if (curPage>buttons.length-1) curPage = 0;
|
||||
if (curPage<0) curPage = buttons.length-1;
|
||||
drawPage(curPage);
|
||||
if (d==1) compute();
|
||||
}
|
||||
|
||||
Bangle.on("touch", touchHandler);
|
||||
Bangle.on("swipe", swipeHandler);
|
||||
g.clear();
|
||||
drawPage(curPage);
|
|
@ -0,0 +1,15 @@
|
|||
{ "id": "scicalc",
|
||||
"name": "Scientific Calculator",
|
||||
"shortName":"SciCalc",
|
||||
"version":"0.01",
|
||||
"description": "Scientific calculator",
|
||||
"icon": "scicalc.png",
|
||||
"readme": "README.md",
|
||||
"tags": "app,tool",
|
||||
"allow_emulator": true,
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"scicalc.app.js","url":"app.js"},
|
||||
{"name":"scicalc.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 562 B |
|
@ -45,3 +45,4 @@
|
|||
0.40: Moved off into Utils, put System after Apps
|
||||
0.41: Stop users disabling all wake-up methods and locking themselves out (fix #1272)
|
||||
0.42: Fix theme customizer on new Bangle 2 firmware
|
||||
0.43: Add some Bangle 1 colours to theme customizer
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.42",
|
||||
"version": "0.43",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
|
|
@ -251,11 +251,15 @@ function showThemeMenu() {
|
|||
}
|
||||
upd(th);
|
||||
}
|
||||
const rgb = {
|
||||
let rgb = {
|
||||
black: "#000", white: "#fff",
|
||||
red: "#f00", green: "#0f0", blue: "#00f",
|
||||
cyan: "#0ff", magenta: "#f0f", yellow: "#ff0",
|
||||
};
|
||||
if (!BANGLEJS2) Object.assign(rgb, {
|
||||
// these would cause dithering, which is not great for e.g. text
|
||||
orange: "#ff7f00", purple: "#7f00ff", grey: "#7f7f7f",
|
||||
});
|
||||
let colors = [], names = [];
|
||||
for(const c in rgb) {
|
||||
names.push(c);
|
||||
|
|
|
@ -3,3 +3,5 @@
|
|||
0.03: Add compatibility for Bangle.js 2 and new firmware, added "Alarm at " for the alarm time
|
||||
0.04: Read alarms from new scheduling library, account for higher acceleration sensor noise on Bangle.js 2
|
||||
0.05: Refactor decodeTime() to scheduling library
|
||||
0.06: Add logging
|
||||
use Layout library and display ETA
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# Sleep Phase Alarm
|
||||
|
||||
The display shows:
|
||||
|
||||
- the current time
|
||||
- time of the next alarm or timer
|
||||
- time difference between current time and alarm time (ETA)
|
||||
- current state of the ESS algorithm, "Sleep" or "Awake", useful for debugging
|
||||
|
||||
## Logging
|
||||
|
||||
For each day of month (1..31) the ESS states are logged. An entry will be overwritten in the next month, e.g. an entry on the 4th May will overwrite an entry on the 4th April.
|
||||
The logs can be viewed with the download button:
|
||||
|
||||

|
|
@ -1,6 +1,10 @@
|
|||
const BANGLEJS2 = process.env.HWVERSION == 2; //# check for bangle 2
|
||||
const alarms = require("Storage").readJSON("sched.json",1)||[];
|
||||
const BANGLEJS2 = process.env.HWVERSION == 2; // check for bangle 2
|
||||
const Layout = require("Layout");
|
||||
const locale = require('locale');
|
||||
const alarms = require("Storage").readJSON("sched.json",1) || [];
|
||||
const config = require("Storage").readJSON("sleepphasealarm.json",1) || {logs: []};
|
||||
const active = alarms.filter(a=>a.on);
|
||||
let logs = [];
|
||||
|
||||
// Sleep/Wake detection with Estimation of Stationary Sleep-segments (ESS):
|
||||
// Marko Borazio, Eugen Berlin, Nagihan Kücükyildiz, Philipp M. Scholl and Kristof Van Laerhoven, "Towards a Benchmark for Wearable Sleep Analysis with Inertial Wrist-worn Sensing Units", ICHI 2014, Verona, Italy, IEEE Press, 2014.
|
||||
|
@ -52,53 +56,45 @@ active.forEach(alarm => {
|
|||
}
|
||||
});
|
||||
|
||||
function drawString(s, y) { //# replaced x: always centered
|
||||
g.reset(); //# moved up to prevent blue background
|
||||
g.clearRect(0, y - 12, 239, y + 8); //# minimized upper+lower clearing
|
||||
g.setFont("Vector", 20);
|
||||
g.setFontAlign(0, 0); // align centered
|
||||
g.drawString(s, g.getWidth() / 2, y); //# set x to center
|
||||
}
|
||||
var layout = new Layout({
|
||||
type:"v", c: [
|
||||
{type:"txt", font:"10%", label:"Sleep Phase Alarm", bgCol:g.theme.bgH, fillx: true, height:Bangle.appRect.h/6},
|
||||
{type:"txt", font:"16%", label: ' '.repeat(20), id:"date", height:Bangle.appRect.h/6},
|
||||
{type:"txt", font:"12%", label: "", id:"alarm_date", height:Bangle.appRect.h/6},
|
||||
{type:"txt", font:"10%", label: ' '.repeat(20), id:"eta", height:Bangle.appRect.h/6},
|
||||
{type:"txt", font:"12%", label: ' '.repeat(20), id:"state", height:Bangle.appRect.h/6},
|
||||
]
|
||||
}, {lazy:true});
|
||||
|
||||
function drawApp() {
|
||||
g.clearRect(0,24,239,215); //# no problem
|
||||
var alarmHour = nextAlarm.getHours();
|
||||
var alarmMinute = nextAlarm.getMinutes();
|
||||
if (alarmHour < 10) alarmHour = "0" + alarmHour;
|
||||
if (alarmMinute < 10) alarmMinute = "0" + alarmMinute;
|
||||
const s = "Alarm at " + alarmHour + ":" + alarmMinute + "\n\n"; //# make distinct to time
|
||||
E.showMessage(s, "Sleep Phase Alarm");
|
||||
layout.alarm_date.label = "Alarm at " + alarmHour + ":" + alarmMinute;
|
||||
layout.render();
|
||||
|
||||
function drawTime() {
|
||||
if (Bangle.isLCDOn()) {
|
||||
const now = new Date();
|
||||
var nowHour = now.getHours();
|
||||
var nowMinute = now.getMinutes();
|
||||
var nowSecond = now.getSeconds();
|
||||
if (nowHour < 10) nowHour = "0" + nowHour;
|
||||
if (nowMinute < 10) nowMinute = "0" + nowMinute;
|
||||
if (nowSecond < 10) nowSecond = "0" + nowSecond;
|
||||
const time = nowHour + ":" + nowMinute + (BANGLEJS2 ? "" : ":" + nowSecond); //# hide seconds on bangle 2
|
||||
drawString(time, BANGLEJS2 ? 85 : 105); //# remove x, adjust height for bangle 2 an newer firmware
|
||||
layout.date.label = locale.time(now, BANGLEJS2 && Bangle.isLocked() ? 1 : 0); // hide seconds on bangle 2
|
||||
const diff = nextAlarm - now;
|
||||
const diffHour = Math.floor((diff % 86400000) / 3600000).toString();
|
||||
const diffMinutes = Math.round(((diff % 86400000) % 3600000) / 60000).toString();
|
||||
layout.eta.label = "ETA: -"+ diffHour + ":" + diffMinutes.padStart(2, '0');
|
||||
layout.render();
|
||||
}
|
||||
}
|
||||
|
||||
if (BANGLEJS2) {
|
||||
drawTime();
|
||||
setTimeout(_ => {
|
||||
drawTime();
|
||||
setInterval(drawTime, 60000);
|
||||
}, 60000 - Date.now() % 60000); //# every new minute on bangle 2
|
||||
} else {
|
||||
setInterval(drawTime, 500); // 2Hz
|
||||
}
|
||||
drawTime();
|
||||
setInterval(drawTime, 500); // 2Hz
|
||||
}
|
||||
|
||||
var buzzCount = 19;
|
||||
function buzz() {
|
||||
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
|
||||
Bangle.setLCDPower(1);
|
||||
Bangle.buzz().then(()=>{
|
||||
Bangle.setLCDPower(1);
|
||||
Bangle.buzz().then(()=>{
|
||||
if (buzzCount--) {
|
||||
setTimeout(buzz, 500);
|
||||
} else {
|
||||
|
@ -108,12 +104,21 @@ function buzz() {
|
|||
});
|
||||
}
|
||||
|
||||
function addLog(time, type) {
|
||||
logs.push({time: time, type: type});
|
||||
require("Storage").writeJSON("sleepphasealarm.json", config);
|
||||
}
|
||||
|
||||
// run
|
||||
var minAlarm = new Date();
|
||||
var measure = true;
|
||||
if (nextAlarm !== undefined) {
|
||||
Bangle.loadWidgets(); //# correct widget load draw order
|
||||
config.logs[nextAlarm.getDate()] = []; // overwrite log on each day of month
|
||||
logs = config.logs[nextAlarm.getDate()];
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
let swest_last;
|
||||
|
||||
// minimum alert 30 minutes early
|
||||
minAlarm.setTime(nextAlarm.getTime() - (30*60*1000));
|
||||
|
@ -124,14 +129,26 @@ if (nextAlarm !== undefined) {
|
|||
|
||||
if (swest !== undefined) {
|
||||
if (Bangle.isLCDOn()) {
|
||||
drawString(swest ? "Sleep" : "Awake", BANGLEJS2 ? 150 : 180); //# remove x, adjust height
|
||||
layout.state.label = swest ? "Sleep" : "Awake";
|
||||
layout.render();
|
||||
}
|
||||
// log
|
||||
if (swest_last != swest) {
|
||||
if (swest) {
|
||||
addLog(new Date(now - sleepthresh*13/12.5*1000), "sleep"); // calculate begin of no motion phase, 13 values/second at 12.5Hz
|
||||
} else {
|
||||
addLog(now, "awake");
|
||||
}
|
||||
swest_last = swest;
|
||||
}
|
||||
}
|
||||
|
||||
if (now >= nextAlarm) {
|
||||
// The alarm widget should handle this one
|
||||
addLog(now, "alarm");
|
||||
setTimeout(load, 1000);
|
||||
} else if (measure && now >= minAlarm && swest === false) {
|
||||
addLog(now, "alarm");
|
||||
buzz();
|
||||
measure = false;
|
||||
}
|
||||
|
@ -141,6 +158,4 @@ if (nextAlarm !== undefined) {
|
|||
E.showMessage('No Alarm');
|
||||
setTimeout(load, 1000);
|
||||
}
|
||||
// BTN2 to menu, BTN3 to main # on bangle 2 only BTN to main
|
||||
if (!BANGLEJS2) setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
||||
setWatch(() => load(), BANGLEJS2 ? BTN : BTN3, { repeat: false, edge: "falling" });
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/chart.js@2.8.0/dist/Chart.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<p>Please select a wakeup day:</p>
|
||||
<div class="form-group">
|
||||
<select id="day" disabled class="form-select">
|
||||
<option selected disabled>No day</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="sleepChart"></canvas>
|
||||
</div>
|
||||
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.1/dist/chart.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@2.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
|
||||
<script>
|
||||
function getData() {
|
||||
const select = document.getElementById("day");
|
||||
const ctx = document.getElementById('sleepChart').getContext('2d');
|
||||
const yTicks = ["sleep", "awake", "alarm"];
|
||||
|
||||
// show loading window
|
||||
Util.showModal("Loading...");
|
||||
// get the data
|
||||
Util.readStorage('sleepphasealarm.json',data=>{
|
||||
let logs = JSON.parse(data || "{}")?.logs || [];
|
||||
// remove window
|
||||
Util.hideModal();
|
||||
|
||||
logs = logs.filter(log => log != null);
|
||||
logs.sort(function(a, b) {return new Date(b?.filter(entry => entry.type === "alarm")[0]?.time) - new Date(a?.filter(entry => entry.type === "alarm")[0]?.time)}); // sort by alarm date desc
|
||||
logs.forEach((log, i) => {
|
||||
const timeStr = log.filter(entry => entry.type === "alarm")[0]?.time;
|
||||
if (timeStr) {
|
||||
const date = new Date(timeStr);
|
||||
let option = document.createElement("option");
|
||||
option.text = date.toLocaleDateString();
|
||||
option.value = i;
|
||||
select.add(option);
|
||||
select.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
labels: [],
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
label: "No date selected",
|
||||
data: [],
|
||||
fill: false,
|
||||
stepped: true,
|
||||
borderColor: '#ff0000',
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
tooltipFormat: 'HH:mm',
|
||||
displayFormats: {
|
||||
millisecond: 'HH:mm:ss.SSS',
|
||||
second: 'HH:mm:ss',
|
||||
minute: 'HH:mm',
|
||||
hour: 'HH',
|
||||
day: 'D MMM.',
|
||||
},
|
||||
},
|
||||
},
|
||||
y: {ticks: {callback: (value, index, values) => yTicks[value]}},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
return yTicks[context.raw];
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
select.onchange = () => {
|
||||
const log = logs[select.value];
|
||||
chart.data.labels = log.map(entry => new Date(entry.time));
|
||||
chart.data.datasets[0].data = log.map(entry => yTicks.indexOf(entry.type));
|
||||
const timeStr = log.filter(entry => entry.type === "alarm")[0]?.time;
|
||||
chart.data.datasets[0].label = new Date(timeStr).toLocaleDateString();
|
||||
chart.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Called when app starts
|
||||
function onInit() {
|
||||
getData();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -2,14 +2,17 @@
|
|||
"id": "sleepphasealarm",
|
||||
"name": "SleepPhaseAlarm",
|
||||
"shortName": "SleepPhaseAlarm",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.",
|
||||
"icon": "app.png",
|
||||
"tags": "alarm",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"dependencies": {"scheduler":"type"},
|
||||
"storage": [
|
||||
{"name":"sleepphasealarm.app.js","url":"app.js"},
|
||||
{"name":"sleepphasealarm.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
],
|
||||
"data": [{"name":"sleepphasealarm.json","storageFile":true}],
|
||||
"interface": "interface.html"
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 45 KiB |
|
@ -1 +1 @@
|
|||
1.0: Initial release on the app repository for Bangle.js 1 and 2
|
||||
0.01: Initial release on the app repository for Bangle.js 1 and 2
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name":"Stardate Clock",
|
||||
"shortName":"Stardate Clock",
|
||||
"description": "A clock displaying a stardate along with a 'standard' digital/analog clock in LCARS design",
|
||||
"version":"1.0",
|
||||
"version":"0.01",
|
||||
"icon": "app.png",
|
||||
"type":"clock",
|
||||
"tags": "clock",
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: first release
|
||||
0.02: Adjust for touch events outside of screen g dimensions
|
||||
0.03: Do not register as watch, manually start clock on button
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "stopwatch",
|
||||
"name": "Stopwatch Touch",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "A touch based stop watch for Bangle JS 2",
|
||||
"icon": "stopwatch.png",
|
||||
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}],
|
||||
|
|
|
@ -227,4 +227,4 @@ g.fillRect(0,0,w,h);
|
|||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
||||
Bangle.setUI("clock"); // Show launcher when button pressed
|
||||
setWatch(() => load(), BTN, { repeat: false, edge: "falling" });
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"version": "0.01",
|
||||
"description": "Let's you swipe from left to right on any app to return back to the clock face. Please configure in the settings app after installing to activate, since its disabled by default.",
|
||||
"icon": "app.png",
|
||||
"type": "boot",
|
||||
"type": "bootloader",
|
||||
"tags": "tools",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: Initial version
|
|
@ -0,0 +1,7 @@
|
|||
# Tap Keyboard
|
||||
This was originally designed for the Noteify app. With the new keyboard system in place, it has become its own keyboard app.
|
||||
|
||||
## Usage
|
||||
* Swipe left or right to cycle between the alphabet, numerals, and symbols.
|
||||
* Hitting "caps" once will capitalize one character only. Hitting "caps" twice activates caps lock, and all subsequent characters will be capitalized until you hit "caps" again.
|
||||
* "New line" is represented by a pilcrow (¶). When you hit the back button, these symbols will be converted into newline.
|
|
@ -0,0 +1 @@
|
|||
atob("MDCBAf////////////////AAAAAAD8AAAAAAA4AAAAAAAYAAAAAAAQf/////4A//////8A//////8A//////8A//////8A//////8A///8P/8A///8P/8A///8P/8A///8P/8A///8P/8A///8P/8A///8P//w///8P//w///8MP/w///8MP/w///8MMPw///8MMPw///8AMMA///8AAMA///8AAAA///8AAAA///8OAAA///8P+AA//wcP/8Af/AEP/8IA+AAP/8IA+AAP/8MA+DAP/8PA+BgP/8P//B4P/8P//g8P/8P//wfP/8P//4Pv/8P//8H//8P//8D//8P//+D//8P///B//4P///gAAAf///wAAAf///4AAA////8AADw==")
|
After Width: | Height: | Size: 678 B |
|
@ -0,0 +1,162 @@
|
|||
exports.input = function(options) {
|
||||
options = options||{};
|
||||
var text = options.text;
|
||||
if ("string"!=typeof text) text="";
|
||||
|
||||
var layer = 0;
|
||||
var caps = 0;
|
||||
|
||||
class keyPad {
|
||||
constructor(x1, y1, x2, y2, func) {
|
||||
this.x1 = x1;
|
||||
this.y1 = y1;
|
||||
this.x2 = x2;
|
||||
this.y2 = y2;
|
||||
this.func = !func ? "" : func;
|
||||
}
|
||||
|
||||
draw() {
|
||||
g.setColor(g.theme.fg).drawRect(this.x1, this.y1, this.x2, this.y2).clearRect(this.x1+1, this.y1+1, this.x2-1, this.y2-1).setFont("6x8",2).setFontAlign(0, 0, 0).drawString(this.func, (((this.x2-this.x1)/2)+this.x1), (((this.y2-this.y1)/2)+this.y1));
|
||||
}
|
||||
|
||||
onTouch(xy) {
|
||||
if (this.func == "space") text += " ";
|
||||
else if (this.func == "<-") text = text.slice(0, -1);
|
||||
else if (this.func == "new\nline") text += String.fromCharCode(182);
|
||||
else if (this.func == "caps") {
|
||||
caps = 1;
|
||||
renderKeys();
|
||||
}
|
||||
else if (this.func == "Caps") {
|
||||
caps = 2;
|
||||
renderKeys();
|
||||
}
|
||||
else if (this.func == "CAPS") {
|
||||
caps = 0;
|
||||
renderKeys();
|
||||
}
|
||||
else {
|
||||
text += this.func;
|
||||
if (caps == 1) caps = 0;
|
||||
}
|
||||
g.clearRect(25, 0, g.getWidth(), 25).setFontAlign(-1, -1).drawString(text.substring(text.length-12, text.length)+"_", 25, 7);
|
||||
}
|
||||
}
|
||||
|
||||
function renderKeys() {
|
||||
var a;
|
||||
var i;
|
||||
if (layer == 0) {
|
||||
if (caps == 0) {
|
||||
a = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "caps", "space", "<-"];
|
||||
}
|
||||
else a = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "Caps", "space", "<-"];
|
||||
if (caps == 2) a[9] = "CAPS";
|
||||
for (i = 0; i < a.length; i++) {
|
||||
pad[i].func = a[i];
|
||||
}
|
||||
}
|
||||
else if (layer == 1) {
|
||||
if (caps == 0) {
|
||||
a = ["j", "k", "l", "m", "n", "o", "p", "q", "r", "caps", "space", "<-"];
|
||||
}
|
||||
else a = ["J", "K", "L", "M", "N", "O", "P", "Q", "R", "Caps", "space", "<-"];
|
||||
if (caps == 2) a[9] = "CAPS";
|
||||
for (i = 0; i < a.length; i++) {
|
||||
pad[i].func = a[i];
|
||||
}
|
||||
}
|
||||
else if (layer == 2) {
|
||||
if (caps == 0) {
|
||||
a = ["s", "t", "u", "v", "w", "x", "y", "z", "0", "caps", "space", "<-"];
|
||||
}
|
||||
else a = ["S", "T", "U", "V", "W", "X", "Y", "Z", "0", "Caps", "space", "<-"];
|
||||
if (caps == 2) a[9] = "CAPS";
|
||||
for (i = 0; i < a.length; i++) {
|
||||
pad[i].func = a[i];
|
||||
}
|
||||
}
|
||||
else if (layer == 3) {
|
||||
if (caps == 0) {
|
||||
a = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "caps", "space", "<-"];
|
||||
}
|
||||
else a = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "Caps", "space", "<-"];
|
||||
if (caps == 2) a[9] = "CAPS";
|
||||
for (i = 0; i < a.length; i++) {
|
||||
pad[i].func = a[i];
|
||||
}
|
||||
}
|
||||
else if (layer == 4) {
|
||||
if (caps == 0) {
|
||||
a = [".", ",", "?", "!", "(", ")", "-", "\'", "new\nline", "caps", "space", "<-"];
|
||||
}
|
||||
else a = ["-", "+", "/", "*", ":", "#", "$", "%", "new\nline", "Caps", "space", "<-"];
|
||||
if (caps == 2) a[9] = "CAPS";
|
||||
for (i = 0; i < a.length; i++) {
|
||||
pad[i].func = a[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0; j < pad.length; j++) {
|
||||
pad[j].draw();
|
||||
}
|
||||
}
|
||||
|
||||
var pad = [];
|
||||
pad[0] = new keyPad(0, 29, 57, 64);
|
||||
pad[1] = new keyPad(59, 29, 116, 64);
|
||||
pad[2] = new keyPad(118, 29, 175, 64);
|
||||
pad[3] = new keyPad(0, 66, 57, 101);
|
||||
pad[4] = new keyPad(59, 66, 116, 101);
|
||||
pad[5] = new keyPad(118, 66, 175, 101);
|
||||
pad[6] = new keyPad(0, 103, 57, 138);
|
||||
pad[7] = new keyPad(59, 103, 116, 138);
|
||||
pad[8] = new keyPad(118, 103, 175, 138);
|
||||
pad[9] = new keyPad(0, 140, 57, 175);
|
||||
pad[10] = new keyPad(59, 140, 116, 175);
|
||||
pad[11] = new keyPad(118, 140, 175, 175);
|
||||
g.clear();
|
||||
renderKeys();
|
||||
|
||||
var drag;
|
||||
var e;
|
||||
|
||||
return new Promise((resolve,reject) => {
|
||||
|
||||
Bangle.setUI({mode:"custom", 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;
|
||||
//horizontal swipes
|
||||
if (Math.abs(dx)>Math.abs(dy)+10) {
|
||||
//swipe left
|
||||
if (dx<0) layer == 4 ? layer = 0 : layer++;
|
||||
//swipe right
|
||||
if (dx>0) layer == 0 ? layer = 4 : layer--;
|
||||
}
|
||||
}
|
||||
renderKeys();
|
||||
},touch:(button, xy)=>{
|
||||
for (var i = 0; i < pad.length; i++) {
|
||||
if ((xy.x >= pad[i].x1) && (xy.x <= pad[i].x2) && (xy.y >= pad[i].y1) && (xy.y <= pad[i].y2)) {
|
||||
pad[i].onTouch(xy);
|
||||
i = pad.length;
|
||||
}
|
||||
}
|
||||
},back:()=>{
|
||||
Bangle.setUI();
|
||||
g.clear();
|
||||
resolve(text.replace(new RegExp(String.fromCharCode(182), 'g'), '\n'));
|
||||
}});
|
||||
g.clearRect(25, 0, g.getWidth(), 25).setColor(g.theme.fg).setFont("6x8", 2);
|
||||
if (text == "") g.setFontAlign(0, -1).drawString("<-Swipe->", g.getWidth()/2, 7);
|
||||
else {
|
||||
text = text.replace(/\n/g, String.fromCharCode(182));
|
||||
g.setFontAlign(-1, -1).drawString(text.substring(text.length-12, text.length)+"_", 25, 7);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "tapkb",
|
||||
"name": "Tap keyboard",
|
||||
"version":"0.01",
|
||||
"description": "An onscreen tap keyboard.",
|
||||
"icon": "app.png",
|
||||
"type":"textinput",
|
||||
"tags": "keyboard",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"storage": [
|
||||
{"name":"textinput","url":"lib.js"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Rename "Activity" in "Motion" and display the true values for it
|
||||
0.03: Add Banglejs 1 compatibility
|
||||
0.04: Fix settings bug
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Terminal Clock",
|
||||
"shortName":"Terminal Clock",
|
||||
"description": "A terminal cli like clock displaying multiple sensor data",
|
||||
"version":"0.03",
|
||||
"version":"0.04",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"" : { "title" : "Terminal Clock" },
|
||||
"< Back" : () => back(),
|
||||
'HR confidence': {
|
||||
value: 50|settings.HRMinConfidence, // 0| converts undefined to 0
|
||||
value: settings.HRMinConfidence,
|
||||
min: 0, max: 100,
|
||||
onchange: v => {
|
||||
settings.HRMinConfidence = v;
|
||||
|
@ -26,7 +26,7 @@
|
|||
}
|
||||
},
|
||||
'Show date': {
|
||||
value: !!settings.showDate,
|
||||
value: settings.showDate,
|
||||
format: v => v?"Yes":"No",
|
||||
onchange: v => {
|
||||
settings.showDate = v;
|
||||
|
@ -34,7 +34,7 @@
|
|||
}
|
||||
},
|
||||
'Show HRM': {
|
||||
value: !!settings.showHRM,
|
||||
value: settings.showHRM,
|
||||
format: v => v?"Yes":"No",
|
||||
onchange: v => {
|
||||
settings.showHRM = v;
|
||||
|
@ -42,7 +42,7 @@
|
|||
}
|
||||
},
|
||||
'Show Activity': {
|
||||
value: !!settings.showActivity,
|
||||
value: settings.showActivity,
|
||||
format: v => v?"Yes":"No",
|
||||
onchange: v => {
|
||||
settings.showActivity = v;
|
||||
|
@ -50,7 +50,7 @@
|
|||
}
|
||||
},
|
||||
'Show Steps': {
|
||||
value: !!settings.showStepCount,
|
||||
value: settings.showStepCount,
|
||||
format: v => v?"Yes":"No",
|
||||
onchange: v => {
|
||||
settings.showStepCount = v;
|
||||
|
|
|
@ -65,6 +65,7 @@ const APP_KEYS = [
|
|||
const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite', 'supports'];
|
||||
const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate'];
|
||||
const SUPPORTS_DEVICES = ["BANGLEJS","BANGLEJS2"]; // device IDs allowed for 'supports'
|
||||
const METADATA_TYPES = ["app","clock","widget","bootloader","RAM","launch","textinput","scheduler","notify","locale"]; // values allowed for "type" field
|
||||
const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info
|
||||
const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ];
|
||||
const GRANDFATHERED_ICONS = ["s7clk", "snek", "astral", "alpinenav", "slomoclock", "arrow", "pebble", "rebble"];
|
||||
|
@ -94,6 +95,8 @@ apps.forEach((app,appIdx) => {
|
|||
if (!app.name) ERROR(`App ${app.id} has no name`);
|
||||
var isApp = !app.type || app.type=="app";
|
||||
if (app.name.length>20 && !app.shortName && isApp) ERROR(`App ${app.id} has a long name, but no shortName`);
|
||||
if (app.type && !METADATA_TYPES.includes(app.type))
|
||||
ERROR(`App ${app.id} 'type' is one one of `+METADATA_TYPES);
|
||||
if (!Array.isArray(app.supports)) ERROR(`App ${app.id} has no 'supports' field or it's not an array`);
|
||||
else {
|
||||
app.supports.forEach(dev => {
|
||||
|
@ -135,6 +138,9 @@ apps.forEach((app,appIdx) => {
|
|||
Object.keys(app.dependencies).forEach(dependency => {
|
||||
if (!["type","app"].includes(app.dependencies[dependency]))
|
||||
ERROR(`App ${app.id} 'dependencies' must all be tagged 'type' or 'app' right now`);
|
||||
if (app.dependencies[dependency]=="type" && !METADATA_TYPES.includes(dependency))
|
||||
ERROR(`App ${app.id} 'type' dependency must be one of `+METADATA_TYPES);
|
||||
|
||||
});
|
||||
} else
|
||||
ERROR(`App ${app.id} 'dependencies' must be an object`);
|
||||
|
|
2
core
|
@ -1 +1 @@
|
|||
Subproject commit 24730dc0bac75ea9eff0e2632e530a6ff7652b19
|
||||
Subproject commit 6fc78fc39531a43148ae8d515efaeff9404d1daf
|
|
@ -19,7 +19,7 @@
|
|||
}
|
||||
|
||||
.tile.column.col-6.col-sm-12.col-xs-12.app-tile {
|
||||
border: solid 1px #fafafa;
|
||||
border: solid 1px #dadee4;
|
||||
margin: 0;
|
||||
min-height: 150px;
|
||||
padding-top: 0.5rem;
|
||||
|
|