Merge branch 'master' of github.com:nxdefiant/BangleApps
|
@ -74,7 +74,7 @@ try and keep filenames short to avoid overflowing the buffer.
|
|||
|
||||
### Screenshots
|
||||
|
||||
In the app `metadata.json` file you can add a list of screenshots with a line like: `"screenshots" : [ { url:"screenshot.png" } ],`
|
||||
In the app `metadata.json` file you can add a list of screenshots with a line like: `"screenshots" : [ { "url":"screenshot.png" } ],`
|
||||
|
||||
To get a screenshot you can:
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -20,4 +20,7 @@
|
|||
0.19: Ensure rescheduled alarms that already fired have 'last' reset
|
||||
0.20: Use the new 'sched' factories to initialize new alarms/timers
|
||||
0.21: Fix time reset after a day of week change (#1676)
|
||||
0.22: Refactor some methods to scheduling library
|
||||
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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -52,6 +52,17 @@ function showMainMenu() {
|
|||
}
|
||||
};
|
||||
});
|
||||
|
||||
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 +92,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
|
||||
|
@ -104,7 +118,7 @@ function editAlarm(alarmIndex, alarm) {
|
|||
value: "SMTWTFS".split("").map((d,n)=>a.dow&(1<<n)?d:".").join(""),
|
||||
onchange: () => editDOW(a.dow, d => {
|
||||
a.dow = d;
|
||||
a.t = encodeTime(t);
|
||||
a.t = require("sched").encodeTime(t);
|
||||
editAlarm(alarmIndex, a);
|
||||
})
|
||||
},
|
||||
|
@ -115,24 +129,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 +165,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 +184,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 +197,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.22",
|
||||
"version": "0.24",
|
||||
"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 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,32 @@
|
|||
# Bluetooth Multimeter
|
||||
|
||||
Connect to compatible a Bluetooth Multimeters and display the result on your wrist!
|
||||
|
||||
## Compatible Bluetooth meters
|
||||
|
||||
Only the OWON is supported right now - feel free to add support for more!
|
||||
|
||||
### OWON OW18E
|
||||
|
||||
Available [on Amazon](https://www.amazon.co.uk/Bluetooth-Multimeter-Multimeters-Voltmeter-Resistance/dp/B08NJT38SF/ref=sr_1_1)
|
||||
|
||||
Turn the meter on, and long-press the Hz/Duty/Delta/Bluetooth button on the right hand side. Now run the app.
|
||||
|
||||
## Usage
|
||||
|
||||
The app currently only displays the current reading from the volt meter.
|
||||
|
||||
If the app fails to connect you'll need to reload it to reconnect.
|
||||
|
||||
To exit the app, long-press the button.
|
||||
|
||||
|
||||
## Future functionality...
|
||||
|
||||
* Logging
|
||||
* Graphs
|
||||
* More than one meter
|
||||
|
||||
## Creator
|
||||
|
||||
Gordon Williams (please file issues via GitHub)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4kA///z3vy067fWlP7/t1r3f33vrU07tdxHKn1mnUEv92DgO75xUwmcziIAIiAWJgYXLiIuLC5dwC6xIJCwP/swAHsIXM5GZAA/HC4kCC42I7oAG3OXC4sgC4/e9v+939AQP7C40iC429C4Pu/wCC9YXHGAYXCzoXC3wCCC5AwDC4WZC4M+909AQM7C5AwCC4ef/tfn/dAQQXIDAQXDAA4XHJIYXBzNVAA9XC4K8CR4QwCC4Mzi93AA1xC4JBCC4QwCC4URABIXBCgaqDC5MWC4YRCI4hfC1VEmMX93u8MR/Pd6J3CCQJ3DR4YXF84XBR4ilDbAYXFagM3iP1R4YQBCwbXER4KHBRYaWBR4TWFYoQXCsKPKCIQuEC452BC6MxiPju8xiYZEI5gXBiLDHO5k+FgKSBCYM+C4inJAAMWmlEolDi8TC4jXJAAQWBAANBUoIXCCQQCBDgQXCn0z+gXDokRjwXCCIQXCDoQuHAAJlBC4ZBBC4kiC4ekC4lBI4ioDYQYXD1QXJXIR3DL4fjn4XBGIdBbQQXFYAYvE0gXEF5DXGC4IYBC4WhC5IFCC4dKC4QDB+KPDC4guCX4n6GAIXCmK/CuAXEAgQvEPAIXC14JDmAXDFwYXEeAKpCBAgXECwYXFeQVDC5AAFC400AwoXQAAwXJgYXWGBoWJAF4A=="))
|
|
@ -0,0 +1,105 @@
|
|||
var decoded;
|
||||
var gatt;
|
||||
|
||||
|
||||
function decode(d) {
|
||||
var value = d.getUint16(4,1);
|
||||
if (value&32768)
|
||||
value = -(value&32767);
|
||||
var flags = d.getUint8(0);
|
||||
var flags2 = d.getUint8(1);
|
||||
// mv dc 27,240 "11xxx"
|
||||
// mv ac 95,240 "1011xxx"
|
||||
// v dc 36,240 "100xxx" 36(2dp) 35(20dp)
|
||||
// v ac 100,240 "1100xxx" 100(2dp) 99(20dp) 97(2000dp)
|
||||
// ohms 55,241 "110xxx"
|
||||
// beep 231,242 "11100xxx"
|
||||
// diode 167,242 "10100xxx"
|
||||
// capac 76,241 "1001xxx"
|
||||
// hz 162,241 "10100xxx"
|
||||
// temp 33,242 "100xxx"
|
||||
// ncv 96,243 "1100xxx"
|
||||
// uA 146,240 "10010xxx"
|
||||
// ma 155,240 "10011xxx"
|
||||
// A 163,240 "10100xxx"
|
||||
var dp = flags&7;
|
||||
var range = (flags>>3)&7;
|
||||
value *= Math.pow(10, -dp);
|
||||
var isAC = !!(flags&64);
|
||||
var mode = "?", units = "";
|
||||
if (flags2==240) {
|
||||
if (flags&128) {
|
||||
mode = "current";
|
||||
units = ["","nA","uA","mA","A","kA","MA",""][range];
|
||||
} else {
|
||||
mode = "voltage";
|
||||
units = ["","nV","uV","mV","V","kV","MV",""][range] + " " + (isAC?"AC":"DC");
|
||||
}
|
||||
} else if (flags2==241) {
|
||||
if (isAC) {
|
||||
mode = "capacitance";
|
||||
units = ["","nF","uF","mF","F","kF","MF",""][range];
|
||||
} else if (flags&128) {
|
||||
mode = "frequency";
|
||||
units = "Hz";
|
||||
} else {
|
||||
mode = "resistance";
|
||||
units = ["","nOhm","uOhm","mOhm","Ohm","kOhm","MOhm",""][range];
|
||||
}
|
||||
} else if (flags2==242) {
|
||||
if (flags&128) mode = isAC ? "continuity" : "diode";
|
||||
else {
|
||||
mode = "temperature";
|
||||
units = isAC ? "F" : "C";
|
||||
}
|
||||
} else if (flags2==243) mode = "ncv";
|
||||
//console.log(mode+" "+value+" "+units,new Uint8Array(d.buffer).slice());
|
||||
decoded = {
|
||||
value : value,
|
||||
mode : mode, // current/voltage/capacitance/frequency/resistance/temperature
|
||||
units : units, // eg 'mA'
|
||||
raw : new Uint8Array(d.buffer).slice(),
|
||||
};
|
||||
updateDisplay(decoded);
|
||||
}
|
||||
|
||||
function updateDisplay(d) {
|
||||
var mode = d.mode;
|
||||
mode = mode.substr(0,1).toUpperCase()+mode.substr(1);
|
||||
var s = d.value.toString();
|
||||
|
||||
var R = Bangle.appRect;
|
||||
g.reset().clearRect(R);
|
||||
g.setFont("12x20").setFontAlign(-1,-1).drawString(mode, R.x, R.y);
|
||||
g.setFont("12x20").setFontAlign(1,1).drawString(d.units, R.x+R.w-1, R.y+R.h-1);
|
||||
var fontSize = 80;
|
||||
g.setFont("Vector",fontSize).setFontAlign(0,0);
|
||||
while (g.stringWidth(s) > R.w-20) {
|
||||
fontSize -= 2;
|
||||
g.setFont("Vector", fontSize);
|
||||
}
|
||||
g.drawString(s, R.x+R.w/2, R.y+R.h/2);
|
||||
}
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
E.showMessage(/*LANG*/"Connecting...");
|
||||
|
||||
NRF.requestDevice({ filters: [{ name: 'BDM' }] }).then(function(device) {
|
||||
return device.gatt.connect();
|
||||
}).then(function(g) {
|
||||
gatt = g;
|
||||
return gatt.getPrimaryService(0xFFF0);
|
||||
}).then(function(service) {
|
||||
return service.getCharacteristic(0xFFF4);
|
||||
}).then(function(c) {
|
||||
c.on('characteristicvaluechanged', function(event) {
|
||||
d = event.target.value;
|
||||
decode(d);
|
||||
});
|
||||
return c.startNotifications();
|
||||
}).then(function() {
|
||||
E.showMessage(/*LANG*/"Connected.");
|
||||
}).catch(function(e) {
|
||||
E.showMessage(e.toString());
|
||||
});
|
After Width: | Height: | Size: 13 KiB |
|
@ -0,0 +1,15 @@
|
|||
{ "id": "btmultimeter",
|
||||
"name": "Bluetooth Multimeter",
|
||||
"shortName":"BT Meter",
|
||||
"version":"0.01",
|
||||
"description": "Connect to compatible a Bluetooth Multimeters and display the result on your wrist!",
|
||||
"icon": "app.png",
|
||||
"tags": "bluetooth,tool",
|
||||
"screenshots" : [ { "url":"screenshot.png" } ],
|
||||
"supports" : ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"btmultimeter.app.js","url":"app.js"},
|
||||
{"name":"btmultimeter.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 2.6 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",
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
0.01: New App!
|
||||
0.02: Added some missing code.
|
||||
0.03: Made the code shorter and somewhat more readable by writing some functions. Also made it work as a library where it returns the text once finished. The keyboard is now made to exit correctly when the 'back' event is called. The keyboard now uses theme colors correctly, although it still looks best with dark theme. The numbers row is now solidly green - except for highlights.
|
||||
0.04: Now displays the opened text string at launch.
|
||||
0.05: Now scrolls text when string gets longer than screen width.
|
|
@ -0,0 +1,16 @@
|
|||
Swipe along the red field and release to select a letter.
|
||||
|
||||
Do the same for green field to select number or punctuation.
|
||||
|
||||
Release on left or right part of black field for backspace or space.
|
||||
|
||||
Swiping between the different fields is possible!
|
||||
|
||||
The drag in Dragboard is a nod to the javascript 'drag' event, which is used to select the characters. Also, you can't help but feel somewhat glamorous and risque when this is your keyboard!
|
||||
|
||||
Known bugs:
|
||||
- Initially developed for use with dark theme set on Bangle.js 2 - that is still the preferred way to view it although it now works with other themes.
|
||||
- When repeatedly doing 'del' on an empty text-string, the letter case is changed back and forth between upper and lower case.
|
||||
|
||||
To do:
|
||||
- Possibly provide a dragboard.settings.js file
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,261 @@
|
|||
//Keep banglejs screen on for 100 sec at 0.55 power level for development purposes
|
||||
//Bangle.setLCDTimeout(30);
|
||||
//Bangle.setLCDPower(1);
|
||||
|
||||
exports.input = function(options) {
|
||||
options = options||{};
|
||||
var text = options.text;
|
||||
if ("string"!=typeof text) text="";
|
||||
|
||||
var BGCOLOR = g.theme.bg;
|
||||
var HLCOLOR = g.theme.fg;
|
||||
var ABCCOLOR = g.toColor(1,0,0);//'#FF0000';
|
||||
var NUMCOLOR = g.toColor(0,1,0);//'#00FF00';
|
||||
var BIGFONT = '6x8:3';
|
||||
var BIGFONTWIDTH = parseInt(BIGFONT.charAt(0)*parseInt(BIGFONT.charAt(-1)));
|
||||
var SMALLFONT = '6x8:1';
|
||||
var SMALLFONTWIDTH = parseInt(SMALLFONT.charAt(0)*parseInt(SMALLFONT.charAt(-1)));
|
||||
|
||||
var ABC = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase();
|
||||
var ABCPADDING = (g.getWidth()-6*ABC.length)/2;
|
||||
|
||||
var NUM = ' 1234567890!?,.- ';
|
||||
var NUMHIDDEN = ' 1234567890!?,.- ';
|
||||
var NUMPADDING = (g.getWidth()-6*NUM.length)/2;
|
||||
|
||||
var rectHeight = 40;
|
||||
|
||||
|
||||
var delSpaceLast;
|
||||
|
||||
function drawAbcRow() {
|
||||
g.clear();
|
||||
g.setFont(SMALLFONT);
|
||||
g.setColor(ABCCOLOR);
|
||||
g.drawString(ABC, ABCPADDING, g.getHeight()/2);
|
||||
g.fillRect(0, g.getHeight()-26, g.getWidth(), g.getHeight());
|
||||
}
|
||||
|
||||
function drawNumRow() {
|
||||
g.setFont(SMALLFONT);
|
||||
g.setColor(NUMCOLOR);
|
||||
g.drawString(NUM, NUMPADDING, g.getHeight()/4);
|
||||
|
||||
g.fillRect(NUMPADDING, g.getHeight()-rectHeight*4/3, g.getWidth()-NUMPADDING, g.getHeight()-rectHeight*2/3);
|
||||
}
|
||||
|
||||
function updateTopString() {
|
||||
"ram"
|
||||
g.setColor(BGCOLOR);
|
||||
g.fillRect(0,4+20,176,13+20);
|
||||
g.setColor(0.2,0,0);
|
||||
var rectLen = text.length<27? text.length*6:27*6;
|
||||
g.fillRect(3,4+20,5+rectLen,13+20);
|
||||
g.setColor(0.7,0,0);
|
||||
g.fillRect(rectLen+5,4+20,rectLen+10,13+20);
|
||||
g.setColor(1,1,1);
|
||||
g.drawString(text.length<=27? text.substr(-27, 27) : '<- '+text.substr(-24,24), 5, 5+20);
|
||||
}
|
||||
|
||||
drawAbcRow();
|
||||
drawNumRow();
|
||||
updateTopString();
|
||||
|
||||
var abcHL;
|
||||
var abcHLPrev = -10;
|
||||
var numHL;
|
||||
var numHLPrev = -10;
|
||||
var type = '';
|
||||
var typePrev = '';
|
||||
var largeCharOffset = 6;
|
||||
|
||||
function resetChars(char, HLPrev, typePadding, heightDivisor, rowColor) {
|
||||
"ram"
|
||||
// Small character in list
|
||||
g.setColor(rowColor);
|
||||
g.setFont(SMALLFONT);
|
||||
g.drawString(char, typePadding + HLPrev*6, g.getHeight()/heightDivisor);
|
||||
// Large character
|
||||
g.setColor(BGCOLOR);
|
||||
g.fillRect(0,g.getHeight()/3,176,g.getHeight()/3+24);
|
||||
//g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, g.getHeight()/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle.
|
||||
// mark in the list
|
||||
}
|
||||
function showChars(char, HL, typePadding, heightDivisor) {
|
||||
"ram"
|
||||
// mark in the list
|
||||
g.setColor(HLCOLOR);
|
||||
g.setFont(SMALLFONT);
|
||||
if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, g.getHeight()/heightDivisor);
|
||||
// show new large character
|
||||
g.setFont(BIGFONT);
|
||||
g.drawString(char, typePadding + HL*6 -largeCharOffset, g.getHeight()/3);
|
||||
g.setFont(SMALLFONT);
|
||||
}
|
||||
|
||||
function changeCase(abcHL) {
|
||||
g.setColor(BGCOLOR);
|
||||
g.drawString(ABC, ABCPADDING, g.getHeight()/2);
|
||||
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) ABC = ABC.toLowerCase();
|
||||
else ABC = ABC.toUpperCase();
|
||||
g.setColor(ABCCOLOR);
|
||||
g.drawString(ABC, ABCPADDING, g.getHeight()/2);
|
||||
}
|
||||
return new Promise((resolve,reject) => {
|
||||
// Interpret touch input
|
||||
Bangle.setUI({
|
||||
mode: 'custom',
|
||||
back: ()=>{
|
||||
Bangle.setUI();
|
||||
g.clearRect(Bangle.appRect);
|
||||
resolve(text);
|
||||
},
|
||||
drag: function(event) {
|
||||
|
||||
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
// Choose character by draging along red rectangle at bottom of screen
|
||||
if (event.y >= ( g.getHeight() - 12 )) {
|
||||
// Translate x-position to character
|
||||
if (event.x < ABCPADDING) { abcHL = 0; }
|
||||
else if (event.x >= 176-ABCPADDING) { abcHL = 25; }
|
||||
else { abcHL = Math.floor((event.x-ABCPADDING)/6); }
|
||||
|
||||
// Datastream for development purposes
|
||||
//print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev));
|
||||
|
||||
// Unmark previous character and mark the current one...
|
||||
// Handling switching between letters and numbers/punctuation
|
||||
if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||
|
||||
if (abcHL != abcHLPrev) {
|
||||
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||
showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2);
|
||||
}
|
||||
// Print string at top of screen
|
||||
if (event.b == 0) {
|
||||
text = text + ABC.charAt(abcHL);
|
||||
updateTopString();
|
||||
|
||||
// Autoswitching letter case
|
||||
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL);
|
||||
}
|
||||
// Update previous character to current one
|
||||
abcHLPrev = abcHL;
|
||||
typePrev = 'abc';
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 12345678901234567890
|
||||
// Choose number or puctuation by draging on green rectangle
|
||||
else if ((event.y < ( g.getHeight() - 12 )) && (event.y > ( g.getHeight() - 52 ))) {
|
||||
// Translate x-position to character
|
||||
if (event.x < NUMPADDING) { numHL = 0; }
|
||||
else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; }
|
||||
else { numHL = Math.floor((event.x-NUMPADDING)/6); }
|
||||
|
||||
// Datastream for development purposes
|
||||
//print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev));
|
||||
|
||||
// Unmark previous character and mark the current one...
|
||||
// Handling switching between letters and numbers/punctuation
|
||||
if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||
|
||||
if (numHL != numHLPrev) {
|
||||
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||
showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4);
|
||||
}
|
||||
// Print string at top of screen
|
||||
if (event.b == 0) {
|
||||
g.setColor(HLCOLOR);
|
||||
// Backspace if releasing before list of numbers/punctuation
|
||||
if (event.x < NUMPADDING) {
|
||||
// show delete sign
|
||||
showChars('del', 0, g.getWidth()/2 +6 -27 , 4);
|
||||
delSpaceLast = 1;
|
||||
text = text.slice(0, -1);
|
||||
updateTopString();
|
||||
//print(text);
|
||||
}
|
||||
// Append space if releasing after list of numbers/punctuation
|
||||
else if (event.x > g.getWidth()-NUMPADDING) {
|
||||
//show space sign
|
||||
showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4);
|
||||
delSpaceLast = 1;
|
||||
text = text + ' ';
|
||||
updateTopString();
|
||||
//print(text);
|
||||
}
|
||||
// Append selected number/punctuation
|
||||
else {
|
||||
text = text + NUMHIDDEN.charAt(numHL);
|
||||
updateTopString();
|
||||
|
||||
// Autoswitching letter case
|
||||
if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase();
|
||||
}
|
||||
}
|
||||
// Update previous character to current one
|
||||
numHLPrev = numHL;
|
||||
typePrev = 'num';
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Make a space or backspace by swiping right or left on screen above green rectangle
|
||||
else if (event.y > 20+4) {
|
||||
if (event.b == 0) {
|
||||
g.setColor(HLCOLOR);
|
||||
if (event.x < g.getWidth()/2) {
|
||||
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||
|
||||
// show delete sign
|
||||
showChars('del', 0, g.getWidth()/2 +6 -27 , 4);
|
||||
delSpaceLast = 1;
|
||||
|
||||
// Backspace and draw string upper right corner
|
||||
text = text.slice(0, -1);
|
||||
updateTopString();
|
||||
if (text.length==0) changeCase(abcHL);
|
||||
//print(text, 'undid');
|
||||
}
|
||||
else {
|
||||
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||
|
||||
//show space sign
|
||||
showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4);
|
||||
delSpaceLast = 1;
|
||||
|
||||
// Append space and draw string upper right corner
|
||||
text = text + NUMHIDDEN.charAt(0);
|
||||
updateTopString();
|
||||
//print(text, 'made space');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
/* return new Promise((resolve,reject) => {
|
||||
Bangle.setUI({mode:"custom", back:()=>{
|
||||
Bangle.setUI();
|
||||
g.clearRect(Bangle.appRect);
|
||||
Bangle.setUI();
|
||||
resolve(text);
|
||||
}});
|
||||
}); */
|
||||
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
{ "id": "dragboard",
|
||||
"name": "Dragboard",
|
||||
"version":"0.05",
|
||||
"description": "A library for text input via swiping keyboard",
|
||||
"icon": "app.png",
|
||||
"type":"textinput",
|
||||
"tags": "keyboard",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"textinput","url":"lib.js"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -10,3 +10,4 @@
|
|||
0.10: added "one click exit" setting for Bangle 2
|
||||
0.11: Fix bangle.js 1 white icons not displaying
|
||||
0.12: On Bangle 2 change to swiping up/down to move between pages as to match page indicator. Swiping from left to right now loads the clock.
|
||||
0.13: Added swipeExit setting so that left-right to exit is an option
|
||||
|
|
|
@ -6,7 +6,8 @@ var settings = Object.assign({
|
|||
showClocks: true,
|
||||
showLaunchers: true,
|
||||
direct: false,
|
||||
oneClickExit:false
|
||||
oneClickExit:false,
|
||||
swipeExit: false
|
||||
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
||||
|
||||
if( settings.oneClickExit)
|
||||
|
@ -88,14 +89,14 @@ function drawPage(p){
|
|||
Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{
|
||||
selected = 0;
|
||||
oldselected=-1;
|
||||
if (dirUpDown==-1){
|
||||
if(settings.swipeExit && dirLeftRight==1) showClock();
|
||||
if (dirUpDown==-1||dirLeftRight==-1){
|
||||
++page; if (page>maxPage) page=0;
|
||||
drawPage(page);
|
||||
} else if (dirUpDown==1){
|
||||
} else if (dirUpDown==1||dirLeftRight==1){
|
||||
--page; if (page<0) page=maxPage;
|
||||
drawPage(page);
|
||||
}
|
||||
if (dirLeftRight==1) showClock();
|
||||
});
|
||||
|
||||
function showClock(){
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "dtlaunch",
|
||||
"name": "Desktop Launcher",
|
||||
"version": "0.12",
|
||||
"version": "0.13",
|
||||
"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",
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
showClocks: true,
|
||||
showLaunchers: true,
|
||||
direct: false,
|
||||
oneClickExit:false
|
||||
oneClickExit:false,
|
||||
swipeExit: false
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
|
@ -39,6 +40,14 @@
|
|||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Swipe Exit': {
|
||||
value: settings.swipeExit,
|
||||
format: v => v?"On":"Off",
|
||||
onchange: v => {
|
||||
settings.swipeExit = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'One click exit': {
|
||||
value: settings.oneClickExit,
|
||||
format: v => v?"On":"Off",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: first release
|
||||
0.02: updated dark theme bg2 color value
|
||||
|
|
|
@ -18,7 +18,7 @@ function flipTheme() {
|
|||
if (!g.theme.dark) {
|
||||
upd({
|
||||
fg:cl("#fff"), bg:cl("#000"),
|
||||
fg2:cl("#0ff"), bg2:cl("#000"),
|
||||
fg2:cl("#fff"), bg2:cl("#004"),
|
||||
fgH:cl("#fff"), bgH:cl("#00f"),
|
||||
dark:true
|
||||
});
|
Before Width: | Height: | Size: 644 B After Width: | Height: | Size: 644 B |
|
@ -2,17 +2,17 @@
|
|||
{
|
||||
"id": "flipper",
|
||||
"name": "flipper",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Switch between dark and light theme and vice versa, combine with pattern launcher and swipe to flip.",
|
||||
"readme":"README.md",
|
||||
"screenshots": [{"url":"flipper.png"}],
|
||||
"icon": "flipper.png",
|
||||
"screenshots": [{"url":"app.png"}],
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
"tags": "game",
|
||||
"tags": "tool",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"flipper.app.js","url":"flipper.app.js"},
|
||||
{"name":"flipper.img","url":"flipper.icon.js","evaluate":true}
|
||||
{"name":"flipper.app.js","url":"app.js"},
|
||||
{"name":"flipper.img","url":"icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: First release
|
||||
0.02: Move translations to locale module (removed watch settings, now pick language in Bangle App Loader, More..., Settings)
|
||||
|
|
|
@ -4,12 +4,11 @@ An imprecise clock for when you're not in a rush.
|
|||
|
||||
This clock is a remake of one of my favourite Pebble watchfaces, Fuzzy Text International. I use this watch for weekends and holidays, when 'within 5 minutes of the actual time' is close enough!
|
||||
|
||||
By default it will use the language set on the watch, go to settings to pick:
|
||||
* en_GB - English
|
||||
* en_US - American
|
||||
Translations are supported to get the time in the language of your choice! To choose language, in the Bangle App Loader, navigate to the 'More...' tab and pick language under 'Settings'. Currently supported languages are below, but if you want to contribution a translation please feel free!:
|
||||
* en_GB - English (Default)
|
||||
* es_ES - Spanish
|
||||
* fr_FR - French
|
||||
* no_NO - Norwegian
|
||||
* nn_NO - Norwegian Nynorsk (thank you zerodogg)
|
||||
* sv_SE - Swedish
|
||||
* de_DE - German
|
||||
|
||||
|
|
|
@ -1,186 +0,0 @@
|
|||
{
|
||||
"en_GB":{
|
||||
"hours":[
|
||||
"midnight", "one", "two", "three", "four", "five",
|
||||
"six", "seven", "eight", "nine", "ten", "eleven",
|
||||
"twelve", "one", "two", "three", "four", "five",
|
||||
"six", "seven", "eight", "nine", "ten", "eleven"
|
||||
],
|
||||
"minutes":[
|
||||
"*$1 o'clock",
|
||||
"five past *$1",
|
||||
"ten past *$1",
|
||||
"quarter past *$1",
|
||||
"twenty past *$1",
|
||||
"twenty five past *$1",
|
||||
"half past *$1",
|
||||
"twenty five to *$2",
|
||||
"twenty to *$2",
|
||||
"quarter to *$2",
|
||||
"ten to *$2",
|
||||
"five to *$2"
|
||||
],
|
||||
"text_scale":3.5
|
||||
},
|
||||
"en_US":{
|
||||
"hours":[
|
||||
"midnight", "one", "two", "three", "four", "five",
|
||||
"six", "seven", "eight", "nine", "ten", "eleven",
|
||||
"twelve", "one", "two", "three", "four", "five",
|
||||
"six", "seven", "eight", "nine", "ten", "eleven"
|
||||
],
|
||||
"minutes":[
|
||||
"*$1 o'clock",
|
||||
"five after *$1",
|
||||
"ten after *$1",
|
||||
"quarter after *$1",
|
||||
"twenty after *$1",
|
||||
"twenty five after *$1",
|
||||
"half past *$1",
|
||||
"twenty five to *$2",
|
||||
"twenty to *$2",
|
||||
"quarter to *$2",
|
||||
"ten to *$2",
|
||||
"five to *$2"
|
||||
],
|
||||
"text_scale":3.5
|
||||
},
|
||||
"es_ES":{
|
||||
"hours":[
|
||||
"doce", "una", "dos", "tres", "cuatro", "cinco",
|
||||
"seis", "siete", "ocho", "nueve", "diez", "once",
|
||||
"doce", "una", "dos", "tres", "cuatro", "cinco",
|
||||
"seis", "siete", "ocho", "nueve", "diez", "once"
|
||||
],
|
||||
"minutes":[
|
||||
"*$1 en punto",
|
||||
"*$1 y cinco",
|
||||
"*$1 y diez",
|
||||
"*$1 y cuarto",
|
||||
"*$1 y veinte",
|
||||
"*$1 y veinti- cinco",
|
||||
"*$1 y media",
|
||||
"*$2 menos veinti- cinco",
|
||||
"*$2 menos veinte",
|
||||
"*$2 menos cuarto",
|
||||
"*$2 menos diez",
|
||||
"*$2 menos cinco"
|
||||
],
|
||||
"text_scale":3.5
|
||||
},
|
||||
"fr_FR":{
|
||||
"hours":[
|
||||
"douze", "une", "deux", "trois", "quatre", "cinq",
|
||||
"six", "sept", "huit", "neuf", "dix", "onze",
|
||||
"douze", "une", "deux", "trois", "quatre", "cinq",
|
||||
"six", "sept", "huit", "neuf", "dix", "onze"
|
||||
],
|
||||
"minutes":[
|
||||
"*$1 heures",
|
||||
"*$1 heures cinq",
|
||||
"*$1 heures dix",
|
||||
"*$1 heures et quart",
|
||||
"*$1 heures vingt",
|
||||
"*$1 heures vingt- cinq",
|
||||
"*$1 heures et demie",
|
||||
"*$2 moins vingt- cinq",
|
||||
"*$2 heures moins vingt",
|
||||
"*$2 moins le quart",
|
||||
"*$2 heures moins dix",
|
||||
"*$2 heures moins cinq"
|
||||
],
|
||||
"text_scale":3.5
|
||||
},
|
||||
"no_NB":{
|
||||
"hours":[
|
||||
"tolv", "ett", "to", "tre", "fire", "fem",
|
||||
"seks", "sju", "åtte", "ni", "ti", "elleve",
|
||||
"tolv", "ett", "to", "tre", "fire", "fem",
|
||||
"seks", "sju", "åtte", "ni", "ti", "elleve"
|
||||
],
|
||||
"minutes":[
|
||||
"klokka er *$1",
|
||||
"fem over *$1",
|
||||
"ti over *$1",
|
||||
"kvart over *$1",
|
||||
"ti på halv *$2",
|
||||
"fem på halv *$2",
|
||||
"halv *$2",
|
||||
"fem over halv *$2",
|
||||
"ti over halv *$2",
|
||||
"kvart på *$2",
|
||||
"ti på *$2",
|
||||
"fem på *$2"
|
||||
],
|
||||
"text_scale":3.5
|
||||
},
|
||||
"nn_NO":{
|
||||
"hours":[
|
||||
"tolv", "eitt", "to", "tre", "fire", "fem",
|
||||
"seks", "sju", "åtte", "ni", "ti", "elleve",
|
||||
"tolv", "eitt", "to", "tre", "fire", "fem",
|
||||
"seks", "sju", "åtte", "ni", "ti", "elleve"
|
||||
],
|
||||
"minutes":[
|
||||
"klokka er *$1",
|
||||
"fem over *$1",
|
||||
"ti over *$1",
|
||||
"kvart over *$1",
|
||||
"ti på halv *$2",
|
||||
"fem på halv *$2",
|
||||
"halv *$2",
|
||||
"fem over halv *$2",
|
||||
"ti over halv *$2",
|
||||
"kvart på *$2",
|
||||
"ti på *$2",
|
||||
"fem på *$2"
|
||||
],
|
||||
"text_scale":3.5
|
||||
},
|
||||
"sv_SE":{
|
||||
"hours":[
|
||||
"tolv", "ett", "två", "tre", "fyra", "fem",
|
||||
"sex", "sju", "åtta", "nio", "tio", "elva",
|
||||
"tolv", "ett", "två", "tre", "fyra", "fem",
|
||||
"sex", "sju", "åtta", "nio", "tio", "elva"
|
||||
],
|
||||
"minutes":[
|
||||
"*$1",
|
||||
"fem över *$1",
|
||||
"tio över *$1",
|
||||
"kvart över *$1",
|
||||
"tjugo över *$1",
|
||||
"fem i halv *$2",
|
||||
"halv *$2",
|
||||
"fem över halv *$2",
|
||||
"tjugo i *$2",
|
||||
"kvart i *$2",
|
||||
"tio i *$2",
|
||||
"fem i *$2"
|
||||
],
|
||||
"text_scale":3.5
|
||||
},
|
||||
"de_DE":{
|
||||
"hours":[
|
||||
"zwölf", "eins", "zwei", "drei", "vier", "fünf",
|
||||
"sechs", "sieben", "acht", "neun", "zehn", "elf",
|
||||
"zwölf", "eins", "zwei", "drei", "vier", "fünf",
|
||||
"sechs", "sieben", "acht", "neun", "zehn", "elf"
|
||||
],
|
||||
"minutes":[
|
||||
"*$1 uhr",
|
||||
"fünf nach *$1",
|
||||
"zehn nach *$1",
|
||||
"viertel nach *$1",
|
||||
"zwanzig nach *$1",
|
||||
"fünf for halb *$2",
|
||||
"halb *$2",
|
||||
"fünf nach halb *$2",
|
||||
"zwanzig vor *$2",
|
||||
"viertel vor *$2",
|
||||
"zehn vor *$2",
|
||||
"fünf vor *$2"
|
||||
],
|
||||
"text_scale":3.5
|
||||
}
|
||||
}
|
|
@ -1,15 +1,37 @@
|
|||
// adapted from https://github.com/hallettj/Fuzzy-Text-International/
|
||||
const fuzzy_strings = require("Storage").readJSON("fuzzy_strings.json");
|
||||
|
||||
const SETTINGS_FILE = "fuzzyw.settings.json";
|
||||
let settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'language': 'System', 'alignment':'Centre'};
|
||||
|
||||
if (settings.language == 'System') {
|
||||
settings.language = require('locale').name;
|
||||
}
|
||||
|
||||
let fuzzy_string = fuzzy_strings[settings.language];
|
||||
let fuzzy_string = {
|
||||
"hours":[
|
||||
/*LANG*/"twelve",
|
||||
/*LANG*/"one",
|
||||
/*LANG*/"two",
|
||||
/*LANG*/"three",
|
||||
/*LANG*/"four",
|
||||
/*LANG*/"five",
|
||||
/*LANG*/"six",
|
||||
/*LANG*/"seven",
|
||||
/*LANG*/"eight",
|
||||
/*LANG*/"nine",
|
||||
/*LANG*/"ten",
|
||||
/*LANG*/"eleven"
|
||||
],
|
||||
"minutes":[
|
||||
/*LANG*/"*$1 o'clock",
|
||||
/*LANG*/"five past *$1",
|
||||
/*LANG*/"ten past *$1",
|
||||
/*LANG*/"quarter past *$1",
|
||||
/*LANG*/"twenty past *$1",
|
||||
/*LANG*/"twenty five past *$1",
|
||||
/*LANG*/"half past *$1",
|
||||
/*LANG*/"twenty five to *$2",
|
||||
/*LANG*/"twenty to *$2",
|
||||
/*LANG*/"quarter to *$2",
|
||||
/*LANG*/"ten to *$2",
|
||||
/*LANG*/"five to *$2"
|
||||
]
|
||||
};
|
||||
|
||||
let text_scale = 3.5;
|
||||
let timeout = 2.5*60;
|
||||
let drawTimeout;
|
||||
|
||||
|
@ -24,24 +46,15 @@ function queueDraw(seconds) {
|
|||
|
||||
const h = g.getHeight();
|
||||
const w = g.getWidth();
|
||||
let align_mode = 0;
|
||||
let align_pos = w/2;
|
||||
if (settings.alignment =='Left') {
|
||||
align_mode = -1;
|
||||
align_pos = 0;
|
||||
} else if (settings.alignment == 'Right') {
|
||||
align_mode = 1;
|
||||
align_pos = w;
|
||||
}
|
||||
|
||||
function getTimeString(date) {
|
||||
let segment = Math.round((date.getMinutes()*60 + date.getSeconds() + 1)/300);
|
||||
let hour = date.getHours() + Math.floor(segment/12);
|
||||
f_string = fuzzy_string.minutes[segment % 12];
|
||||
if (f_string.includes('$1')) {
|
||||
f_string = f_string.replace('$1', fuzzy_string.hours[(hour) % 24]);
|
||||
f_string = f_string.replace('$1', fuzzy_string.hours[(hour) % 12]);
|
||||
} else {
|
||||
f_string = f_string.replace('$2', fuzzy_string.hours[(hour + 1) % 24]);
|
||||
f_string = f_string.replace('$2', fuzzy_string.hours[(hour + 1) % 12]);
|
||||
}
|
||||
return f_string;
|
||||
}
|
||||
|
@ -49,11 +62,11 @@ function getTimeString(date) {
|
|||
function draw() {
|
||||
let time_string = getTimeString(new Date()).replace('*', '');
|
||||
// print(time_string);
|
||||
g.setFont('Vector', (h-24*2)/fuzzy_string.text_scale);
|
||||
g.setFontAlign(align_mode, 0);
|
||||
g.setFont('Vector', (h-24*2)/text_scale);
|
||||
g.setFontAlign(0, 0);
|
||||
g.clearRect(0, 24, w, h-24);
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawString(g.wrapString(time_string, w).join("\n"), align_pos, h/2);
|
||||
g.drawString(g.wrapString(time_string, w).join("\n"), w/2, h/2);
|
||||
queueDraw(timeout);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
(function(back) {
|
||||
const SETTINGS_FILE = "fuzzyw.settings.json";
|
||||
|
||||
var align_options = ['Left','Centre','Right'];
|
||||
var language_options = ['System', 'en_GB', 'en_US', 'es_ES', 'fr_FR', 'no_NO', 'sv_SE', 'de_DE'];
|
||||
|
||||
// initialize with default settings...
|
||||
let s = {'language': 'System', 'alignment': 'Centre'};
|
||||
|
||||
// ...and overwrite them with any saved values
|
||||
// This way saved values are preserved if a new version adds more settings
|
||||
const storage = require('Storage')
|
||||
let settings = storage.readJSON(SETTINGS_FILE, 1) || s;
|
||||
const saved = settings || {}
|
||||
for (const key in saved) {
|
||||
s[key] = saved[key]
|
||||
}
|
||||
|
||||
function save() {
|
||||
settings = s
|
||||
storage.write(SETTINGS_FILE, settings)
|
||||
}
|
||||
|
||||
E.showMenu({
|
||||
'': { 'title': 'Fuzzy Text Clock' },
|
||||
'< Back': back,
|
||||
'Language': {
|
||||
value: 0 | language_options.indexOf(s.language),
|
||||
min: 0, max: language_options.length - 1,
|
||||
format: v => language_options[v],
|
||||
onchange: v => {
|
||||
s.language = language_options[v];
|
||||
save();
|
||||
}
|
||||
},
|
||||
'Alignment': {
|
||||
value: 0 | align_options.indexOf(s.alignment),
|
||||
min: 0, max: align_options.length - 1,
|
||||
format: v => align_options[v],
|
||||
onchange: v => {
|
||||
s.alignment = align_options[v];
|
||||
save();
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
|
@ -2,7 +2,7 @@
|
|||
"id":"fuzzyw",
|
||||
"name":"Fuzzy Text Clock",
|
||||
"shortName": "Fuzzy Text",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "An imprecise clock for when you're not in a rush",
|
||||
"readme": "README.md",
|
||||
"icon":"fuzzyw.png",
|
||||
|
@ -13,8 +13,6 @@
|
|||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"fuzzyw.app.js","url":"fuzzyw.app.js"},
|
||||
{"name":"fuzzyw.settings.js","url":"fuzzyw.settings.js"},
|
||||
{"name":"fuzzyw.img","url":"fuzzyw.icon.js","evaluate":true},
|
||||
{"name":"fuzzy_strings.json","url":"fuzzy_strings.json"}
|
||||
{"name":"fuzzyw.img","url":"fuzzyw.icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -86,7 +86,8 @@ function infoColor(name) {
|
|||
* @param l
|
||||
*/
|
||||
function rScroller(l) {
|
||||
g.setFont("Vector", Math.round(g.getHeight()*l.fsz.slice(0, -1)/100));
|
||||
var size=l.font.split(":")[1].slice(0,-1);
|
||||
g.setFont("Vector", Math.round(g.getHeight()*size/100));
|
||||
const w = g.stringWidth(l.label)+40,
|
||||
y = l.y+l.h/2;
|
||||
l.offset = l.offset%w;
|
||||
|
|
|
@ -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"}],
|
||||
|
|
|
@ -11,3 +11,5 @@
|
|||
0.10: Adds additional 3 minute setting for HRM
|
||||
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,43 +1,13 @@
|
|||
function getSettings() {
|
||||
return require("Storage").readJSON("health.json",1)||{};
|
||||
}
|
||||
|
||||
function setSettings(healthSettings) {
|
||||
require("Storage").writeJSON("health.json",healthSettings);
|
||||
}
|
||||
|
||||
function menuMain() {
|
||||
swipe_enabled = false;
|
||||
clearButton();
|
||||
E.showMenu({
|
||||
"":{title:"Health Tracking"},
|
||||
"< Back":()=>load(),
|
||||
"Step Counting":()=>menuStepCount(),
|
||||
"Movement":()=>menuMovement(),
|
||||
"Heart Rate":()=>menuHRM(),
|
||||
"Settings":()=>menuSettings()
|
||||
});
|
||||
}
|
||||
|
||||
function menuSettings() {
|
||||
swipe_enabled = false;
|
||||
clearButton();
|
||||
var healthSettings=getSettings();
|
||||
//print(healthSettings);
|
||||
E.showMenu({
|
||||
"":{title:"Health Tracking"},
|
||||
"< Back":()=>menuMain(),
|
||||
"Heart Rt":{
|
||||
value : 0|healthSettings.hrm,
|
||||
min : 0, max : 3,
|
||||
format : v=>["Off","3 mins","10 mins","Always"][v],
|
||||
onchange : v => { healthSettings.hrm=v;setSettings(healthSettings); }
|
||||
},
|
||||
"Daily Step Goal":{
|
||||
value : (healthSettings.stepGoal ? healthSettings.stepGoal : 10000),
|
||||
min : 0, max : 20000, step : 100,
|
||||
onchange : v => { healthSettings.stepGoal=v;setSettings(healthSettings); }
|
||||
}
|
||||
"": { title: /*LANG*/"Health Tracking" },
|
||||
/*LANG*/"< Back": () => load(),
|
||||
/*LANG*/"Step Counting": () => menuStepCount(),
|
||||
/*LANG*/"Movement": () => menuMovement(),
|
||||
/*LANG*/"Heart Rate": () => menuHRM(),
|
||||
/*LANG*/"Settings": () => eval(require("Storage").read("health.settings.js"))(()=>menuMain())
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -45,10 +15,10 @@ function menuStepCount() {
|
|||
swipe_enabled = false;
|
||||
clearButton();
|
||||
E.showMenu({
|
||||
"":{title:"Step Counting"},
|
||||
"< Back":()=>menuMain(),
|
||||
"per hour":()=>stepsPerHour(),
|
||||
"per day":()=>stepsPerDay()
|
||||
"": { title:/*LANG*/"Steps" },
|
||||
/*LANG*/"< Back": () => menuMain(),
|
||||
/*LANG*/"per hour": () => stepsPerHour(),
|
||||
/*LANG*/"per day": () => stepsPerDay()
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -56,10 +26,10 @@ function menuMovement() {
|
|||
swipe_enabled = false;
|
||||
clearButton();
|
||||
E.showMenu({
|
||||
"":{title:"Movement"},
|
||||
"< Back":()=>menuMain(),
|
||||
"per hour":()=>movementPerHour(),
|
||||
"per day":()=>movementPerDay(),
|
||||
"": { title:/*LANG*/"Movement" },
|
||||
/*LANG*/"< Back": () => menuMain(),
|
||||
/*LANG*/"per hour": () => movementPerHour(),
|
||||
/*LANG*/"per day": () => movementPerDay(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -67,17 +37,16 @@ function menuHRM() {
|
|||
swipe_enabled = false;
|
||||
clearButton();
|
||||
E.showMenu({
|
||||
"":{title:"Heart Rate"},
|
||||
"< Back":()=>menuMain(),
|
||||
"per hour":()=>hrmPerHour(),
|
||||
"per day":()=>hrmPerDay(),
|
||||
"": { title:/*LANG*/"Heart Rate" },
|
||||
/*LANG*/"< Back": () => menuMain(),
|
||||
/*LANG*/"per hour": () => hrmPerHour(),
|
||||
/*LANG*/"per day": () => hrmPerDay(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function stepsPerHour() {
|
||||
E.showMessage(/*LANG*/"Loading...");
|
||||
var data = new Uint16Array(24);
|
||||
let data = new Uint16Array(24);
|
||||
require("health").readDay(new Date(), h=>data[h.hr]+=h.steps);
|
||||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
|
@ -88,7 +57,7 @@ function stepsPerHour() {
|
|||
|
||||
function stepsPerDay() {
|
||||
E.showMessage(/*LANG*/"Loading...");
|
||||
var data = new Uint16Array(31);
|
||||
let data = new Uint16Array(31);
|
||||
require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps);
|
||||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
|
@ -99,8 +68,8 @@ function stepsPerDay() {
|
|||
|
||||
function hrmPerHour() {
|
||||
E.showMessage(/*LANG*/"Loading...");
|
||||
var data = new Uint16Array(24);
|
||||
var cnt = new Uint8Array(23);
|
||||
let data = new Uint16Array(24);
|
||||
let cnt = new Uint8Array(23);
|
||||
require("health").readDay(new Date(), h=>{
|
||||
data[h.hr]+=h.bpm;
|
||||
if (h.bpm) cnt[h.hr]++;
|
||||
|
@ -115,8 +84,8 @@ function hrmPerHour() {
|
|||
|
||||
function hrmPerDay() {
|
||||
E.showMessage(/*LANG*/"Loading...");
|
||||
var data = new Uint16Array(31);
|
||||
var cnt = new Uint8Array(31);
|
||||
let data = new Uint16Array(31);
|
||||
let cnt = new Uint8Array(31);
|
||||
require("health").readDailySummaries(new Date(), h=>{
|
||||
data[h.day]+=h.bpm;
|
||||
if (h.bpm) cnt[h.day]++;
|
||||
|
@ -131,7 +100,7 @@ function hrmPerDay() {
|
|||
|
||||
function movementPerHour() {
|
||||
E.showMessage(/*LANG*/"Loading...");
|
||||
var data = new Uint16Array(24);
|
||||
let data = new Uint16Array(24);
|
||||
require("health").readDay(new Date(), h=>data[h.hr]+=h.movement);
|
||||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
|
@ -142,7 +111,7 @@ function movementPerHour() {
|
|||
|
||||
function movementPerDay() {
|
||||
E.showMessage(/*LANG*/"Loading...");
|
||||
var data = new Uint16Array(31);
|
||||
let data = new Uint16Array(31);
|
||||
require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.movement);
|
||||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
|
@ -164,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;
|
||||
|
||||
|
@ -176,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;
|
||||
}
|
||||
|
||||
|
@ -198,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);
|
||||
|
@ -214,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;
|
||||
|
@ -244,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
|
||||
|
@ -260,4 +229,5 @@ function clearButton() {
|
|||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
menuMain();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "health",
|
||||
"name": "Health Tracking",
|
||||
"version": "0.12",
|
||||
"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": [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
0.01: New App!
|
||||
0.02: using TS and rollup to bundle
|
||||
0.03: bug fixes and support bangle 1
|
||||
0.04: removing TS
|
||||
0.04: removing TS
|
||||
0.05: major overhaul; now you customize your calendar based on your location for candle lighting times
|
||||
0.06: bug fixes and improvements
|
After Width: | Height: | Size: 3.0 KiB |
|
@ -1,19 +1,22 @@
|
|||
# Hebrew Calendar
|
||||
|
||||
Displays the current hebrew calendar date
|
||||
Add screen shots (if possible) to the app folder and link then into this file with 
|
||||
Displays the current hebrew calendar date and upcoming holidays alongside a clock
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
Open the app, and it shows a menu with the date components
|
||||
Set it up as your clock in the settings
|
||||
|
||||
## Features
|
||||
|
||||
Shows the hebrew date, month, and year; alongside the gregorian date
|
||||
- Shows the hebrew date, month, and year; alongside the gregorian date
|
||||
- Shows when upcoming holidays start
|
||||
- Shows the gregorian day of week, date, and current time
|
||||
|
||||
## Controls
|
||||
|
||||
Name the buttons and what they are used for
|
||||
N/A
|
||||
|
||||
## Requests
|
||||
|
||||
|
@ -22,5 +25,5 @@ Michael Salaverry (github.com/barakplasma)
|
|||
## Creator
|
||||
|
||||
Michael Salaverry
|
||||
with help from https://github.com/IonicaBizau/hebrew-date (MIT license)
|
||||
with help from https://github.com/hebcal/hebcal-es6 (MIT license) which is used to calculate the calendar
|
||||
<div>Icons made by <a href="https://www.flaticon.com/authors/smashicons" title="Smashicons">Smashicons</a> from <a href="https://www.flaticon.com/" title="Flaticon">[www.flaticon.com](https://www.flaticon.com/premium-icon/calendar_3130060?term=jewish&page=1&position=10&page=1&position=10&related_id=3130060&origin=tag)</a></div>
|
|
@ -1,26 +1,181 @@
|
|||
g.clear();
|
||||
const dayInMS = 86400000;
|
||||
|
||||
let now = new Date();
|
||||
const DateProvider = { now: () => Date.now() };
|
||||
|
||||
let today = require('hebrewDate').hebrewDate(now);
|
||||
const Layout = require("Layout");
|
||||
const Locale = require("locale");
|
||||
|
||||
var mainmenu = {
|
||||
"": {
|
||||
"title": "Hebrew Date"
|
||||
},
|
||||
greg: {
|
||||
// @ts-ignore
|
||||
value: require('locale').date(now, 1),
|
||||
},
|
||||
date: {
|
||||
value: today.date,
|
||||
},
|
||||
month: {
|
||||
value: today.month_name,
|
||||
},
|
||||
year: {
|
||||
value: today.year,
|
||||
let nextEndingEvent;
|
||||
|
||||
function getCurrentEvents() {
|
||||
const now = DateProvider.now();
|
||||
|
||||
const current = hebrewCalendar.filter(
|
||||
(x) => x.startEvent <= now && x.endEvent >= now
|
||||
);
|
||||
|
||||
nextEndingEvent = current.reduce((acc, ev) => {
|
||||
return Math.min(acc, ev.endEvent);
|
||||
}, Infinity);
|
||||
|
||||
return current.map((event, i) => {
|
||||
return {
|
||||
type: "txt",
|
||||
font: "12x20",
|
||||
id: "currentEvents" + i,
|
||||
label: event.desc,
|
||||
pad: 2,
|
||||
bgCol: g.theme.bg,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getUpcomingEvents() {
|
||||
const now = DateProvider.now();
|
||||
|
||||
const futureEvents = hebrewCalendar.filter(
|
||||
(x) => x.startEvent >= now && x.startEvent <= now + dayInMS
|
||||
);
|
||||
|
||||
let warning;
|
||||
let eventsLeft = hebrewCalendar.filter(
|
||||
(x) => x.startEvent >= now && x.startEvent <= now + dayInMS * 14
|
||||
).length;
|
||||
|
||||
if (eventsLeft < 14) {
|
||||
warning = {
|
||||
startEvent: 0,
|
||||
type: "txt",
|
||||
font: "4x6",
|
||||
id: "warning",
|
||||
label: "only " + eventsLeft + " events left in calendar; update soon",
|
||||
pad: 2,
|
||||
bgCol: g.theme.bg,
|
||||
};
|
||||
}
|
||||
};
|
||||
// @ts-ignore
|
||||
E.showMenu(mainmenu);
|
||||
|
||||
return futureEvents
|
||||
.slice(0, 2)
|
||||
.map((event, i) => {
|
||||
return {
|
||||
startEvent: event.startEvent,
|
||||
type: "txt",
|
||||
font: "6x8",
|
||||
id: "upcomingEvents" + 1,
|
||||
label: event.desc + " at " + Locale.time(new Date(event.startEvent), 1),
|
||||
pad: 2,
|
||||
bgCol: g.theme.bg,
|
||||
};
|
||||
})
|
||||
.concat(warning)
|
||||
.sort(function (a, b) {
|
||||
return a.startEvent - b.startEvent;
|
||||
});
|
||||
}
|
||||
|
||||
function dateTime() {
|
||||
return (
|
||||
Locale.dow(new Date(), 1) +
|
||||
" " +
|
||||
Locale.date(new Date(), 1) +
|
||||
" " +
|
||||
Locale.time(new Date(), 1)
|
||||
);
|
||||
}
|
||||
|
||||
function makeLayout() {
|
||||
return new Layout(
|
||||
{
|
||||
type: "v",
|
||||
c: [
|
||||
{
|
||||
type: "txt",
|
||||
font: "6x8",
|
||||
id: "title",
|
||||
label: "-- Hebrew Calendar Events --",
|
||||
pad: 2,
|
||||
bgCol: g.theme.bg2,
|
||||
},
|
||||
{
|
||||
type: "txt",
|
||||
font: "6x8",
|
||||
id: "currently",
|
||||
label: "Currently",
|
||||
pad: 2,
|
||||
bgCol: g.theme.bgH,
|
||||
},
|
||||
]
|
||||
.concat(getCurrentEvents())
|
||||
.concat([
|
||||
{
|
||||
type: "txt",
|
||||
font: "6x8",
|
||||
label: "Upcoming",
|
||||
id: "upcoming",
|
||||
pad: 2,
|
||||
bgCol: g.theme.bgH,
|
||||
},
|
||||
])
|
||||
.concat(getUpcomingEvents())
|
||||
.concat([
|
||||
{
|
||||
type: "txt",
|
||||
font: "Vector14",
|
||||
id: "time",
|
||||
label: dateTime(),
|
||||
pad: 2,
|
||||
bgCol: undefined,
|
||||
},
|
||||
]),
|
||||
},
|
||||
{ lazy: true }
|
||||
);
|
||||
}
|
||||
let layout = makeLayout();
|
||||
// see also https://www.espruino.com/Bangle.js+Layout#updating-the-screen
|
||||
|
||||
// timeout used to update every minute
|
||||
let drawTimeout;
|
||||
|
||||
function draw() {
|
||||
layout.time.label = dateTime();
|
||||
layout.render();
|
||||
|
||||
// schedule a draw for the next minute
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function () {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (DateProvider.now() % 60000));
|
||||
console.log("updated time");
|
||||
}
|
||||
|
||||
// update time and draw
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
||||
|
||||
function findNextEvent() {
|
||||
return hebrewCalendar.find((ev) => {
|
||||
return ev.startEvent > DateProvider.now();
|
||||
});
|
||||
}
|
||||
|
||||
function updateCalendar() {
|
||||
layout.clear();
|
||||
layout = makeLayout();
|
||||
layout.forgetLazyState();
|
||||
layout.render();
|
||||
|
||||
let nextChange = Math.min(
|
||||
findNextEvent().startEvent - DateProvider.now() + 5000,
|
||||
nextEndingEvent - DateProvider.now() + 5000
|
||||
);
|
||||
setTimeout(updateCalendar, nextChange);
|
||||
console.log("updated events");
|
||||
}
|
||||
|
||||
updateCalendar();
|
||||
|
||||
Bangle.setUI("clock");
|
|
@ -0,0 +1,46 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hebrew Calendar Customizer</title>
|
||||
<link rel="stylesheet" href="https://banglejs.com/apps/css/spectre.min.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container grid-sm">
|
||||
<div class="panel center">
|
||||
<div class="panel-header">
|
||||
<div class="panel-title text-center h5 mt-10">Hebrew Calendar Loader</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="h4">Your location</div>
|
||||
<form method="GET">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="lat">Latitude</label>
|
||||
<input class="form-input" type="number" id="lat" name="lat" required min="-90" max="90" step="0.0000001" value="31.776580" placeholder="31.776580">
|
||||
<label class="form-label" for="lon">Longitude</label>
|
||||
<input class="form-input" type="number" id="lon" name="lon" required min="-180" max="180" step="0.0000001" value="35.233706" placeholder="35.233706">
|
||||
<div>get your latitude and longitude from <a href="https://plus.codes/map">plus.codes</a> or:</div>
|
||||
<button class="btn btn-secondary input-group-btn" id="geoloc">Get Latitude and Longitude automatically (using location permission)</button>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" id="inIL" name="inIL" checked>
|
||||
<i class="form-icon"></i> In Israel?
|
||||
</label>
|
||||
<button class="btn btn-primary input-group-btn" type="submit">Upload</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<div class="text-center h6"><a href="https://github.com/hebcal/hebcal-es6">With help from @hebcal/core</a></div>
|
||||
<div class="text-center" id="hDate"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://banglejs.com/apps/core/lib/customize.js"></script>
|
||||
<script type="module" src="customizer.mjs"></script>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,329 @@
|
|||
import {
|
||||
HebrewCalendar,
|
||||
HDate,
|
||||
Location,
|
||||
Zmanim,
|
||||
} from "https://cdn.skypack.dev/@hebcal/core@^3?min";
|
||||
|
||||
function onload(event) {
|
||||
event.preventDefault();
|
||||
const latLon = getLatLonFromForm();
|
||||
const events = generateHebCal(latLon);
|
||||
const calendar = serializeEvents(events);
|
||||
console.debug(calendar);
|
||||
globalThis["cal"] = calendar;
|
||||
loadWatch(calendar);
|
||||
}
|
||||
|
||||
function loadWatch(json) {
|
||||
sendCustomizedApp({
|
||||
id: "hebrew_calendar",
|
||||
|
||||
storage: [
|
||||
{
|
||||
name: "hebrew_calendar.app.js",
|
||||
url: "app.js",
|
||||
// content below is same as app.js except for the first line which customizes the hebrewCalendar object used
|
||||
content: `
|
||||
let hebrewCalendar = ${json};
|
||||
|
||||
const dayInMS = 86400000;
|
||||
|
||||
const DateProvider = { now: () => Date.now() };
|
||||
|
||||
const Layout = require("Layout");
|
||||
const Locale = require("locale");
|
||||
|
||||
let nextEndingEvent;
|
||||
|
||||
function getCurrentEvents() {
|
||||
const now = DateProvider.now();
|
||||
|
||||
const current = hebrewCalendar.filter(
|
||||
(x) => x.startEvent <= now && x.endEvent >= now
|
||||
);
|
||||
|
||||
nextEndingEvent = current.reduce((acc, ev) => {
|
||||
return Math.min(acc, ev.endEvent);
|
||||
}, Infinity);
|
||||
|
||||
return current.map((event, i) => {
|
||||
return {
|
||||
type: "txt",
|
||||
font: "12x20",
|
||||
id: "currentEvents" + i,
|
||||
label: event.desc,
|
||||
pad: 2,
|
||||
bgCol: g.theme.bg,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getUpcomingEvents() {
|
||||
const now = DateProvider.now();
|
||||
|
||||
const futureEvents = hebrewCalendar.filter(
|
||||
(x) => x.startEvent >= now && x.startEvent <= now + dayInMS
|
||||
);
|
||||
|
||||
let warning;
|
||||
let eventsLeft = hebrewCalendar.filter(
|
||||
(x) => x.startEvent >= now && x.startEvent <= now + dayInMS * 14
|
||||
).length;
|
||||
|
||||
if (eventsLeft < 14) {
|
||||
warning = {
|
||||
startEvent: 0,
|
||||
type: "txt",
|
||||
font: "4x6",
|
||||
id: "warning",
|
||||
label: "only " + eventsLeft + " events left in calendar; update soon",
|
||||
pad: 2,
|
||||
bgCol: g.theme.bg,
|
||||
};
|
||||
}
|
||||
|
||||
return futureEvents
|
||||
.slice(0, 2)
|
||||
.map((event, i) => {
|
||||
return {
|
||||
startEvent: event.startEvent,
|
||||
type: "txt",
|
||||
font: "6x8",
|
||||
id: "upcomingEvents" + 1,
|
||||
label: event.desc + " at " + Locale.time(new Date(event.startEvent), 1),
|
||||
pad: 2,
|
||||
bgCol: g.theme.bg,
|
||||
};
|
||||
})
|
||||
.concat(warning)
|
||||
.sort(function (a, b) {
|
||||
return a.startEvent - b.startEvent;
|
||||
});
|
||||
}
|
||||
|
||||
function dateTime() {
|
||||
return (
|
||||
Locale.dow(new Date(), 1) +
|
||||
" " +
|
||||
Locale.date(new Date(), 1) +
|
||||
" " +
|
||||
Locale.time(new Date(), 1)
|
||||
);
|
||||
}
|
||||
|
||||
function makeLayout() {
|
||||
return new Layout(
|
||||
{
|
||||
type: "v",
|
||||
c: [
|
||||
{
|
||||
type: "txt",
|
||||
font: "6x8",
|
||||
id: "title",
|
||||
label: "-- Hebrew Calendar Events --",
|
||||
pad: 2,
|
||||
bgCol: g.theme.bg2,
|
||||
},
|
||||
{
|
||||
type: "txt",
|
||||
font: "6x8",
|
||||
id: "currently",
|
||||
label: "Currently",
|
||||
pad: 2,
|
||||
bgCol: g.theme.bgH,
|
||||
},
|
||||
]
|
||||
.concat(getCurrentEvents())
|
||||
.concat([
|
||||
{
|
||||
type: "txt",
|
||||
font: "6x8",
|
||||
label: "Upcoming",
|
||||
id: "upcoming",
|
||||
pad: 2,
|
||||
bgCol: g.theme.bgH,
|
||||
},
|
||||
])
|
||||
.concat(getUpcomingEvents())
|
||||
.concat([
|
||||
{
|
||||
type: "txt",
|
||||
font: "Vector14",
|
||||
id: "time",
|
||||
label: dateTime(),
|
||||
pad: 2,
|
||||
bgCol: undefined,
|
||||
},
|
||||
]),
|
||||
},
|
||||
{ lazy: true }
|
||||
);
|
||||
}
|
||||
let layout = makeLayout();
|
||||
// see also https://www.espruino.com/Bangle.js+Layout#updating-the-screen
|
||||
|
||||
// timeout used to update every minute
|
||||
let drawTimeout;
|
||||
|
||||
function draw() {
|
||||
layout.time.label = dateTime();
|
||||
layout.render();
|
||||
|
||||
// schedule a draw for the next minute
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function () {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (DateProvider.now() % 60000));
|
||||
console.log("updated time");
|
||||
}
|
||||
|
||||
// update time and draw
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
||||
|
||||
function findNextEvent() {
|
||||
return hebrewCalendar.find((ev) => {
|
||||
return ev.startEvent > DateProvider.now();
|
||||
});
|
||||
}
|
||||
|
||||
function updateCalendar() {
|
||||
layout.clear();
|
||||
layout = makeLayout();
|
||||
layout.forgetLazyState();
|
||||
layout.render();
|
||||
|
||||
let nextChange = Math.min(
|
||||
findNextEvent().startEvent - DateProvider.now() + 5000,
|
||||
nextEndingEvent - DateProvider.now() + 5000
|
||||
);
|
||||
setTimeout(updateCalendar, nextChange);
|
||||
console.log("updated events");
|
||||
}
|
||||
|
||||
updateCalendar();
|
||||
|
||||
Bangle.setUI("clock");
|
||||
`,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
document
|
||||
.querySelector("button[type=submit]")
|
||||
.addEventListener("click", onload, false);
|
||||
|
||||
document.querySelector("#geoloc")?.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(pos) => {
|
||||
const {
|
||||
coords: { latitude, longitude },
|
||||
} = pos;
|
||||
locationElements[0].value = latitude;
|
||||
locationElements[1].value = longitude;
|
||||
console.debug(pos);
|
||||
},
|
||||
(err) => {
|
||||
if (err.PERMISSION_DENIED) {
|
||||
alert("permission required to use geolocation api; enter manually");
|
||||
}
|
||||
if (err.POSITION_UNAVAILABLE) {
|
||||
alert("position unavailable; enter manually");
|
||||
}
|
||||
},
|
||||
{ enableHighAccuracy: false }
|
||||
);
|
||||
});
|
||||
|
||||
document.querySelector(
|
||||
"#hDate"
|
||||
).innerText = `Today is ${new Date().toLocaleDateString()} & ${new HDate().toString()}`;
|
||||
|
||||
const locationElements = [
|
||||
document.querySelector("#lat"),
|
||||
document.querySelector("#lon"),
|
||||
];
|
||||
|
||||
function getLatLonFromForm() {
|
||||
const latLon = locationElements.map((el) => el.value);
|
||||
if (locationElements.every((x) => x.checkValidity())) {
|
||||
return latLon;
|
||||
} else {
|
||||
console.debug("lat lon invalid error");
|
||||
return [0, 0];
|
||||
}
|
||||
}
|
||||
|
||||
function groupBy(arr, fn) {
|
||||
return arr
|
||||
.map(typeof fn === "function" ? fn : (val) => val[fn])
|
||||
.reduce((acc, val, i) => {
|
||||
acc[val] = (acc[val] || []).concat(arr[i]);
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function generateHebCal(latLon) {
|
||||
const location = new Location(
|
||||
...latLon,
|
||||
document.querySelector("#inIL").checked
|
||||
);
|
||||
|
||||
const now = new Date();
|
||||
|
||||
const options = {
|
||||
year: now.getFullYear(),
|
||||
isHebrewYear: false,
|
||||
candlelighting: true,
|
||||
location,
|
||||
addHebrewDates: true,
|
||||
addHebrewDatesForEvents: true,
|
||||
sedrot: true,
|
||||
start: now,
|
||||
end: new Date(now.getFullYear(), now.getMonth() + 3),
|
||||
};
|
||||
|
||||
const events = HebrewCalendar.calendar(options).map((ev) => {
|
||||
const { desc, eventTime, startEvent, endEvent } = ev;
|
||||
|
||||
const zman = new Zmanim(ev.date, ...latLon.map(Number));
|
||||
|
||||
let output = {
|
||||
desc,
|
||||
startEvent: startEvent?.eventTime?.getTime() || zman.gregEve().getTime(),
|
||||
endEvent: endEvent?.eventTime?.getTime() || zman.shkiah().getTime(),
|
||||
};
|
||||
|
||||
if (eventTime) {
|
||||
delete output.startEvent;
|
||||
delete output.endEvent;
|
||||
output.startEvent = eventTime.getTime();
|
||||
output.endEvent = eventTime.getTime() + 60000 * 15;
|
||||
}
|
||||
|
||||
return output;
|
||||
});
|
||||
|
||||
// console.table(events)
|
||||
|
||||
return events.sort((a, b) => {
|
||||
return a.startEvent - b.startEvent;
|
||||
});
|
||||
}
|
||||
|
||||
function enc(data) {
|
||||
return btoa(heatshrink.compress(new TextEncoder().encode(data)));
|
||||
}
|
||||
|
||||
function serializeEvents(events) {
|
||||
// const splitByGregorianMonth = groupBy(events, (evt) => {
|
||||
// return new Date(evt.startEvent).getMonth();
|
||||
// });
|
||||
return JSON.stringify(events);
|
||||
}
|
|
@ -1,311 +0,0 @@
|
|||
/*!
|
||||
* This script was taked from this page http://www.shamash.org/help/javadate.shtml and ported to Node.js by Ionică Bizău in https://github.com/IonicaBizau/hebrew-date
|
||||
*
|
||||
* This script was adapted from C sources written by
|
||||
* Scott E. Lee, which contain the following copyright notice:
|
||||
*
|
||||
* Copyright 1993-1995, Scott E. Lee, all rights reserved.
|
||||
* Permission granted to use, copy, modify, distribute and sell so long as
|
||||
* the above copyright and this permission statement are retained in all
|
||||
* copies. THERE IS NO WARRANTY - USE AT YOUR OWN RISK.
|
||||
*
|
||||
* Bill Hastings
|
||||
* RBI Software Systems
|
||||
* bhastings@rbi.com
|
||||
*/
|
||||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
|
||||
var GREG_SDN_OFFSET = 32045, DAYS_PER_5_MONTHS = 153, DAYS_PER_4_YEARS = 1461, DAYS_PER_400_YEARS = 146097;
|
||||
var HALAKIM_PER_HOUR = 1080, HALAKIM_PER_DAY = 25920, HALAKIM_PER_LUNAR_CYCLE = 29 * HALAKIM_PER_DAY + 13753, HALAKIM_PER_METONIC_CYCLE = HALAKIM_PER_LUNAR_CYCLE * (12 * 19 + 7);
|
||||
var HEB_SDN_OFFSET = 347997, NEW_MOON_OF_CREATION = 31524, NOON = 18 * HALAKIM_PER_HOUR, AM3_11_20 = 9 * HALAKIM_PER_HOUR + 204, AM9_32_43 = 15 * HALAKIM_PER_HOUR + 589;
|
||||
var SUN = 0, MON = 1, TUES = 2, WED = 3, THUR = 4, FRI = 5, SAT = 6;
|
||||
function weekdayarr(d0, d1, d2, d3, d4, d5, d6) {
|
||||
this[0] = d0;
|
||||
this[1] = d1;
|
||||
this[2] = d2;
|
||||
this[3] = d3;
|
||||
this[4] = d4;
|
||||
this[5] = d5;
|
||||
this[6] = d6;
|
||||
}
|
||||
function gregmontharr(m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11) {
|
||||
this[0] = m0;
|
||||
this[1] = m1;
|
||||
this[2] = m2;
|
||||
this[3] = m3;
|
||||
this[4] = m4;
|
||||
this[5] = m5;
|
||||
this[6] = m6;
|
||||
this[7] = m7;
|
||||
this[8] = m8;
|
||||
this[9] = m9;
|
||||
this[10] = m10;
|
||||
this[11] = m11;
|
||||
}
|
||||
function hebrewmontharr(m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13) {
|
||||
this[0] = m0;
|
||||
this[1] = m1;
|
||||
this[2] = m2;
|
||||
this[3] = m3;
|
||||
this[4] = m4;
|
||||
this[5] = m5;
|
||||
this[6] = m6;
|
||||
this[7] = m7;
|
||||
this[8] = m8;
|
||||
this[9] = m9;
|
||||
this[10] = m10;
|
||||
this[11] = m11;
|
||||
this[12] = m12;
|
||||
this[13] = m13;
|
||||
}
|
||||
function monthsperyeararr(m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18) {
|
||||
this[0] = m0;
|
||||
this[1] = m1;
|
||||
this[2] = m2;
|
||||
this[3] = m3;
|
||||
this[4] = m4;
|
||||
this[5] = m5;
|
||||
this[6] = m6;
|
||||
this[7] = m7;
|
||||
this[8] = m8;
|
||||
this[9] = m9;
|
||||
this[10] = m10;
|
||||
this[11] = m11;
|
||||
this[12] = m12;
|
||||
this[13] = m13;
|
||||
this[14] = m14;
|
||||
this[15] = m15;
|
||||
this[16] = m16;
|
||||
this[17] = m17;
|
||||
this[18] = m18;
|
||||
}
|
||||
var gWeekday = new weekdayarr("Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur"), gMonth = new gregmontharr("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"), hMonth = new hebrewmontharr("Tishri", "Heshvan", "Kislev", "Tevet", "Shevat", "AdarI", "AdarII", "Nisan", "Iyyar", "Sivan", "Tammuz", "Av", "Elul"), mpy = new monthsperyeararr(12, 12, 13, 12, 12, 13, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 13);
|
||||
/**
|
||||
* hebrewDate
|
||||
* Convert the Gregorian dates into Hebrew calendar dates.
|
||||
*
|
||||
* @name hebrewDate
|
||||
* @function
|
||||
* @param {Date|Number} inputDate The date object (representing the Gregorian date) or the year.
|
||||
* @return {Object} An object containing:
|
||||
*
|
||||
* - `year`: The Hebrew year.
|
||||
* - `month`: The Hebrew month.
|
||||
* - `month_name`: The Hebrew month name.
|
||||
* - `date`: The Hebrew date.
|
||||
*/
|
||||
function hebrewDate(inputDateOrYear) {
|
||||
var inputMonth, inputDate;
|
||||
var hebrewMonth = 0, hebrewDate = 0, hebrewYear = 0, metonicCycle = 0, metonicYear = 0, moladDay = 0, moladHalakim = 0;
|
||||
function GregorianToSdn(inputYear, inputMonth, inputDay) {
|
||||
var year = 0, month = 0, sdn = void 0;
|
||||
// Make year a positive number
|
||||
if (inputYear < 0) {
|
||||
year = inputYear + 4801;
|
||||
}
|
||||
else {
|
||||
year = inputYear + 4800;
|
||||
}
|
||||
// Adjust the start of the year
|
||||
if (inputMonth > 2) {
|
||||
month = inputMonth - 3;
|
||||
}
|
||||
else {
|
||||
month = inputMonth + 9;
|
||||
year--;
|
||||
}
|
||||
sdn = Math.floor(Math.floor(year / 100) * DAYS_PER_400_YEARS / 4);
|
||||
sdn += Math.floor(year % 100 * DAYS_PER_4_YEARS / 4);
|
||||
sdn += Math.floor((month * DAYS_PER_5_MONTHS + 2) / 5);
|
||||
sdn += inputDay - GREG_SDN_OFFSET;
|
||||
return sdn;
|
||||
}
|
||||
function SdnToHebrew(sdn) {
|
||||
var tishri1 = 0, tishri1After = 0, yearLength = 0, inputDay = sdn - HEB_SDN_OFFSET;
|
||||
FindTishriMolad(inputDay);
|
||||
tishri1 = Tishri1(metonicYear, moladDay, moladHalakim);
|
||||
if (inputDay >= tishri1) {
|
||||
// It found Tishri 1 at the start of the year.
|
||||
hebrewYear = metonicCycle * 19 + metonicYear + 1;
|
||||
if (inputDay < tishri1 + 59) {
|
||||
if (inputDay < tishri1 + 30) {
|
||||
hebrewMonth = 1;
|
||||
hebrewDate = inputDay - tishri1 + 1;
|
||||
}
|
||||
else {
|
||||
hebrewMonth = 2;
|
||||
hebrewDate = inputDay - tishri1 - 29;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// We need the length of the year to figure this out,so find Tishri 1 of the next year.
|
||||
moladHalakim += HALAKIM_PER_LUNAR_CYCLE * mpy[metonicYear];
|
||||
moladDay += Math.floor(moladHalakim / HALAKIM_PER_DAY);
|
||||
moladHalakim = moladHalakim % HALAKIM_PER_DAY;
|
||||
tishri1After = Tishri1((metonicYear + 1) % 19, moladDay, moladHalakim);
|
||||
}
|
||||
else {
|
||||
// It found Tishri 1 at the end of the year.
|
||||
hebrewYear = metonicCycle * 19 + metonicYear;
|
||||
if (inputDay >= tishri1 - 177) {
|
||||
// It is one of the last 6 months of the year.
|
||||
if (inputDay > tishri1 - 30) {
|
||||
hebrewMonth = 13;
|
||||
hebrewDate = inputDay - tishri1 + 30;
|
||||
}
|
||||
else if (inputDay > tishri1 - 60) {
|
||||
hebrewMonth = 12;
|
||||
hebrewDate = inputDay - tishri1 + 60;
|
||||
}
|
||||
else if (inputDay > tishri1 - 89) {
|
||||
hebrewMonth = 11;
|
||||
hebrewDate = inputDay - tishri1 + 89;
|
||||
}
|
||||
else if (inputDay > tishri1 - 119) {
|
||||
hebrewMonth = 10;
|
||||
hebrewDate = inputDay - tishri1 + 119;
|
||||
}
|
||||
else if (inputDay > tishri1 - 148) {
|
||||
hebrewMonth = 9;
|
||||
hebrewDate = inputDay - tishri1 + 148;
|
||||
}
|
||||
else {
|
||||
hebrewMonth = 8;
|
||||
hebrewDate = inputDay - tishri1 + 178;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (mpy[(hebrewYear - 1) % 19] == 13) {
|
||||
hebrewMonth = 7;
|
||||
hebrewDate = inputDay - tishri1 + 207;
|
||||
if (hebrewDate > 0)
|
||||
return;
|
||||
hebrewMonth--;
|
||||
hebrewDate += 30;
|
||||
if (hebrewDate > 0)
|
||||
return;
|
||||
hebrewMonth--;
|
||||
hebrewDate += 30;
|
||||
}
|
||||
else {
|
||||
hebrewMonth = 6;
|
||||
hebrewDate = inputDay - tishri1 + 207;
|
||||
if (hebrewDate > 0)
|
||||
return;
|
||||
hebrewMonth--;
|
||||
hebrewDate += 30;
|
||||
}
|
||||
if (hebrewDate > 0)
|
||||
return;
|
||||
hebrewMonth--;
|
||||
hebrewDate += 29;
|
||||
if (hebrewDate > 0)
|
||||
return;
|
||||
// We need the length of the year to figure this out,so find Tishri 1 of this year.
|
||||
tishri1After = tishri1;
|
||||
FindTishriMolad(moladDay - 365);
|
||||
tishri1 = Tishri1(metonicYear, moladDay, moladHalakim);
|
||||
}
|
||||
}
|
||||
yearLength = tishri1After - tishri1;
|
||||
moladDay = inputDay - tishri1 - 29;
|
||||
if (yearLength == 355 || yearLength == 385) {
|
||||
// Heshvan has 30 days
|
||||
if (moladDay <= 30) {
|
||||
hebrewMonth = 2;
|
||||
hebrewDate = moladDay;
|
||||
return;
|
||||
}
|
||||
moladDay -= 30;
|
||||
}
|
||||
else {
|
||||
// Heshvan has 29 days
|
||||
if (moladDay <= 29) {
|
||||
hebrewMonth = 2;
|
||||
hebrewDate = moladDay;
|
||||
return;
|
||||
}
|
||||
moladDay -= 29;
|
||||
}
|
||||
// It has to be Kislev.
|
||||
hebrewMonth = 3;
|
||||
hebrewDate = moladDay;
|
||||
}
|
||||
function FindTishriMolad(inputDay) {
|
||||
// Estimate the metonic cycle number. Note that this may be an under
|
||||
// estimate because there are 6939.6896 days in a metonic cycle not
|
||||
// 6940,but it will never be an over estimate. The loop below will
|
||||
// correct for any error in this estimate.
|
||||
metonicCycle = Math.floor((inputDay + 310) / 6940);
|
||||
// Calculate the time of the starting molad for this metonic cycle.
|
||||
MoladOfMetonicCycle();
|
||||
// If the above was an under estimate,increment the cycle number until
|
||||
// the correct one is found. For modern dates this loop is about 98.6%
|
||||
// likely to not execute,even once,because the above estimate is
|
||||
// really quite close.
|
||||
while (moladDay < inputDay - 6940 + 310) {
|
||||
metonicCycle++;
|
||||
moladHalakim += HALAKIM_PER_METONIC_CYCLE;
|
||||
moladDay += Math.floor(moladHalakim / HALAKIM_PER_DAY);
|
||||
moladHalakim = moladHalakim % HALAKIM_PER_DAY;
|
||||
}
|
||||
// Find the molad of Tishri closest to this date.
|
||||
for (metonicYear = 0; metonicYear < 18; metonicYear++) {
|
||||
if (moladDay > inputDay - 74)
|
||||
break;
|
||||
moladHalakim += HALAKIM_PER_LUNAR_CYCLE * mpy[metonicYear];
|
||||
moladDay += Math.floor(moladHalakim / HALAKIM_PER_DAY);
|
||||
moladHalakim = moladHalakim % HALAKIM_PER_DAY;
|
||||
}
|
||||
}
|
||||
function MoladOfMetonicCycle() {
|
||||
var r1 = void 0, r2 = void 0, d1 = void 0, d2 = void 0;
|
||||
// Start with the time of the first molad after creation.
|
||||
r1 = NEW_MOON_OF_CREATION;
|
||||
// Calculate gMetonicCycle * HALAKIM_PER_METONIC_CYCLE. The upper 32
|
||||
// bits of the result will be in r2 and the lower 16 bits will be in r1.
|
||||
r1 += metonicCycle * (HALAKIM_PER_METONIC_CYCLE & 0xFFFF);
|
||||
r2 = r1 >> 16;
|
||||
r2 += metonicCycle * (HALAKIM_PER_METONIC_CYCLE >> 16 & 0xFFFF);
|
||||
// Calculate r2r1 / HALAKIM_PER_DAY. The remainder will be in r1,the
|
||||
// upper 16 bits of the quotient will be in d2 and the lower 16 bits
|
||||
// will be in d1.
|
||||
d2 = Math.floor(r2 / HALAKIM_PER_DAY);
|
||||
r2 -= d2 * HALAKIM_PER_DAY;
|
||||
r1 = r2 << 16 | r1 & 0xFFFF;
|
||||
d1 = Math.floor(r1 / HALAKIM_PER_DAY);
|
||||
r1 -= d1 * HALAKIM_PER_DAY;
|
||||
moladDay = d2 << 16 | d1;
|
||||
moladHalakim = r1;
|
||||
}
|
||||
function Tishri1(metonicYear, moladDay, moladHalakim) {
|
||||
var tishri1 = moladDay, dow = tishri1 % 7, leapYear = metonicYear == 2 || metonicYear == 5 || metonicYear == 7 || metonicYear == 10 || metonicYear == 13 || metonicYear == 16 || metonicYear == 18, lastWasLeapYear = metonicYear == 3 || metonicYear == 6 || metonicYear == 8 || metonicYear == 11 || metonicYear == 14 || metonicYear == 17 || metonicYear == 0;
|
||||
// Apply rules 2,3 and 4
|
||||
if (moladHalakim >= NOON || !leapYear && dow == TUES && moladHalakim >= AM3_11_20 || lastWasLeapYear && dow == MON && moladHalakim >= AM9_32_43) {
|
||||
tishri1++;
|
||||
dow++;
|
||||
if (dow == 7)
|
||||
dow = 0;
|
||||
}
|
||||
// Apply rule 1 after the others because it can cause an additional delay of one day.
|
||||
if (dow == WED || dow == FRI || dow == SUN) {
|
||||
tishri1++;
|
||||
}
|
||||
return tishri1;
|
||||
}
|
||||
var inputYear = inputDateOrYear;
|
||||
if ((typeof inputYear === "undefined" ? "undefined" : _typeof(inputYear)) === "object") {
|
||||
inputMonth = inputDateOrYear.getMonth() + 1;
|
||||
inputDate = inputDateOrYear.getDate();
|
||||
inputYear = inputDateOrYear.getFullYear();
|
||||
}
|
||||
SdnToHebrew(GregorianToSdn(inputYear, inputMonth, inputDate));
|
||||
return {
|
||||
year: hebrewYear,
|
||||
month: hebrewMonth,
|
||||
date: hebrewDate,
|
||||
month_name: hMonth[hebrewMonth - 1]
|
||||
};
|
||||
}
|
||||
|
||||
exports.hebrewDate = hebrewDate;
|
|
@ -2,25 +2,19 @@
|
|||
"id": "hebrew_calendar",
|
||||
"name": "Hebrew Calendar",
|
||||
"shortName": "HebCal",
|
||||
"version": "0.04",
|
||||
"description": "lists the date according to the hebrew calendar",
|
||||
"version": "0.06",
|
||||
"description": "lists the date & holidays according to the hebrew calendar",
|
||||
"icon": "app.png",
|
||||
"allow_emulator": false,
|
||||
"tags": "tool,locale",
|
||||
"tags": "clocks,tools",
|
||||
"custom": "customizer.html",
|
||||
"supports": [
|
||||
"BANGLEJS",
|
||||
"BANGLEJS2"
|
||||
],
|
||||
"type": "clock",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{
|
||||
"name": "hebrew_calendar.app.js",
|
||||
"url": "app.js"
|
||||
},
|
||||
{
|
||||
"name": "hebrewDate",
|
||||
"url": "hebrewDate.js"
|
||||
},
|
||||
{
|
||||
"name": "hebrew_calendar.img",
|
||||
"url": "app-icon.js",
|
||||
|
|
|
@ -1,2 +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 |
|
@ -54,18 +54,18 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) );
|
|||
if (strArr.length == 0) {
|
||||
Rx1 = 4;
|
||||
Rx2 = 6*4;
|
||||
Ry1 = 8*4;
|
||||
Ry2 = 8*4 + 3;
|
||||
Ry1 = 8*4 + R.y;
|
||||
Ry2 = 8*4 + 3 + R.y;
|
||||
} else if (strArr.length <= 4) {
|
||||
Rx1 = (strArr[strArr.length-1].length)%7*6*4 + 4 ;
|
||||
Rx2 = (strArr[strArr.length-1].length)%7*6*4 + 6*4;
|
||||
Ry1 = (strArr.length)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4);
|
||||
Ry2 = (strArr.length)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + 3;
|
||||
Ry1 = (strArr.length)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + R.y;
|
||||
Ry2 = (strArr.length)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + 3 + R.y;
|
||||
} else {
|
||||
Rx1 = (strArr[strArr.length-1].length)%7*6*4 + 4 ;
|
||||
Rx2 = (strArr[strArr.length-1].length)%7*6*4 + 6*4;
|
||||
Ry1 = (4)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4);
|
||||
Ry2 = (4)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + 3;
|
||||
Ry1 = (4)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + R.y;
|
||||
Ry2 = (4)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + 3 + R.y;
|
||||
}
|
||||
//print(Rx1,Rx2,Ry1, Ry2);
|
||||
return {x:Rx1,y:Ry1,x2:Rx2,y2:Ry2};
|
||||
|
@ -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.02",
|
||||
"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"
|
||||
```
|
||||
|
||||
|
|
|
@ -47,3 +47,4 @@
|
|||
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)
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "messages",
|
||||
"name": "Messages",
|
||||
"version": "0.34",
|
||||
"version": "0.35",
|
||||
"description": "App to display notifications from iOS and Gadgetbridge/Android",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# Mountain Pass Clock
|
||||
|
||||
Based on the Pebble watchface Weather Land.
|
||||
|
||||
Mountain Pass Clock changes depending on time (day/night) and weather conditions.
|
||||
|
||||
This clock requires Gadgetbridge and an app that Gadgetbridge can use to get the current weather from OpenWeatherMap (e.g. Weather Notification). To set up Gadgetbridge and weather, see https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Weather.
|
||||
|
||||
The scene will change according to the following OpenWeatherMap conditions: clear, cloudy, overcast, lightning, drizzle, rain, fog and snow. Each weather condition has night/day scenes.
|
||||
|
||||
If you choose not to set up weather (or are not connected to Gadgetbridge, for that matter), this clock will default to clear weather, and the scenery will still change from night to day.
|
||||
|
||||
Special thanks to Serj for testing this on the original Bangle.
|
||||
|
||||
## Images
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
|
@ -0,0 +1 @@
|
|||
atob("MDCEBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVAABVVVVVVVVVVVVVVVVVVVVVVVVVVVVQAzAFVVVVVVVVVVVVVVVVVVVVVVVVVVVQLzIFVVVVVVAFVVVVVVVVVVVVVVVVVVUB//MQVVVVVQAQVVVVVVVVVVVVVVVVVVAB8RMQBVVVVQIwBVVVVVVVVVVVVVVVVQAQAAABAFVVUB8zAFVVVVVVVVVVVVVVVQExAiAYEFVVAD/zIFVVVVVVVVVVVVVVUBMzgzg4gQVQAvMvMAVVVVVVVVVVVVVVAIMzMzM4iABQACABAABVVVVVVVVVVVVQAjMzMzMziCABgQAQAiAFVVVVVVVVVVVQEzMzMzMzOIECMyIyKIEFVVVVVVVVVVUBMzMzMzMzM4gQIzMzM4gQVVVVVVVVVVAIMzMzMzMzM4iACDMzM4iABVVVVVVVVQAjMzMzMzMzMziCATMzMziCAFVVVVVVVQEzMzMzMzMzMzOIECMzMzOIEFVVVVVVUBMzMzMzMzMzMzM4gQIzMzM4gQVVVVVVAIMiMzMzMzMzMiM4iACDMzMiiABVVVVQAjIAIzMzMzMzIAIziCATMzIAKCAFVVVQE4AACDMzMzM4AACDOIECM4AACIEFVVUAERAiARERERERAiAREREAERAiAREAVVUAAAEyEAAAAAAAEyEAAAAAAAEyEAAAVVVVUBMzIQVVVVUBMzIQVVVVUBMzIQVVVVVVABEREQBVVVABEREQBVVVABEREQBVVVVVAAAAAABVVVAAAAAABVVVAAAAAABVVVVVUGIiJgVVVVUGIiJgVVVVUGIiJgVVVVVVACIiIgBVVVACIiIgBVVVACIiIgBVVVVQAiIiInAFVQAiIiInAFVQAiIiInAFVVVQEiIiInYFVQEiIiInYFVQEiIiInYFVVUAAAAAAAAAUAAAAAAAAAUAAAAAAAAAVVVVAHd3dwBVVVAHd3dwBVVVAHd3dwBVVVVQB3d3d3AFVQB3d3d3AFVQB3d3d3AFVVVQYiIiInYFVQYiIiInYFVQYiIiInYFVVUAIiIiIicAUAIiIiIicAUAIiIiIicAVVUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVVUAAAARAAAAUAAAARAAAAUAAAARAAAAVVVVVVBEBVVVVVVVBEBVVVVVVVBEBVVVVVVVVVBEBVVVVVVVBEBVVVVVVVBEBVVVVVVVVVBEAFVVVVVVBEBVVVVVVQBEBVVVVVVVVVAAAAAAAAVVBEBVUAAAAAAABVVVVVVVUAASM/MyIQAAAAAAABIjPzMhAAVVVVVVABP///////MyIiIjP///////MQBVVVVQE////////////////////////zEFVVUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVVUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQ==")
|
|
@ -0,0 +1,350 @@
|
|||
var data = require("Storage").readJSON("mtnclock.json", 1) || {};
|
||||
|
||||
//seeded RNG to generate stars, snow, etc
|
||||
function sfc32(a, b, c, d) {
|
||||
return function() {
|
||||
a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0;
|
||||
var t = (a + b) | 0;
|
||||
a = b ^ b >>> 9;
|
||||
b = c + (c << 3) | 0;
|
||||
c = (c << 21 | c >>> 11);
|
||||
d = d + 1 | 0;
|
||||
t = t + d | 0;
|
||||
c = c + t | 0;
|
||||
return (t >>> 0) / 4294967296;
|
||||
};
|
||||
}
|
||||
|
||||
//scale x, y coords to screen
|
||||
function px(x) {
|
||||
return x*g.getWidth()/100;
|
||||
}
|
||||
|
||||
function py(y) {
|
||||
return y*g.getHeight()/100;
|
||||
}
|
||||
|
||||
function drawMtn(color, coord, dimen) {
|
||||
//scale mountains to different sizes
|
||||
g.setColor(color.mtn1).fillPoly([
|
||||
coord.x,coord.y,
|
||||
coord.x,coord.y+dimen.h,
|
||||
coord.x-dimen.w/2,coord.y+dimen.h
|
||||
]);
|
||||
g.setColor(color.mtn2).fillPoly([
|
||||
coord.x,coord.y,
|
||||
coord.x,coord.y+dimen.h,
|
||||
coord.x+dimen.w/2,coord.y+dimen.h
|
||||
]);
|
||||
}
|
||||
|
||||
function drawTree(color, coord, dimen) {
|
||||
//scale trees to different sizes
|
||||
g.setColor(color.tree1).fillPoly([
|
||||
coord.x,coord.y,
|
||||
coord.x-dimen.w/5,coord.y+dimen.h/4,
|
||||
coord.x-dimen.w/12,coord.y+dimen.h/4,
|
||||
coord.x-dimen.w/2.8,coord.y+1.95*dimen.h/4,
|
||||
coord.x-dimen.w/8,coord.y+1.95*dimen.h/4,
|
||||
coord.x-dimen.w/2,coord.y+3*dimen.h/4,
|
||||
coord.x,coord.y+3*dimen.h/4
|
||||
]);
|
||||
g.setColor(color.tree2).fillPoly([
|
||||
coord.x,coord.y,
|
||||
coord.x+dimen.w/5,coord.y+dimen.h/4,
|
||||
coord.x+dimen.w/12,coord.y+dimen.h/4,
|
||||
coord.x+dimen.w/2.8,coord.y+1.95*dimen.h/4,
|
||||
coord.x+dimen.w/8,coord.y+1.95*dimen.h/4,
|
||||
coord.x+dimen.w/2,coord.y+3*dimen.h/4,
|
||||
coord.x,coord.y+3*dimen.h/4
|
||||
]);
|
||||
g.setColor(color.tree3).fillRect(
|
||||
coord.x-dimen.w/12,coord.y+3*dimen.h/4,
|
||||
coord.x+dimen.w/12,coord.y+dimen.h
|
||||
);
|
||||
}
|
||||
|
||||
function drawSnow(color, coord, size) {
|
||||
g.setColor(color).drawLine(coord.x-px(size),coord.y-py(size),coord.x+px(size),coord.y+py(size));
|
||||
g.drawLine(coord.x-px(size),coord.y+py(size),coord.x+px(size),coord.y-py(size));
|
||||
g.drawLine(coord.x,coord.y+py(size),coord.x,coord.y-py(size));
|
||||
g.drawLine(coord.x-px(size),coord.y,coord.x+px(size),coord.y);
|
||||
}
|
||||
|
||||
function draw(color) {
|
||||
|
||||
var seed;
|
||||
var rand;
|
||||
|
||||
g.clear();
|
||||
//background
|
||||
g.setColor(color.bg1).fillRect(
|
||||
px(0),py(0),
|
||||
px(100),py(45)
|
||||
);
|
||||
g.setColor(color.bg2).fillRect(
|
||||
px(0),py(45),
|
||||
px(100),py(100)
|
||||
);
|
||||
//lightning
|
||||
if (color.ltn) {
|
||||
g.setColor(color.ltn).fillPoly([
|
||||
px(70),py(20),
|
||||
px(60),py(28),
|
||||
px(71),py(29),
|
||||
px(63),py(40),
|
||||
px(75),py(28),
|
||||
px(64),py(27)
|
||||
]);
|
||||
g.fillPoly([
|
||||
px(40),py(20),
|
||||
px(30),py(28),
|
||||
px(41),py(29),
|
||||
px(33),py(40),
|
||||
px(45),py(28),
|
||||
px(34),py(27)
|
||||
]);
|
||||
}
|
||||
//stars
|
||||
if (color.star) {
|
||||
seed = 4;
|
||||
rand = sfc32(0x9E3779B9, 0x243F6A88, 0xB7E15162, seed);
|
||||
for (let i = 0; i < 40; i++) {
|
||||
g.setColor(color.star).drawCircle(Math.floor(rand() * px(100)),Math.floor(rand() * py(33)),Math.floor(rand() * 2));
|
||||
}
|
||||
}
|
||||
//birds
|
||||
if (color.bird) {
|
||||
g.setColor(color.bird).fillCircle(px(17),py(12),px(5)).fillCircle(px(23),py(10),px(5));
|
||||
g.setColor(color.bg1).fillCircle(px(18),py(15),px(6)).fillCircle(px(24),py(13),px(6));
|
||||
g.setColor(color.bird).fillCircle(px(28),py(19),px(4)).fillCircle(px(33),py(19),px(4));
|
||||
g.setColor(color.bg1).fillCircle(px(28),py(21),px(5)).fillCircle(px(34),py(21),px(5));
|
||||
}
|
||||
//sun/moon
|
||||
if (color.sun) g.setColor(color.sun).fillCircle(px(65), py(22), py(20));
|
||||
//path
|
||||
g.setColor(color.path).fillPoly([
|
||||
px(60),py(44),
|
||||
px(39),py(55),
|
||||
px(72),py(57),
|
||||
px(30),py(100),
|
||||
px(70),py(100),
|
||||
px(78),py(55),
|
||||
px(46),py(53)
|
||||
]);
|
||||
//fog
|
||||
if (color.fog) {
|
||||
g.setColor(color.fog);
|
||||
for (let i = 1; i <= 47; i = i+2) {
|
||||
g.drawLine(px(0),py(i),px(100),py(i));
|
||||
}
|
||||
}
|
||||
//rain
|
||||
if (color.rain1) {
|
||||
g.setColor(color.rain1);
|
||||
for (let i = 0; i <= 6; i++) {
|
||||
g.drawLine(px(6+i*20),py(20),px(-14+i*20),py(45));
|
||||
}
|
||||
if (color.rain2) {
|
||||
for (let i = 0; i <= 6; i++) {
|
||||
g.drawLine(px(16+i*20),py(20),px(-4+i*20),py(45));
|
||||
}
|
||||
}
|
||||
}
|
||||
//snow
|
||||
if (color.snow) {
|
||||
seed = 11;
|
||||
rand = sfc32(0x9E3779B9, 0x243F6A88, 0xB7E15162, seed);
|
||||
for (let i = 0; i < 30; i++) {
|
||||
drawSnow(color.snow, {x:Math.floor(rand() * px(100)), y:(Math.floor(rand() * py(25))+py(20))}, Math.floor(rand() * 3));
|
||||
}
|
||||
}
|
||||
//mountains
|
||||
drawMtn({mtn1:color.mtn2, mtn2:color.mtn1}, {x:px(35), y:py(30)}, {w:px(38), h:py(17)});
|
||||
drawMtn({mtn1:color.mtn2, mtn2:color.mtn1}, {x:px(10), y:py(20)}, {w:px(50), h:py(30)});
|
||||
drawMtn({mtn1:color.mtn1, mtn2:color.mtn2}, {x:px(90), y:py(20)}, {w:px(70), h:py(30)});
|
||||
//lake
|
||||
g.setColor(color.lake).fillEllipse(px(-15), py(52), px(30), py(57));
|
||||
//trees
|
||||
drawTree({tree1:color.tree2, tree2:color.tree1, tree3:color.tree3}, {x:px(12),y:py(52)}, {w:px(13),h:py(13)});
|
||||
drawTree({tree1:color.tree2, tree2:color.tree1, tree3:color.tree3}, {x:px(48),y:py(52)}, {w:px(13),h:py(13)});
|
||||
drawTree({tree1:color.tree2, tree2:color.tree1, tree3:color.tree3}, {x:px(34),y:py(46)}, {w:px(6),h:py(6)});
|
||||
drawTree({tree1:color.tree1, tree2:color.tree2, tree3:color.tree3}, {x:px(70),y:py(46)}, {w:px(6),h:py(6)});
|
||||
drawTree({tree1:color.tree1, tree2:color.tree2, tree3:color.tree3}, {x:px(90),y:py(52)}, {w:px(13),h:py(13)});
|
||||
//clouds
|
||||
if (color.cloud1) {
|
||||
g.setColor(color.cloud1);
|
||||
if (color.cloud2) g.fillRect(0, 0, px(100), py(10));
|
||||
g.fillCircle(px(3), py(12), py(4));
|
||||
g.fillCircle(px(10), py(12), py(5));
|
||||
g.fillCircle(px(16), py(11), py(6));
|
||||
g.fillCircle(px(24), py(10), py(8));
|
||||
g.fillCircle(px(30), py(11), py(6));
|
||||
g.fillCircle(px(35), py(12), py(5));
|
||||
g.fillCircle(px(40), py(12), py(6));
|
||||
g.fillCircle(px(48), py(13), py(5));
|
||||
g.fillCircle(px(55), py(14), py(5));
|
||||
g.fillCircle(px(60), py(12), py(5));
|
||||
g.fillCircle(px(65), py(11), py(6));
|
||||
g.fillCircle(px(75), py(10), py(8));
|
||||
g.fillCircle(px(85), py(11), py(6));
|
||||
g.fillCircle(px(90), py(12), py(5));
|
||||
g.fillCircle(px(97), py(13), py(4));
|
||||
}
|
||||
|
||||
//clock text
|
||||
(color.clock == undefined) ? g.setColor(0xFFFF) : g.setColor(color.clock);
|
||||
g.setFont("Vector", py(20)).setFontAlign(-1, -1).drawString((require("locale").time(new Date(), 1).replace(" ", "")), px(2), py(67));
|
||||
g.setFont("Vector", py(10)).drawString(require('locale').dow(new Date(), 1)+" "+new Date().getDate()+" "+require('locale').month(new Date(), 1)+((data.temp == undefined) ? "" : " | "+require('locale').temp(Math.round(data.temp-273.15)).replace(".0", "")), px(2), py(87));
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
|
||||
function setWeather() {
|
||||
var a = {};
|
||||
//clear day/night is default weather
|
||||
if ((data.code >= 800 && data.code <=802) || data.code == undefined) {
|
||||
if (new Date().getHours() >= 7 && new Date().getHours() <= 19) {
|
||||
//day-clear
|
||||
a = {
|
||||
bg1:0x4FFF, bg2:0x03E0,
|
||||
sun:0xFD20,
|
||||
path:0x8200,
|
||||
mtn1:0x045f, mtn2:0x000F,
|
||||
lake:0x000F,
|
||||
tree1:0x07E0, tree2:0, tree3:0x7BE0,
|
||||
bird:0xFFFF
|
||||
};
|
||||
//day-cloudy
|
||||
if (data.code == 801 || data.code == 802) a.cloud1 = 0xFFFF;
|
||||
}
|
||||
else {
|
||||
//night-clear
|
||||
a = {
|
||||
bg1:0, bg2:0x0005,
|
||||
sun:0xC618,
|
||||
path:0,
|
||||
mtn1:0x0210, mtn2:0x0010,
|
||||
lake:0x000F,
|
||||
tree1:0x0200, tree2:0, tree3:0x59E0,
|
||||
star:0xFFFF
|
||||
};
|
||||
//night-cloudy
|
||||
if (data.code == 801 || data.code == 802) a.cloud1 = 0x4208;
|
||||
}
|
||||
}
|
||||
else if (((data.code >= 300) && (data.code < 600)) || ((data.code >= 200) && (data.code < 300)) || data.code == 803 || data.code == 804) {
|
||||
if (new Date().getHours() >= 7 && new Date().getHours() <= 19) {
|
||||
//day-overcast
|
||||
a = {
|
||||
bg1:0xC618, bg2:0x0200,
|
||||
path:0x3000,
|
||||
mtn1:0x3B38, mtn2:0x0005,
|
||||
lake:0x000F,
|
||||
tree1:0x03E0, tree2:0, tree3:0x59E0,
|
||||
cloud1:0x7BEF, cloud2:1
|
||||
};
|
||||
//day-lightning
|
||||
if (data.code >= 200 && data.code < 300) a.ltn = 0xFFFF;
|
||||
//day-drizzle
|
||||
if ((data.code >= 300 && data.code < 600) || (data.code >= 200 && data.code <= 202) || (data.code >= 230 && data.code <= 232)) a.rain1 = 0xFFFF;
|
||||
//day-rain
|
||||
if ((data.code >= 500 && data.code < 600) || (data.code >= 200 && data.code <= 202)) a.rain2 = 1;
|
||||
}
|
||||
else {
|
||||
//night-overcast
|
||||
a = {
|
||||
bg1:0, bg2:0x0005,
|
||||
path:0,
|
||||
mtn1:0x0010, mtn2:0x000F,
|
||||
lake:0x000F,
|
||||
tree1:0x0200, tree2:0, tree3:0x59E0,
|
||||
cloud1:0x4208, cloud2:1
|
||||
};
|
||||
//night-lightning
|
||||
if (data.code >= 200 && data.code < 300) a.ltn = 0xFFFF;
|
||||
//night-drizzle
|
||||
if ((data.code >= 300 && data.code < 600) || (data.code >= 200 && data.code <= 202) || (data.code >= 230 && data.code <= 232)) a.rain1 = 0xC618;
|
||||
//night-rain
|
||||
if ((data.code >= 500 && data.code < 600) || (data.code >= 200 && data.code <= 202)) rain2 = 1;
|
||||
}
|
||||
}
|
||||
else if ((data.code >= 700) && (data.code < 800)) {
|
||||
if (new Date().getHours() >= 7 && new Date().getHours() <= 19) {
|
||||
//day-fog
|
||||
a = {
|
||||
bg1:0xC618, bg2:0x0200,
|
||||
path:0x3000,
|
||||
mtn1:0x3B38, mtn2:0x0005,
|
||||
lake:0x000F,
|
||||
tree1:0x03E0, tree2:0, tree3:0x59E0,
|
||||
fog:0xFFFF
|
||||
};
|
||||
}
|
||||
else {
|
||||
//night-fog
|
||||
a = {
|
||||
bg1:0, bg2:0x0005,
|
||||
path:0,
|
||||
mtn1:0x0010, mtn2:0x000F,
|
||||
lake:0x000F,
|
||||
tree1:0x0200, tree2:0, tree3:0x59E0,
|
||||
fog:0x7BEF
|
||||
};
|
||||
}
|
||||
}
|
||||
else if ((data.code >= 600) && (data.code < 700)) {
|
||||
if (new Date().getHours() >= 7 && new Date().getHours() <= 19) {
|
||||
//day-snow
|
||||
a = {
|
||||
bg1:0, bg2:0x7BEF,
|
||||
path:0xC618,
|
||||
mtn1:0xFFFF, mtn2:0x7BEF,
|
||||
lake:0x07FF,
|
||||
tree1:0xC618, tree2:0xC618, tree3:0x59E0,
|
||||
cloud1:0x7BEF, cloud2:1,
|
||||
snow:0xFFFF,
|
||||
clock: 0
|
||||
};
|
||||
}
|
||||
else {
|
||||
//night-snow
|
||||
a = {
|
||||
bg1:0, bg2:0x0005,
|
||||
path:0,
|
||||
mtn1:0x0010, mtn2:0x000F,
|
||||
lake:0x000F,
|
||||
tree1:0x39E7, tree2:0x39E7, tree3:0x59E0,
|
||||
cloud1:0x4208, cloud2:1,
|
||||
snow:0xFFFF
|
||||
};
|
||||
}
|
||||
}
|
||||
draw(a);
|
||||
}
|
||||
|
||||
const _GB = global.GB;
|
||||
global.GB = (event) => {
|
||||
if (event.t==="weather") {
|
||||
data = event;
|
||||
require("Storage").write('mtnclock.json', event);
|
||||
setWeather();
|
||||
}
|
||||
if (_GB) setTimeout(_GB, 0, event);
|
||||
};
|
||||
|
||||
var drawTimeout;
|
||||
|
||||
//update watchface in next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
setWeather();
|
||||
queueDraw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
queueDraw();
|
||||
setWeather();
|
||||
Bangle.setUI("clock");
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"id": "mtnclock",
|
||||
"name": "Mountain Pass Clock",
|
||||
"shortName": "Mtn Clock",
|
||||
"version": "0.01",
|
||||
"description": "A clock that changes scenery based on time and weather.",
|
||||
"readme":"README.md",
|
||||
"icon": "app.png",
|
||||
"screenshots": [
|
||||
{"url":"screenshot1.png"},
|
||||
{"url":"screenshot2.png"},
|
||||
{"url":"screenshot3.png"},
|
||||
{"url":"screenshot4.png"},
|
||||
{"url":"screenshot5.png"}
|
||||
],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"mtnclock.app.js","url":"app.js"},
|
||||
{"name":"mtnclock.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"mtnclock.json"}]
|
||||
}
|
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.3 KiB |
|
@ -2,3 +2,5 @@
|
|||
0.02: Enhanced icon, make it bolder
|
||||
0.03: Fixed issue with defaulting back to London
|
||||
0.04: Fixed issue selecting Frankfurt not saved
|
||||
0.05: Fixed issue with back option
|
||||
0.06: renamed source files to match standard
|
||||
|
|
|
@ -55,7 +55,7 @@ function showMainMenu() {
|
|||
//console.log("showMainMenu");
|
||||
const mainmenu = {
|
||||
'': { 'title': 'My Location' },
|
||||
'<Back': ()=>{ load(); },
|
||||
'< Back': ()=>{ load(); },
|
||||
'City': {
|
||||
value: 0 | locations.indexOf(s.location),
|
||||
min: 0, max: locations.length - 1,
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
@ -1,17 +1,17 @@
|
|||
{ "id": "mylocation",
|
||||
"name": "My Location",
|
||||
"shortName":"My Location",
|
||||
"icon": "mylocation.png",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
"screenshots": [{"url":"screenshot_1.png"}],
|
||||
"version":"0.04",
|
||||
"version":"0.06",
|
||||
"description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README",
|
||||
"readme": "README.md",
|
||||
"tags": "tool,utility",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"mylocation.app.js","url":"mylocation.app.js"},
|
||||
{"name":"mylocation.img","url":"mylocation.icon.js","evaluate": true }
|
||||
{"name":"mylocation.app.js","url":"app.js"},
|
||||
{"name":"mylocation.img","url":"icon.js","evaluate": true }
|
||||
],
|
||||
"data": [
|
||||
{"name":"mylocation.json"}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -4,3 +4,6 @@
|
|||
0.04: Fix `getTimeToAlarm` to check for next dow if alarm.t lower currentTime.
|
||||
0.05: Export new functions (`newDefaultAlarm/Timer`), add Settings page
|
||||
0.06: Refactor some methods to library
|
||||
0.07: Update settings
|
||||
Correct `decodeTime(t)` to return a more likely expected time
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ Global Settings
|
|||
- `Unlock at Buzz` - If `Yes` the alarm/timer will unlock the watch
|
||||
- `Default Auto Snooze` - Default _Auto Snooze_ value for newly created alarms (_Alarms_ only)
|
||||
- `Default Snooze` - Default _Snooze_ value for newly created alarms/timers
|
||||
- `Default Repeat` - Default _Repeat_ value for newly created alarms (_Alarms_ only)
|
||||
- `Buzz Count` - The number of buzzes before the watch goes silent
|
||||
- `Buzz Interval` - The interval between one buzz and the next
|
||||
- `Default Alarm/Timer Pattern` - Default vibration pattern for newly created alarms/timers
|
||||
|
@ -38,7 +39,7 @@ Alarms are stored in an array in `sched.json`, and take the form:
|
|||
// WED = 8
|
||||
// THU = 16
|
||||
// FRI = 32
|
||||
// SAT = 64
|
||||
// SAT = 64
|
||||
|
||||
date : "2022-04-04", // OPTIONAL date for the alarm, in YYYY-MM-DD format
|
||||
// eg (new Date()).toISOString().substr(0,10)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
@ -59,8 +59,8 @@ exports.newDefaultAlarm = function () {
|
|||
let alarm = {
|
||||
t: 12 * 3600000, // Default to 12:00
|
||||
on: true,
|
||||
rp: false, // repeat not the default
|
||||
as: settings.defaultAutoSnooze || false,
|
||||
rp: settings.defaultRepeat,
|
||||
as: settings.defaultAutoSnooze,
|
||||
dow: 0b1111111,
|
||||
last: 0,
|
||||
vibrate: settings.defaultAlarmPattern,
|
||||
|
@ -95,6 +95,7 @@ exports.getSettings = function () {
|
|||
unlockAtBuzz: false,
|
||||
defaultSnoozeMillis: 600000, // 10 minutes
|
||||
defaultAutoSnooze: false,
|
||||
defaultRepeat: false,
|
||||
buzzCount: 10,
|
||||
buzzIntervalMillis: 3000, // 3 seconds
|
||||
defaultAlarmPattern: "..",
|
||||
|
@ -110,9 +111,9 @@ exports.setSettings = function(settings) {
|
|||
|
||||
// time in ms -> { hrs, mins }
|
||||
exports.decodeTime = function(t) {
|
||||
t = 0 | t; // sanitise
|
||||
let hrs = 0 | (t / 3600000);
|
||||
return { hrs: hrs, mins: Math.round((t - hrs * 3600000) / 60000) };
|
||||
t = Math.ceil(t / 60000); // sanitise to full minutes
|
||||
let hrs = 0 | (t / 60);
|
||||
return { hrs: hrs, mins: t - hrs * 60 };
|
||||
}
|
||||
|
||||
// time in { hrs, mins } -> ms
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "sched",
|
||||
"name": "Scheduler",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"description": "Scheduling library for alarms and timers",
|
||||
"icon": "app.png",
|
||||
"type": "scheduler",
|
||||
|
|
|
@ -36,6 +36,15 @@
|
|||
}
|
||||
},
|
||||
|
||||
/*LANG*/"Default Repeat": {
|
||||
value: settings.defaultRepeat,
|
||||
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
|
||||
onchange: v => {
|
||||
settings.defaultRepeat = v;
|
||||
require("sched").setSettings(settings);
|
||||
}
|
||||
},
|
||||
|
||||
/*LANG*/"Buzz Count": {
|
||||
value: settings.buzzCount,
|
||||
min: 5,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: Initial version
|