Merge branch 'master' of github.com:nxdefiant/BangleApps

pull/1769/head
Erik Andresen 2022-04-27 21:22:56 +02:00
commit d1d50bb965
118 changed files with 2258 additions and 838 deletions

View File

@ -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:

View File

@ -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

View File

@ -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();
}}

View File

@ -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",

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Fix the settings bug and some tweaking

View File

@ -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

View File

@ -15,7 +15,7 @@ function drawAlert(){
});
Bangle.buzz(400);
setTimeout(load, 10000);
setTimeout(load, 20000);
}
function run(){

View File

@ -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") || [];
};

View File

@ -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"}
]
}

View File

@ -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;

View File

@ -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

View File

@ -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.

View File

@ -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();

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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",

View File

@ -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);
})

View File

@ -0,0 +1 @@
0.01: New App!

View File

@ -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)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4kA///z3vy067fWlP7/t1r3f33vrU07tdxHKn1mnUEv92DgO75xUwmcziIAIiAWJgYXLiIuLC5dwC6xIJCwP/swAHsIXM5GZAA/HC4kCC42I7oAG3OXC4sgC4/e9v+939AQP7C40iC429C4Pu/wCC9YXHGAYXCzoXC3wCCC5AwDC4WZC4M+909AQM7C5AwCC4ef/tfn/dAQQXIDAQXDAA4XHJIYXBzNVAA9XC4K8CR4QwCC4Mzi93AA1xC4JBCC4QwCC4URABIXBCgaqDC5MWC4YRCI4hfC1VEmMX93u8MR/Pd6J3CCQJ3DR4YXF84XBR4ilDbAYXFagM3iP1R4YQBCwbXER4KHBRYaWBR4TWFYoQXCsKPKCIQuEC452BC6MxiPju8xiYZEI5gXBiLDHO5k+FgKSBCYM+C4inJAAMWmlEolDi8TC4jXJAAQWBAANBUoIXCCQQCBDgQXCn0z+gXDokRjwXCCIQXCDoQuHAAJlBC4ZBBC4kiC4ekC4lBI4ioDYQYXD1QXJXIR3DL4fjn4XBGIdBbQQXFYAYvE0gXEF5DXGC4IYBC4WhC5IFCC4dKC4QDB+KPDC4guCX4n6GAIXCmK/CuAXEAgQvEPAIXC14JDmAXDFwYXEeAKpCBAgXECwYXFeQVDC5AAFC400AwoXQAAwXJgYXWGBoWJAF4A=="))

105
apps/btmultimeter/app.js Normal file
View File

@ -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());
});

BIN
apps/btmultimeter/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -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}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -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

View File

@ -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",

5
apps/dragboard/ChangeLog Normal file
View File

@ -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.

16
apps/dragboard/README.md Normal file
View File

@ -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

BIN
apps/dragboard/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

261
apps/dragboard/lib.js Normal file
View File

@ -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);
}});
}); */
};

View File

@ -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"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -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

View File

@ -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(){

View File

@ -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",

View File

@ -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",

View File

@ -1 +1,2 @@
0.01: first release
0.02: updated dark theme bg2 color value

View File

@ -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
});

View File

Before

Width:  |  Height:  |  Size: 644 B

After

Width:  |  Height:  |  Size: 644 B

View File

@ -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}
]
}

View File

@ -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)

View File

@ -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

View File

@ -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
}
}

View File

@ -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);
}

View File

@ -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();
}
},
});
})

View File

@ -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}
]
}

View File

@ -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;

View File

@ -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"}],

View File

@ -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

View File

@ -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();

View File

@ -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"}]
}

43
apps/health/settings.js Normal file
View File

@ -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);
}
})

View File

@ -1 +1 @@
1.0: Initial release.
0.01: Initial release.

View File

@ -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": [

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -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 ![](<name>.png)
Displays the current hebrew calendar date and upcoming holidays alongside a clock
![](./HebrewCalendar-Screenshot.png)
## 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>

View File

@ -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");

View File

@ -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>

View File

@ -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);
}

View File

@ -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;

View File

@ -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",

View File

@ -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.

View File

@ -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.
![](key.png)
## Usage

BIN
apps/kbswipe/key.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -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);

View File

@ -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"}

View File

@ -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"
```

View File

@ -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)

View File

@ -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 = [

View File

@ -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",

21
apps/mtnclock/README.md Normal file
View File

@ -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
![](screenshot1.png)
![](screenshot2.png)
![](screenshot3.png)
![](screenshot4.png)
![](screenshot5.png)

View File

@ -0,0 +1 @@
atob("MDCEBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVAABVVVVVVVVVVVVVVVVVVVVVVVVVVVVQAzAFVVVVVVVVVVVVVVVVVVVVVVVVVVVQLzIFVVVVVVAFVVVVVVVVVVVVVVVVVVUB//MQVVVVVQAQVVVVVVVVVVVVVVVVVVAB8RMQBVVVVQIwBVVVVVVVVVVVVVVVVQAQAAABAFVVUB8zAFVVVVVVVVVVVVVVVQExAiAYEFVVAD/zIFVVVVVVVVVVVVVVUBMzgzg4gQVQAvMvMAVVVVVVVVVVVVVVAIMzMzM4iABQACABAABVVVVVVVVVVVVQAjMzMzMziCABgQAQAiAFVVVVVVVVVVVQEzMzMzMzOIECMyIyKIEFVVVVVVVVVVUBMzMzMzMzM4gQIzMzM4gQVVVVVVVVVVAIMzMzMzMzM4iACDMzM4iABVVVVVVVVQAjMzMzMzMzMziCATMzMziCAFVVVVVVVQEzMzMzMzMzMzOIECMzMzOIEFVVVVVVUBMzMzMzMzMzMzM4gQIzMzM4gQVVVVVVAIMiMzMzMzMzMiM4iACDMzMiiABVVVVQAjIAIzMzMzMzIAIziCATMzIAKCAFVVVQE4AACDMzMzM4AACDOIECM4AACIEFVVUAERAiARERERERAiAREREAERAiAREAVVUAAAEyEAAAAAAAEyEAAAAAAAEyEAAAVVVVUBMzIQVVVVUBMzIQVVVVUBMzIQVVVVVVABEREQBVVVABEREQBVVVABEREQBVVVVVAAAAAABVVVAAAAAABVVVAAAAAABVVVVVUGIiJgVVVVUGIiJgVVVVUGIiJgVVVVVVACIiIgBVVVACIiIgBVVVACIiIgBVVVVQAiIiInAFVQAiIiInAFVQAiIiInAFVVVQEiIiInYFVQEiIiInYFVQEiIiInYFVVUAAAAAAAAAUAAAAAAAAAUAAAAAAAAAVVVVAHd3dwBVVVAHd3dwBVVVAHd3dwBVVVVQB3d3d3AFVQB3d3d3AFVQB3d3d3AFVVVQYiIiInYFVQYiIiInYFVQYiIiInYFVVUAIiIiIicAUAIiIiIicAUAIiIiIicAVVUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVVUAAAARAAAAUAAAARAAAAUAAAARAAAAVVVVVVBEBVVVVVVVBEBVVVVVVVBEBVVVVVVVVVBEBVVVVVVVBEBVVVVVVVBEBVVVVVVVVVBEAFVVVVVVBEBVVVVVVQBEBVVVVVVVVVAAAAAAAAVVBEBVUAAAAAAABVVVVVVVUAASM/MyIQAAAAAAABIjPzMhAAVVVVVVABP///////MyIiIjP///////MQBVVVVQE////////////////////////zEFVVUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVVUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQ==")

350
apps/mtnclock/app.js Normal file
View File

@ -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");

BIN
apps/mtnclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -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"}]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -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

View File

@ -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,

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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"}

View File

@ -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)

View File

@ -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",

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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",

View File

@ -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,

View File

@ -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

View File

@ -3,7 +3,7 @@
"name":"Stardate Clock",
"shortName":"Stardate Clock",
"description": "A clock displaying a stardate along with a 'standard' digital/analog clock in LCARS design",
"version":"1.0",
"version":"0.01",
"icon": "app.png",
"type":"clock",
"tags": "clock",

1
apps/tapkb/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Initial version

Some files were not shown because too many files have changed in this diff Show More