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

pull/1794/head
Erik Andresen 2022-05-04 22:37:51 +02:00
commit 54f2017c23
69 changed files with 829 additions and 359 deletions

1
.gitignore vendored
View File

@ -11,3 +11,4 @@ tests/Layout/bin/tmp.*
tests/Layout/testresult.bmp
apps.local.json
_site
.jekyll-cache

View File

@ -186,7 +186,7 @@ The widget example is available in [`apps/_example_widget`](apps/_example_widget
Widgets are just small bits of code that run whenever an app that supports them
calls `Bangle.loadWidgets()`. If they want to display something in the 24px high
widget bar at the top of the screen they can add themselves to the global
widget bar at the top of the screen they can add themselves to the global
`WIDGETS` array with:
```
@ -226,10 +226,8 @@ and which gives information about the app for the Launcher.
"name":"Short Name", // for Bangle.js menu
"icon":"*myappid", // for Bangle.js menu
"src":"-myappid", // source file
"type":"widget/clock/app/bootloader", // optional, default "app"
// if this is 'widget' then it's not displayed in the menu
// if it's 'clock' then it'll be loaded by default at boot time
// if this is 'bootloader' then it's code that is run at boot time, but is not in a menu
"type":"widget/clock/app/bootloader/...", // optional, default "app"
// see 'type' in 'metadata.json format' below for more options/info
"version":"1.23",
// added by BangleApps loader on upload based on metadata.json
"files:"file1,file2,file3",
@ -252,17 +250,23 @@ and which gives information about the app for the Launcher.
"version": "0v01", // the version of this app
"description": "...", // long description (can contain markdown)
"icon": "icon.png", // icon in apps/
"screenshots" : [ { url:"screenshot.png" } ], // optional screenshot for app
"screenshots" : [ { "url":"screenshot.png" } ], // optional screenshot for app
"type":"...", // optional(if app) -
// 'app' - an application
// 'clock' - a clock - required for clocks to automatically start
// 'widget' - a widget
// 'launch' - replacement launcher app
// 'bootloader' - code that runs at startup only
// 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js'
// 'RAM' - code that runs and doesn't upload anything to storage
// 'launch' - replacement 'Launcher'
// 'textinput' - provides a 'textinput' library that allows text to be input on the Bangle
// 'scheduler' - provides 'sched' library and boot code for scheduling alarms/timers
// (currently only 'sched' app)
// 'notify' - provides 'notify' library for showing notifications
// 'locale' - provides 'locale' library for language-specific date/distance/etc
// (a version of 'locale' is included in the firmware)
"tags": "", // comma separated tag list for searching
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above)
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
"readme": "README.md", // if supplied, a link to a markdown-style text file
@ -415,7 +419,7 @@ Example `settings.js`
// make sure to enclose the function in parentheses
(function(back) {
let settings = require('Storage').readJSON('myappid.json',1)||{};
if (typeof settings.monkeys !== "number") settings.monkeys = 12; // default value
if (typeof settings.monkeys !== "number") settings.monkeys = 12; // default value
function save(key, value) {
settings[key] = value;
require('Storage').write('myappid.json', settings);

View File

@ -1,2 +1,4 @@
0.01: New App!
0.02: Fix the settings bug and some tweaking
0.03: Do not alarm while charging
0.04: Obey system quiet mode

View File

@ -1,13 +1,14 @@
# Activity reminder
A reminder to take short walks for the ones with a sedentary lifestyle.
The alert will popup only if you didn't take your short walk yet
The alert will popup only if you didn't take your short walk yet.
Different settings can be personalized:
- Enable : Enable/Disable the app
- Start hour: Hour to start the reminder
- End hour: Hour to end the reminder
- 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
- Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 60 min
Notice: If Dissmiss delay > Max inactivity then it will be equal Max inactivity
- Min steps: Minimal amount of steps to count as an activity

View File

@ -13,8 +13,11 @@ function drawAlert(){
}
load();
});
Bangle.buzz(400);
// Obey system quiet mode:
if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) {
Bangle.buzz(400);
}
setTimeout(load, 20000);
}

View File

@ -1,4 +1,5 @@
function run(){
if (Bangle.isCharging()) return;
var now = new Date();
var h = now.getHours();
if(h >= activityreminder.startHour && h < activityreminder.endHour){

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.02",
"version":"0.04",
"icon": "app.png",
"type": "app",
"tags": "tool,activity",

View File

@ -43,7 +43,7 @@
},
'Dismiss delay': {
value: settings.dismissDelayMin,
min: 5, max: 15,
min: 5, max: 60,
onchange: v => {
settings.dismissDelayMin = v;
require("activityreminder").writeSettings(settings);

View File

@ -25,3 +25,4 @@
0.24: Automatically save the alarm/timer when the user returns to the main menu using the back arrow
Add "Enable All", "Disable All" and "Remove All" actions
0.25: Fix redrawing selected Alarm/Timer entry inside edit submenu
0.26: Add support for Monday as first day of the week (#1780)

View File

@ -2,10 +2,14 @@ Bangle.loadWidgets();
Bangle.drawWidgets();
// An array of alarm objects (see sched/README.md)
let alarms = require("sched").getAlarms();
var alarms = require("sched").getAlarms();
// 0 = Sunday
// 1 = Monday
var firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0;
function getCurrentTime() {
let time = new Date();
var time = new Date();
return (
time.getHours() * 3600000 +
time.getMinutes() * 60000 +
@ -14,6 +18,9 @@ function getCurrentTime() {
}
function saveAndReload() {
// Before saving revert the dow to the standard format
alarms.forEach(a => a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek));
require("sched").setAlarms(alarms);
require("sched").reload();
}
@ -23,30 +30,32 @@ function showMainMenu() {
// Alarm img "\0"+atob("FBSBAABgA4YcMPDGP8Zn/mx/48//PP/zD/8A//AP/wD/8A//AP/wH/+D//w//8AAAADwAAYA")
const menu = {
'': { 'title': /*LANG*/'Alarms&Timers' },
/*LANG*/'< Back' : ()=>{load();},
/*LANG*/'New Alarm': ()=>editAlarm(-1),
/*LANG*/'New Timer': ()=>editTimer(-1)
/*LANG*/'< Back': () => { load(); },
/*LANG*/'New Alarm': () => editAlarm(-1),
/*LANG*/'New Timer': () => editTimer(-1)
};
alarms.forEach((alarm,idx)=>{
var type,txt; // a leading space is currently required (JS error in Espruino 2v12)
alarms.forEach((alarm, idx) => {
alarm.dow = handleFirstDayOfWeek(alarm.dow, firstDayOfWeek);
var type, txt; // a leading space is currently required (JS error in Espruino 2v12)
if (alarm.timer) {
type = /*LANG*/"Timer";
txt = " "+require("sched").formatTime(alarm.timer);
txt = " " + require("sched").formatTime(alarm.timer);
} else {
type = /*LANG*/"Alarm";
txt = " "+require("sched").formatTime(alarm.t);
txt = " " + require("sched").formatTime(alarm.t);
}
if (alarm.rp) txt += "\0"+atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA=");
if (alarm.rp) txt += "\0" + atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA=");
// rename duplicate alarms
if (menu[type+txt]) {
if (menu[type + txt]) {
var n = 2;
while (menu[type+" "+n+txt]) n++;
txt = type+" "+n+txt;
} else txt = type+txt;
while (menu[type + " " + n + txt]) n++;
txt = type + " " + n + txt;
} else txt = type + txt;
// add to menu
menu[txt] = {
value : "\0"+atob(alarm.on?"EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g":"EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"),
onchange : function() {
value: "\0" + atob(alarm.on ? "EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g" : "EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"),
onchange: function () {
setTimeout(alarm.timer ? editTimer : editAlarm, 10, idx, alarm);
}
};
@ -69,25 +78,28 @@ function showMainMenu() {
function editDOW(dow, onchange) {
const menu = {
'': { 'title': /*LANG*/'Days of Week' },
/*LANG*/'< Back' : () => onchange(dow)
/*LANG*/'< Back': () => onchange(dow)
};
for (let i = 0; i < 7; i++) (i => {
let dayOfWeek = require("locale").dow({ getDay: () => i });
menu[dayOfWeek] = {
value: !!(dow&(1<<i)),
require("date_utils").dows(firstDayOfWeek).forEach((day, i) => {
menu[day] = {
value: !!(dow & (1 << (i + firstDayOfWeek))),
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
onchange: v => v ? dow |= 1<<i : dow &= ~(1<<i),
onchange: v => v ? (dow |= 1 << (i + firstDayOfWeek)) : (dow &= ~(1 << (i + firstDayOfWeek)))
};
})(i);
});
E.showMenu(menu);
}
function editAlarm(alarmIndex, alarm) {
let newAlarm = alarmIndex < 0;
let a = require("sched").newDefaultAlarm();
var newAlarm = alarmIndex < 0;
var a = require("sched").newDefaultAlarm();
a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek);
if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
if (alarm) Object.assign(a,alarm);
let t = require("sched").decodeTime(a.t);
if (alarm) Object.assign(a, alarm);
var t = require("sched").decodeTime(a.t);
const menu = {
'': { 'title': /*LANG*/'Alarm' },
@ -96,17 +108,17 @@ function editAlarm(alarmIndex, alarm) {
showMainMenu();
},
/*LANG*/'Hours': {
value: t.hrs, min : 0, max : 23, wrap : true,
onchange: v => t.hrs=v
value: t.hrs, min: 0, max: 23, wrap: true,
onchange: v => t.hrs = v
},
/*LANG*/'Minutes': {
value: t.mins, min : 0, max : 59, wrap : true,
onchange: v => t.mins=v
value: t.mins, min: 0, max: 59, wrap: true,
onchange: v => t.mins = v
},
/*LANG*/'Enabled': {
value: a.on,
format: v => v ? /*LANG*/"On" : /*LANG*/"Off",
onchange: v=>a.on=v
onchange: v => a.on = v
},
/*LANG*/'Repeat': {
value: a.rp,
@ -114,14 +126,14 @@ function editAlarm(alarmIndex, alarm) {
onchange: v => a.rp = v
},
/*LANG*/'Days': {
value: "SMTWTFS".split("").map((d,n)=>a.dow&(1<<n)?d:".").join(""),
value: decodeDOW(a.dow),
onchange: () => setTimeout(editDOW, 100, a.dow, d => {
a.dow = d;
a.t = require("sched").encodeTime(t);
editAlarm(alarmIndex, a);
})
},
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v),
/*LANG*/'Auto Snooze': {
value: a.as,
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
@ -156,11 +168,11 @@ function saveAlarm(newAlarm, alarmIndex, a, t) {
}
function editTimer(alarmIndex, alarm) {
let newAlarm = alarmIndex < 0;
let a = require("sched").newDefaultTimer();
var newAlarm = alarmIndex < 0;
var a = require("sched").newDefaultTimer();
if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
if (alarm) Object.assign(a,alarm);
let t = require("sched").decodeTime(a.timer);
if (alarm) Object.assign(a, alarm);
var t = require("sched").decodeTime(a.timer);
const menu = {
'': { 'title': /*LANG*/'Timer' },
@ -169,26 +181,26 @@ function editTimer(alarmIndex, alarm) {
showMainMenu();
},
/*LANG*/'Hours': {
value: t.hrs, min : 0, max : 23, wrap : true,
onchange: v => t.hrs=v
value: t.hrs, min: 0, max: 23, wrap: true,
onchange: v => t.hrs = v
},
/*LANG*/'Minutes': {
value: t.mins, min : 0, max : 59, wrap : true,
onchange: v => t.mins=v
value: t.mins, min: 0, max: 59, wrap: true,
onchange: v => t.mins = v
},
/*LANG*/'Enabled': {
value: a.on,
format: v => v ? /*LANG*/"On" : /*LANG*/"Off",
onchange: v => a.on = v
},
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v),
};
menu[/*LANG*/"Cancel"] = () => showMainMenu();
if (!newAlarm) {
menu[/*LANG*/"Delete"] = function() {
alarms.splice(alarmIndex,1);
menu[/*LANG*/"Delete"] = function () {
alarms.splice(alarmIndex, 1);
saveAndReload();
showMainMenu();
};
@ -210,6 +222,26 @@ function saveTimer(newAlarm, alarmIndex, a, t) {
saveAndReload();
}
function handleFirstDayOfWeek(dow, firstDayOfWeek) {
if (firstDayOfWeek == 1) {
if ((dow & 1) == 1) {
// By default 1 = Sunday.
// Here the week starts on Monday and Sunday is ON so move Sunday to 128.
dow += 127;
} else if ((dow & 128) == 128) {
dow -= 127;
}
}
return dow;
}
function decodeDOW(dow) {
return require("date_utils")
.dows(firstDayOfWeek, 2)
.map((day, index) => dow & (1 << (index + firstDayOfWeek)) ? day : "_")
.join("");
}
function enableAll(on) {
E.showPrompt(/*LANG*/"Are you sure?", {
title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All"

View File

@ -2,7 +2,7 @@
"id": "alarm",
"name": "Alarms & Timers",
"shortName": "Alarms",
"version": "0.25",
"version": "0.26",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm,widget",

View File

@ -3,7 +3,7 @@
"name": "HRM Accelerometer event recorder",
"shortName": "HRM ACC recorder",
"version": "0.01",
"type": "ram",
"type": "RAM",
"description": "Record HRM and accelerometer events in high resolution to CSV files in your browser",
"icon": "app.png",
"tags": "debug",

View File

@ -115,7 +115,23 @@ E.on('notify',msg=>{
// could also use NRF.ancsGetAppInfo(msg.appId) here
};
var unicodeRemap = {
'2019':"'"
'2019':"'",
'260':"A",
'261':"a",
'262':"C",
'263':"c",
'280':"E",
'281':"e",
'321':"L",
'322':"l",
'323':"N",
'324':"n",
'346':"S",
'347':"s",
'377':"Z",
'378':"z",
'379':"Z",
'380':"z",
};
var replacer = ""; //(n)=>print('Unknown unicode '+n.toString(16));
//if (appNames[msg.appId]) msg.a

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Introduced settings to customize the layout and functionality of the keyboard.

View File

@ -2,6 +2,17 @@
A library that provides an on-screen keyboard for text input.
## Settings
Text size - small or big text font. Default=Big. Suggested=Small.
Offset keyboard - display the keyboard on top, making it faster to see what character you have selected. Default=No. Suggested=Yes.
Loop around - should the keyboard highlight loop around when going past the edges? Default=Yes. Suggested=No.
One-to-one input and release to select - should the input correspond directly to discrete areas on the screen, instead of being handled by scaled relative changes in position on swipes? Default=No. Suggested=Yes.
Speed scaling - how much should a swipe move the highligt on the keyboard? Higher number corresponds to slower movement. Not applicable if using one-to-one input. Default=24. Suggested=15.
## Usage
In your app's metadata, add:

View File

@ -69,13 +69,24 @@ var KEYEXTRA = [
String.fromCharCode(27,91,53,126), // 0x84 page up
String.fromCharCode(27,91,54,126), // 0x85 page down
];
var settings = Object.assign({
// default values
textSize: 1,
offsetKeyboard: 0,
loopAround: 1,
oneToOne: 0,
speedScaling: 24
}, require('Storage').readJSON("kbtouch.settings.json", true) || {});
// state
const R = Bangle.appRect;
var kbx = 0, kby = 0, kbdx = 0, kbdy = 0, kbShift = false, flashToggle = false;
const PX=12, PY=16, DRAGSCALE=24;
var xoff = 3, yoff = g.getHeight()-PY*4;
const PX=12, PY=16, DRAGSCALE=settings.speedScaling;
var xoff = 3, yoff = g.getHeight()-PY*(4+5*settings.offsetKeyboard);
function draw() {
"ram";
var map = kbShift ? KEYMAPUPPER : KEYMAPLOWER;
//g.drawImage(KEYIMG,0,yoff);
g.reset().setFont("6x8:2");
@ -88,9 +99,9 @@ function draw() {
g.drawString(map[1],xoff,yoff+PY);
g.drawString(map[2],xoff,yoff+PY*2);
g.drawString(map[3],xoff,yoff+PY*3);
var l = g.setFont("6x8:4").wrapString(text+(flashToggle?"_":" "), R.w-8);
if (l.length>2) l=l.slice(-2);
g.drawString(l.join("\n"),R.x+4,R.y+4);
var l = g.setFont(settings.textSize ? "6x8:4":"6x8:2").wrapString(text+(flashToggle?"_":" "), R.w-8);
if (l.length>2+2*settings.textSize) l=l.slice(-(2+2*settings.textSize));
g.drawString(l.join("\n"),R.x+4,R.y+4 +82*settings.offsetKeyboard);
g.flip();
}
@ -104,24 +115,49 @@ function draw() {
return new Promise((resolve,reject) => {
Bangle.setUI({mode:"custom", drag:e=>{
kbdx += e.dx;
kbdy += e.dy;
var dx = Math.round(kbdx/DRAGSCALE), dy = Math.round(kbdy/DRAGSCALE);
kbdx -= dx*DRAGSCALE;
kbdy -= dy*DRAGSCALE;
if (dx || dy) {
kbx = (kbx+dx+15)%15;
kby = (kby+dy+4)%4;
if (settings.oneToOne) {
kbx = Math.max(Math.min(Math.floor((e.x-16) / (6*2)) , 13) , 0);
kby = Math.max(Math.min(Math.floor((e.y-120) / (8*2)) , 3) , 0);
//print(e.y, kby, e.x, kbx);
}
if (!settings.oneToOne) {
kbdx += e.dx;
kbdy += e.dy;
var dx = Math.round(kbdx/DRAGSCALE), dy = Math.round(kbdy/DRAGSCALE);
kbdx -= dx*DRAGSCALE;
kbdy -= dy*DRAGSCALE;
if (dx || dy) {
if (settings.loopAround) {
kbx = (kbx+dx+15)%15;
kby = (kby+dy+4)%4;
} else {
kbx = Math.max(Math.min((kbx+dx),13),0);
kby = Math.max(Math.min((kby+dy),3),0);
}
}
}
draw();
if (!e.b && e.y>Bangle.appRect.y && settings.oneToOne /*&& settings.releaseToSelect*/) {
var map = kbShift ? KEYMAPUPPER : KEYMAPLOWER;
var ch = map[kby][kbx];
if (ch=="\2") kbShift=!kbShift;
else if (ch=="\b") text = text.slice(0,-1);
else text += ch;
Bangle.buzz(20);
draw();
}
},touch:()=>{
var map = kbShift ? KEYMAPUPPER : KEYMAPLOWER;
var ch = map[kby][kbx];
if (ch=="\2") kbShift=!kbShift;
else if (ch=="\b") text = text.slice(0,-1);
else text += ch;
Bangle.buzz(20);
draw();
if ( !settings.oneToOne /*|| !settings.releaseToSelect*/) {
var map = kbShift ? KEYMAPUPPER : KEYMAPLOWER;
var ch = map[kby][kbx];
if (ch=="\2") kbShift=!kbShift;
else if (ch=="\b") text = text.slice(0,-1);
else text += ch;
Bangle.buzz(20);
draw();
}
},back:()=>{
clearInterval(flashInterval);
Bangle.setUI();

View File

@ -1,6 +1,6 @@
{ "id": "kbtouch",
"name": "Touch keyboard",
"version":"0.01",
"version":"0.02",
"description": "A library for text input via onscreen keyboard",
"icon": "app.png",
"type":"textinput",
@ -9,6 +9,7 @@
"screenshots": [{"url":"screenshot.png"}],
"readme": "README.md",
"storage": [
{"name":"textinput","url":"lib.js"}
{"name":"textinput","url":"lib.js"},
{"name":"kbtouch.settings.js","url":"settings.js"}
]
}

59
apps/kbtouch/settings.js Normal file
View File

@ -0,0 +1,59 @@
(function(back) {
function settings() {
let settings = require('Storage').readJSON("kbtouch.settings.json", true) || {};
if (settings.textSize===undefined) settings.textSize=1;
if (settings.offsetKeyboard===undefined) settings.offsetKeyboard=0;
if (settings.loopAround===undefined) settings.loopAround=1;
if (settings.oneToOne===undefined) settings.oneToOne=0;
if (settings.speedScaling===undefined) settings.speedScaling=24;
return settings;
}
function updateSetting(setting, value) {
let settings = require('Storage').readJSON("kbtouch.settings.json", true) || {};
settings[setting] = value;
require('Storage').writeJSON("kbtouch.settings.json", settings);
}
var mainmenu = {
"" : { "title" : /*LANG*/"Touch Keyboard" },
"< Back" : back,
/*LANG*/'Text size': {
value: settings().textSize,
min: 0, max: 1,
format: v => [/*LANG*/"Small",/*LANG*/"Big"][v],
onchange: v => updateSetting("textSize", v)
},
/*LANG*/'Offset keyboard': {
value: settings().offsetKeyboard,
min: 0, max: 1,
format: v => [/*LANG*/"No",/*LANG*/"Yes"][v],
onchange: v => updateSetting("offsetKeyboard", v)
},
/*LANG*/'Loop around': {
value: settings().loopAround,
min: 0, max: 1,
format: v => [/*LANG*/"No",/*LANG*/"Yes"][v],
onchange: v => updateSetting("loopAround", v)
},
/*LANG*/'One-to-one input and release to select': {
value: settings().oneToOne,
min: 0, max: 1,
format: v => [/*LANG*/"No",/*LANG*/"Yes"][v],
onchange: v => updateSetting("oneToOne", v)
},
/*LANG*/'Speed scaling': {
value: settings().speedScaling,
min: 1, max: 24, step : 1,
format: v => v,
onchange: v => updateSetting("speedScaling", v)
}
///*LANG*/'Release to select': {
// value: 1|settings().fontSize,
// min: 0, max: 1,
// format: v => [/*LANG*/"No",/*LANG*/"Yes"][v],
// onchange: v => updateSetting("releaseToSelect", v)
//}
};
E.showMenu(mainmenu);
})

View File

@ -4,7 +4,7 @@
"version": "0.02",
"description": "Replace Bangle.js 2's menus with a version that contains smaller text",
"icon": "app.png",
"type": "boot",
"type": "bootloader",
"tags": "system",
"supports": ["BANGLEJS2"],
"storage": [

View File

@ -9,7 +9,7 @@
{"url":"screenshot_b1_dark.png"},{"url":"screenshot_b1_edit.png"},{"url":"screenshot_b1_light.png"},
{"url":"screenshot_b2_dark.png"},{"url":"screenshot_b2_edit.png"},{"url":"screenshot_b2_light.png"}
],
"type": "boot",
"type": "bootloader",
"tags": "system",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [

View File

@ -5,7 +5,7 @@
"icon": "mmind.png",
"version":"0.01",
"description": "This is the classic game for masterminds",
"type": "game",
"type": "app",
"tags": "mastermind, game, classic",
"readme":"README.md",
"supports": ["BANGLEJS2"],

View File

@ -4,7 +4,7 @@
"version": "0.01",
"description": "A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.",
"icon": "mystic-dock.png",
"type": "dock",
"type": "app",
"tags": "dock",
"supports": ["BANGLEJS"],
"readme": "README.md",

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Fix true wind computation, add swipe gesture to pause GPS

View File

@ -14,7 +14,9 @@ additionally displayed in red. In this mode, the speed over ground in knots is
## Controls
There are no controls in the main app, but there are two settings in the settings app that can be changed:
In the main app, when true wind mode is enabled (see below), swiping left on the screen will temporarily disable GPS (to preserve battery); a small
red satellite symbol will appear on the bottom right. Swiping right will turn GPS back on.
The settings app provides the following two settings:
* True wind: enables or disables true wind calculations; enabling this will turn on GPS inside the app
* Mounting angle: mounting relative to the boat of the wind instrument (in degrees)

View File

@ -1,16 +1,20 @@
OW_CHAR_UUID = '0000cc91-0000-1000-8000-00805f9b34fb';
require("Font7x11Numeric7Seg").add(Graphics);
gatt = {};
cx = g.getWidth()/2;
cy = 24+(g.getHeight()-24)/2;
w = (g.getWidth()-24)/2;
gps_course = { spd: 0 };
var gatt = {};
var cx = g.getWidth()/2;
var cy = 24+(g.getHeight()-24)/2;
var w = (g.getWidth()-24)/2;
var y1 = 24;
var y2 = g.getHeight()-1;
var gps_course = { spd: 0 };
var course_marker_len = g.getWidth()/4;
var settings = require("Storage").readJSON('openwindsettings.json', 1) || {};
i = 0;
hullpoly = [];
var pause_gps = false;
var i = 0;
var hullpoly = [];
for (y=-1; y<=1; y+=0.1) {
hullpoly[i++] = cx - (y<0 ? 1+y*0.15 : (Math.sqrt(1-0.7*y*y)-Math.sqrt(0.3))/(1-Math.sqrt(0.3)))*w*0.3;
hullpoly[i++] = cy - y*w*0.7;
@ -22,21 +26,22 @@ for (y=1; y>=-1; y-=0.1) {
function wind_updated(ev) {
if (ev.target.uuid == "0xcc91") {
awa = settings.mount_angle-ev.target.value.getInt16(1, true)*0.1;
awa = settings.mount_angle+ev.target.value.getInt16(1, true)*0.1;
if (awa<0) awa += 360;
aws = ev.target.value.getInt16(3, true)*0.01;
// console.log(awa, aws);
//console.log(awa, aws);
if (gps_course.spd > 0) {
wv = { // wind vector (in fixed reference frame)
lon: Math.sin(Math.PI*(gps_course.course+awa)/180)*aws,
lat: Math.cos(Math.PI*(gps_course.course+awa)/180)*aws
wv = { // wind vector (in "earth" reference frame)
vlon: Math.sin(Math.PI*(gps_course.course+(awa+180))/180)*aws,
vlat: Math.cos(Math.PI*(gps_course.course+(awa+180))/180)*aws
};
twv = { lon: wv.lon+gps_course.lon, lat: wv.lat+gps_course.lat };
tws = Math.sqrt(Math.pow(twv.lon,2)+Math.pow(twv.lat, 2));
twa = Math.atan2(twv.lat, twv.lon)*180/Math.PI-gps_course.course;
twv = { vlon: wv.vlon+gps_course.vlon, vlat: wv.vlat+gps_course.vlat };
tws = Math.sqrt(Math.pow(twv.vlon,2)+Math.pow(twv.vlat, 2));
twa = 180+Math.atan2(twv.vlon, twv.vlat)*180/Math.PI-gps_course.course;
if (twa<0) twa += 360;
if (twa>360) twa -=360;
}
else {
else {
tws = -1;
twa = 0;
}
@ -57,34 +62,37 @@ function draw_compass(awa, aws, twa, tws) {
a = i*Math.PI/2+Math.PI/4;
g.drawLineAA(cx+Math.cos(a)*w*0.85, cy+Math.sin(a)*w*0.85, cx+Math.cos(a)*w*0.99, cy+Math.sin(a)*w*0.99);
}
g.setColor(0, 1, 0).fillCircle(cx+Math.sin(Math.PI*awa/180)*w*0.9, cy+Math.cos(Math.PI*awa/180)*w*0.9, w*0.1);
g.setColor(0, 1, 0).fillCircle(cx+Math.sin(Math.PI*awa/180)*w*0.9, cy-Math.cos(Math.PI*awa/180)*w*0.9, w*0.1);
if (tws>0) g.setColor(1, 0, 0).fillCircle(cx+Math.sin(Math.PI*twa/180)*w*0.9, cy+Math.cos(Math.PI*twa/180)*w*0.9, w*0.1);
g.setColor(0, 1, 0).setFont("7x11Numeric7Seg",w*0.06);
g.setFontAlign(0, 0, 0).drawString(aws.toFixed(1), cx, cy-0.32*w);
if (tws>0) g.setColor(1, 0, 0).drawString(tws.toFixed(1), cx, cy+0.32*w);
if (settings.truewind && typeof gps_course.spd!=='undefined') {
spd = gps_course.spd/1.852;
g.setColor(g.theme.fg).setFont("7x11Numeric7Seg", w*0.03).setFontAlign(-1, 1, 0).drawString(spd.toFixed(1), 1, g.getHeight()-1);
if (!pause_gps) {
if (tws>0) g.setColor(1, 0, 0).drawString(tws.toFixed(1), cx, cy+0.32*w);
if (settings.truewind && gps_course.spd!=-1) {
spd = gps_course.spd/1.852;
g.setColor(g.theme.fg).setFont("7x11Numeric7Seg", w*0.03).setFontAlign(-1, 1, 0).drawString(spd.toFixed(1), 1, g.getHeight()-1);
}
}
if (pause_gps) g.setColor("#f00").drawImage(atob("DAwBEAKARAKQE4DwHkPqPRGKAEAA"),g.getWidth()-15, g.getHeight()-15);
}
function parseDevice(d) {
device = d;
console.log("Found device");
device.gatt.connect().then(function(ga) {
console.log("Connected");
gatt = ga;
return ga.getPrimaryService("cc90");
}).then(function(s) {
return s.getCharacteristic("cc91");
}).then(function(c) {
c.on('characteristicvaluechanged', (event)=>wind_updated(event));
return c.startNotifications();
}).then(function() {
console.log("Done!");
}).catch(function(e) {
console.log("ERROR"+e);
});}
device.gatt.connect().then(function(ga) {
console.log("Connected");
gatt = ga;
return ga.getPrimaryService("cc90");
}).then(function(s) {
return s.getCharacteristic("cc91");
}).then(function(c) {
c.on('characteristicvaluechanged', (event)=>wind_updated(event));
return c.startNotifications();
}).then(function() {
console.log("Done!");
}).catch(function(e) {
console.log("ERROR"+e);
});}
function connection_setup() {
NRF.setScan();
@ -96,8 +104,10 @@ if (settings.truewind) {
Bangle.on('GPS',function(fix) {
if (fix.fix && fix.satellites>3 && fix.speed>2) { // only uses fixes w/ more than 3 sats and speed > 2kph
gps_course =
{ lon: Math.sin(Math.PI*fix.course/180)*fix.speed/1.852,
lat: Math.cos(Math.PI*fix.course/180)*fix.speed/1.852,
{ vlon: Math.sin(Math.PI*fix.course/180)*fix.speed/1.852,
vlat: Math.cos(Math.PI*fix.course/180)*fix.speed/1.852,
lat: fix.lat,
lon: fix.lon,
spd: fix.speed,
course: fix.course
};
@ -107,6 +117,20 @@ if (settings.truewind) {
Bangle.setGPSPower(1, "app");
}
if (settings.truewind) {
Bangle.on("swipe", (d)=>{
if (d==-1 && !pause_gps) {
pause_gps = true;
Bangle.setGPSPower(0);
draw_compass(0, 0, 0, 0);
}
else if (d==1 && pause_gps) {
pause_gps = false;
Bangle.setGPSPower(1, "app");
draw_compass(0, 0, 0, 0);
}
});
}
Bangle.loadWidgets();
Bangle.drawWidgets();
draw_compass(0, 0, 0, 0);

View File

@ -1,7 +1,7 @@
{ "id": "openwind",
"name": "OpenWind",
"shortName":"OpenWind",
"version":"0.01",
"version":"0.02",
"description": "OpenWind",
"icon": "openwind.png",
"readme": "README.md",

View File

@ -4,7 +4,7 @@
"version": "0.02",
"description": "Replace the built in menu function. Supports Bangle.js 1 and Bangle.js 2.",
"icon": "icon.png",
"type": "boot",
"type": "bootloader",
"tags": "system",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",

View File

@ -1 +1,2 @@
0.01: Initial version
0.02: Moved settings from launcher to settings->apps menu

View File

@ -1 +0,0 @@
require("heatshrink").decompress(atob("kMigILIgPAAYMD/ADBwcGhkAwM5wcA/+2//Av/Rn/giFoyFggkUrFggEKlAkCiApCx+AAYNGoADBkU4AYMQj4DBvEICANkAoIPBgE2B4MAiMAH4MAwECAYNALYUgBIISCHYMYAoQWBAIMEgAYBAIMBwEDDQNgDwUf/4eBg4DCAA4"))

View File

@ -1,14 +1,15 @@
{ "id": "quicklaunch",
{
"id": "quicklaunch",
"name": "Quick Launch",
"icon": "app.png",
"version":"0.01",
"description": "Tap or swipe left/right/up/down on your clock face to launch up to five apps of your choice.",
"version":"0.02",
"description": "Tap or swipe left/right/up/down on your clock face to launch up to five apps of your choice. Configurations can be accessed through Settings->Apps.",
"type": "bootloader",
"tags": "tools, system",
"supports": ["BANGLEJS2"],
"storage": [
{"name":"quicklaunch.app.js","url":"app.js"},
{"name":"quicklaunch.boot.js","url":"boot.js"},
{"name":"quicklaunch.img","url":"app-icon.js","evaluate":true}
{"name":"quicklaunch.settings.js","url":"settings.js"},
{"name":"quicklaunch.boot.js","url":"boot.js"}
],
"data": [{"name":"quicklaunch.json"}]
}

View File

@ -1,3 +1,4 @@
(function(back) {
var settings = Object.assign(require("Storage").readJSON("quicklaunch.json", true) || {});
var apps = require("Storage").list(/\.info$/).map(app=>{var a=require("Storage").readJSON(app,1);return a&&{name:a.name,type:a.type,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="launch" || app.type=="clock" || !app.type));
@ -118,3 +119,4 @@ apps.forEach((a)=>{
});
showMainMenu();
});

1
apps/scicalc/ChangeLog Normal file
View File

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

24
apps/scicalc/README.md Normal file
View File

@ -0,0 +1,24 @@
# SciCalc
Simple scientific calculator. I needed one, so I wrote a basic one, no design frills. Input expressions are slightly post processed and then evaluated
by the JS interpreter.
## Usage
Buttons are arranged on 3 separate screens, swiping left or right switches between them. Swiping down has the same effect as hitting the "=" button.
## Features
The calculator supports the following operations:
* basic arithmetic: +, -, *, /, ^ (raise to a power), +/- (invert sign), 1/x (inverse), use of parentheses
* trigonometric fucntions: sin, cos, tan, asin, acos, atan
* exponential exp, natural logarithm log, pow function (this one takes 2 comma separated arguments)
* Pi is provided as a constant
* a memory button "M" stores or recalls the last result (after hitting the "=" button or swiping down)
![](scicalc_screenshot1.png)
![](scicalc_screenshot2.png)
![](scicalc_screenshot3.png)

1
apps/scicalc/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AJioAaF1wwSFzowRCQUZo4AWjIvVFy4ABF/4vXyGQAYov/R+sZFy8ZF6oAcF/4vvi4AeF/4SCjseAAMdAx8MAAYvVEAQABAx4v/R/TvvF96PUg8cAAMHd9QuCAAIv/R+rvvF96Pvd94vvR97vvF96Pvd94vvR97vsGDwuQGDouSAH4A/AGwA=="))

113
apps/scicalc/app.js Normal file
View File

@ -0,0 +1,113 @@
const W = g.getWidth();
const H = g.getHeight();
const dispH = H/5;
const butH = H-dispH;
const buttons = [[['7', '8', '9'],
['4', '5', '6'],
['1', '2', '3'],
['E', '0', '.']],
[['<', 'M', 'C'],
['+', '-', '*'],
['/', '(', ')'],
['^', ',', '=']],
[['Sin', 'Cos', 'Tan'],
['Asi', 'Aco', 'Ata'],
['Pi', '1/x', '+/-'],
['Log', 'Exp', 'Pow']
]];
var curPage = 0;
var inputStr = '';
var memory = '';
var qResult = false;
function drawPage (p) {
g.clearRect(0, dispH, W-1, H-1);
g.setFont('Vector', butH/5).setFontAlign(0, 0, 0).setColor(g.theme.fg);
for (x=0; x<3; ++x)
for (y=0; y<4; ++y)
g.drawString(buttons[p][y][x], (x+0.5)*W/3, dispH+(y+0.7)*butH/4);
g.setColor(0.5, 0.5, 0.5);
for (x=1; x<3; ++x) g.drawLine(x*W/3, dispH+0.2*butH/4-2, x*W/3, H-1);
for (y=1; y<4; ++y) g.drawLine(0, dispH+(y+0.2)*butH/4, W-1, dispH+(y+0.2)*butH/4);
g.setColor(g.theme.fg).drawLine(0, dispH+0.2*butH/4-2, W-1, dispH+0.2*butH/4-2);
}
function updateDisp(s, len) {
var fh = butH/5;
if (s.toString().length>len) s = s.toString().substr(0,len);
g.setFont("Vector", butH/5).setColor(g.theme.fg).setFontAlign(1, 0, 0);
while (g.stringWidth(s) > W-1) {
fh /= 1.05;
g.setFont("Vector", fh);
}
g.clearRect(0, 0, W-1, dispH-1).drawString(s, W-2, dispH/2);
g.setColor(g.theme.fg).drawLine(0, dispH+0.2*butH/4-2, W-1, dispH+0.2*butH/4-2);
}
function processInp (s) {
var idx = s.indexOf("^");
if (idx > 0) s = "Math.pow(" + s.slice(0,idx) + "," + s.slice(idx+1, s.length) + ")";
['Sin', 'Cos', 'Tan', 'Asin', 'Acos', 'Atan', 'Log', 'Exp', 'Pow'].forEach((x) => {
var i = s.indexOf(x);
while (i>-1) {
s = s.slice(0,i)+"Math."+s.slice(i,i+1).toLowerCase()+s.slice(i+1, s.length);
i = s.indexOf(x, i+6);
}
});
idx = s.indexOf('Pi');
if (idx>-1) s = s.slice(0,idx) + "Math.PI" + s.slice(idx+2, s.length);
idx = 0;
s.split('').forEach((x)=>{ if (x=='(') idx++; if (x==')') idx-- });
s += ')'.repeat(idx);
return s;
}
function compute() {
var res;
console.log(processInp(inputStr));
try { res = eval(processInp(inputStr)); }
catch(e) { res = "error"; }
inputStr = res;
qResult = true;
updateDisp(inputStr, 19);
}
function touchHandler(e, d) {
var x = Math.floor(d.x/(W/3));
var y = Math.floor((d.y-dispH-0.2*butH/4)/(butH/4));
var c = buttons[curPage][y][x];
if (c=="=") { // do the computation
compute();
return;
}
else if (c=="<" && inputStr.length>0) inputStr = inputStr.slice(0, -1); // delete last character
else if (c=='M' && qResult) memory = inputStr;
else if (c=='M') inputStr += memory;
else if (c=="C") inputStr = ''; // clear
else {
if ("Sin Cos Tan Log Exp Pow".indexOf(c)>-1 && c!='E') c += "(";
if ("Asi Aco Ata".indexOf(c)>-1) c += "n(";
if (c=='1/x') { inputStr = "1/("+inputStr+")"; compute(); return; }
if (c=='+/-') { inputStr = "-("+inputStr+")"; compute(); return; }
if (qResult && "+-*/^".indexOf(c)==-1) inputStr = c + inputStr + ")";
else inputStr += c;
}
qResult = false;
updateDisp(inputStr, 32);
}
function swipeHandler(e,d) {
curPage -= e;
if (curPage>buttons.length-1) curPage = 0;
if (curPage<0) curPage = buttons.length-1;
drawPage(curPage);
if (d==1) compute();
}
Bangle.on("touch", touchHandler);
Bangle.on("swipe", swipeHandler);
g.clear();
drawPage(curPage);

View File

@ -0,0 +1,16 @@
{ "id": "scicalc",
"name": "Scientific Calculator",
"shortName":"SciCalc",
"version":"0.01",
"description": "Scientific calculator",
"icon": "scicalc.png",
"screenshots" : [ { "url":"scicalc_screenshot1.png" }, { "url":"scicalc_screenshot2.png" }, { "url":"scicalc_screenshot3.png" } ],
"readme": "README.md",
"tags": "app,tool",
"allow_emulator": true,
"supports" : ["BANGLEJS2"],
"storage": [
{"name":"scicalc.app.js","url":"app.js"},
{"name":"scicalc.img","url":"app-icon.js","evaluate":true}
]
}

BIN
apps/scicalc/scicalc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -46,3 +46,5 @@
0.41: Stop users disabling all wake-up methods and locking themselves out (fix #1272)
0.42: Fix theme customizer on new Bangle 2 firmware
0.43: Add some Bangle 1 colours to theme customizer
0.44: Add "Start Week On X" option (#1780)
UI improvements to Locale and Date & Time menu

View File

@ -7,9 +7,9 @@ This is Bangle.js's settings menu
* **Beep** most Bangle.js do not have a speaker inside, but they can use the vibration motor to beep in different pitches. You can change the behaviour here to use a Piezo speaker if one is connected
* **Vibration** enable/disable the vibration motor
* **Quiet Mode** prevent notifications/alarms from vibrating/beeping/turning the screen on - see below
* **Locale** set time zone/whether the clock is 12/24 hour (for supported clocks)
* **Locale** set time zone, the time format (12/24h, for supported clocks) and the first day of the week
* **Select Clock** if you have more than one clock face, select the default one
* **Set Time** Configure the current time - Note that this can be done much more easily by choosing 'Set Time' from the App Loader
* **Date & Time** Configure the current time - Note that this can be done much more easily by choosing 'Set Time' from the App Loader
* **LCD** Configure settings about the screen. How long it stays on, how bright it is, and when it turns on - see below.
* **Theme** Adjust the colour scheme
* **Utils** Utilities - including resetting settings (see below)
@ -35,11 +35,15 @@ This is Bangle.js's settings menu
`Wake on Touch` actually uses the accelerometer, and you need to actually tap the display to wake Bangle.js.
* **Twist X** these options adjust the sensitivity of `Wake on Twist` to ensure Bangle.js wakes up with just the right amount of wrist movement.
## Locale
* **Time Zone** your current Time zone. This is usually set automatically by the App Loader
* **Time Format** whether you want a 24 or 12 hour clock. However not all clocks will honour this.
* **Start Week On** start the displayed week on Sunday, or Monday. This currently only applies to the Alarm app.
## Quiet Mode
Quiet Mode is a hint to apps and widgets that you do not want to be disturbed.
Quiet Mode is a hint to apps and widgets that you do not want to be disturbed.
The exact effects depend on the app. In general the watch will not wake up by itself, but will still respond to button presses.
* **Quiet Mode**

View File

@ -1,7 +1,7 @@
{
"id": "setting",
"name": "Settings",
"version": "0.43",
"version": "0.44",
"description": "A menu for setting up Bangle.js",
"icon": "settings.png",
"tags": "tool,system",

View File

@ -37,18 +37,19 @@ function internalToG(u) {
function resetSettings() {
settings = {
ble: true, // Bluetooth enabled by default
blerepl: true, // Is REPL on Bluetooth - can Espruino IDE be used?
log: false, // Do log messages appear on screen?
quiet: 0, // quiet mode: 0: off, 1: priority only, 2: total silence
timeout: 10, // Default LCD timeout in seconds
vibrate: true, // Vibration enabled by default. App must support
beep: BANGLEJS2?true:"vib", // Beep enabled by default. App must support
timezone: 0, // Set the timezone for the device
HID: false, // BLE HID mode, off by default
clock: null, // a string for the default clock's name
"12hour" : false, // 12 or 24 hour clock?
brightness: 1, // LCD brightness from 0 to 1
ble: true, // Bluetooth enabled by default
blerepl: true, // Is REPL on Bluetooth - can Espruino IDE be used?
log: false, // Do log messages appear on screen?
quiet: 0, // quiet mode: 0: off, 1: priority only, 2: total silence
timeout: 10, // Default LCD timeout in seconds
vibrate: true, // Vibration enabled by default. App must support
beep: BANGLEJS2 ? true : "vib", // Beep enabled by default. App must support
timezone: 0, // Set the timezone for the device
HID: false, // BLE HID mode, off by default
clock: null, // a string for the default clock's name
"12hour" : false, // 12 or 24 hour clock?
firstDayOfWeek: 0, // 0 -> Sunday (default), 1 -> Monday
brightness: 1, // LCD brightness from 0 to 1
// welcomed : undefined/true (whether welcome app should show)
options: {
wakeOnBTN1: true,
@ -94,7 +95,7 @@ function showSystemMenu() {
/*LANG*/'LCD': ()=>showLCDMenu(),
/*LANG*/'Locale': ()=>showLocaleMenu(),
/*LANG*/'Select Clock': ()=>showClockMenu(),
/*LANG*/'Set Time': ()=>showSetTimeMenu()
/*LANG*/'Date & Time': ()=>showSetTimeMenu()
};
return E.showMenu(mainmenu);
@ -478,6 +479,7 @@ function showLocaleMenu() {
'< Back': ()=>showSystemMenu(),
/*LANG*/'Time Zone': {
value: settings.timezone,
format: v => (v > 0 ? "+" : "") + v,
min: -11,
max: 13,
step: 0.5,
@ -486,13 +488,23 @@ function showLocaleMenu() {
updateSettings();
}
},
/*LANG*/'Clock Style': {
/*LANG*/'Time Format': {
value: !!settings["12hour"],
format: v => v ? "12hr" : "24hr",
format: v => v ? "12h" : "24h",
onchange: v => {
settings["12hour"] = v;
updateSettings();
}
},
/*LANG*/'Start Week On': {
value: settings["firstDayOfWeek"] || 0,
min: 0, // Sunday
max: 1, // Monday
format: v => require("date_utils").dow(v, 1),
onchange: v => {
settings["firstDayOfWeek"] = v;
updateSettings();
},
}
};
return E.showMenu(localemenu);
@ -606,11 +618,34 @@ function showClockMenu() {
function showSetTimeMenu() {
d = new Date();
const timemenu = {
'': { 'title': /*LANG*/'Set Time' },
'': { 'title': /*LANG*/'Date & Time' },
'< Back': function () {
setTime(d.getTime() / 1000);
showSystemMenu();
},
/*LANG*/'Day': {
value: d.getDate(),
onchange: function (v) {
this.value = ((v+30)%31)+1;
d.setDate(this.value);
}
},
/*LANG*/'Month': {
value: d.getMonth() + 1,
format: v => require("date_utils").month(v),
onchange: function (v) {
this.value = ((v+11)%12)+1;
d.setMonth(this.value - 1);
}
},
/*LANG*/'Year': {
value: d.getFullYear(),
min: 2019,
max: 2100,
onchange: function (v) {
d.setFullYear(v);
}
},
/*LANG*/'Hour': {
value: d.getHours(),
onchange: function (v) {
@ -631,28 +666,6 @@ function showSetTimeMenu() {
this.value = (v+60)%60;
d.setSeconds(this.value);
}
},
/*LANG*/'Date': {
value: d.getDate(),
onchange: function (v) {
this.value = ((v+30)%31)+1;
d.setDate(this.value);
}
},
/*LANG*/'Month': {
value: d.getMonth() + 1,
onchange: function (v) {
this.value = ((v+11)%12)+1;
d.setMonth(this.value - 1);
}
},
/*LANG*/'Year': {
value: d.getFullYear(),
min: 2019,
max: 2100,
onchange: function (v) {
d.setFullYear(v);
}
}
};
return E.showMenu(timemenu);

View File

@ -1 +1,2 @@
0.01: Release
0.01: Release
0.02: Rewrite with new interface

View File

@ -1,21 +1,18 @@
# Simple Timer
A simple app to set a timer quickly. Simply tab on top/bottom/left/right
to select the minutes and tab in the middle of the screen to start/stop
the timer. Note that this timer depends on qalarm.
A simple app to set a timer quickly. Drag or tap on the up and down buttons over the hour, minute or second to set the time.
# Overview
If you open the app, you can simply control the timer
by clicking on top, bottom, left or right of the screen.
If you tab at the middle of the screen, the timer is
started / stopped.
This app uses the `sched` library, which allows the timer to continue to run in the background when this app is closed.
![](description.png)
![](screenshot_1.png)
![](screenshot_2.png)
![](screenshot_3.png)
![](screenshot_4.png)
# Creator
# Creators
[David Peer](https://github.com/peerdavid)
[Sir Indy](https://github.com/sir-indy)
# Thanks to...
Time icon created by <a href="https://www.flaticon.com/free-icons/time" title="time icons">CreativeCons - Flaticon</a>

View File

@ -3,122 +3,188 @@
*
* Creator: David Peer
* Date: 02/2022
*
* Modified: Sir Indy
* Date: 05/2022
*/
Bangle.loadWidgets();
const alarm = require("sched");
const Layout = require("Layout");
const alarm = require("sched")
const TIMER_IDX = "smpltmr";
const screenWidth = g.getWidth();
const screenHeight = g.getHeight();
const cx = parseInt(screenWidth/2);
const cy = parseInt(screenHeight/2)-12;
var minutes = 5;
var interval; //used for the 1 second interval timer
function isTimerEnabled(){
var alarmObj = alarm.getAlarm(TIMER_IDX);
if(alarmObj===undefined || !alarmObj.on){
return false;
const secondsToTime = (s) => new Object({h:Math.floor((s/3600) % 24), m:Math.floor((s/60) % 60), s:Math.floor(s % 60)});
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
function formatTime(s) {
var t = secondsToTime(s);
if (t.h) {
return t.h + ':' + ("0" + t.m).substr(-2) + ':' + ("0" + t.s).substr(-2);
} else {
return t.m + ':' + ("0" + t.s).substr(-2);
}
return true;
}
function getTimerMin(){
var alarmObj = alarm.getAlarm(TIMER_IDX);
return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
var seconds = 5 * 60; // Default to 5 minutes
var drawTimeout;
//var timerRunning = false;
function timerRunning() {
return (alarm.getTimeToAlarm(alarm.getAlarm(TIMER_IDX)) != undefined)
}
const imgArrow = atob("CQmBAAgOBwfD47ndx+OA");
const imgPause = atob("GBiBAP+B//+B//+B//+B//+B//+B//+B//+B//+B//+B//+B//+B//+B//+B//+B//+B//+B//+B//+B//+B//+B//+B//+B//+B/w==");
const imgPlay = atob("GBiBAIAAAOAAAPgAAP4AAP+AAP/gAP/4AP/+AP//gP//4P//+P///v///v//+P//4P//gP/+AP/4AP/gAP+AAP4AAPgAAOAAAIAAAA==");
function onDrag(event) {
if (!timerRunning()) {
Bangle.buzz(40, 0.3);
var diff = -Math.round(event.dy/5);
if (event.x < timePickerLayout.hours.w) {
diff *= 3600;
} else if (event.x > timePickerLayout.mins.x && event.x < timePickerLayout.secs.x) {
diff *= 60;
}
updateTimePicker(diff);
}
}
function setTimer(minutes){
function onTouch(button, xy) {
if (xy.y > (timePickerLayout.btnStart.y||timerLayout.btnStart.y)) {
Bangle.buzz(40, 0.3);
onButton();
return;
}
if (!timerRunning()) {
var touchMidpoint = timePickerLayout.hours.y + timePickerLayout.hours.h/2;
var diff = 0;
Bangle.buzz(40, 0.3);
if (xy.y > 24 && xy.y < touchMidpoint - 10) {
diff = 1;
} else if (xy.y > touchMidpoint + 10 && xy.y < timePickerLayout.btnStart.y) {
diff = -1;
}
if (xy.x < timePickerLayout.hours.w) {
diff *= 3600;
} else if (xy.x > timePickerLayout.mins.x && xy.x < timePickerLayout.secs.x) {
diff *= 60;
}
updateTimePicker(diff);
}
}
function onButton() {
g.clearRect(Bangle.appRect);
if (timerRunning()) {
timerStop();
} else {
if (seconds > 0) {
timerRun();
}
}
}
function updateTimePicker(diff) {
seconds = clamp(seconds + (diff || 0), 0, 24 * 3600 - 1);
var set_time = secondsToTime(seconds);
updateLayoutField(timePickerLayout, 'hours', set_time.h);
updateLayoutField(timePickerLayout, 'mins', set_time.m);
updateLayoutField(timePickerLayout, 'secs', set_time.s);
}
function updateTimer() {
var timeToNext = alarm.getTimeToAlarm(alarm.getAlarm(TIMER_IDX));
updateLayoutField(timerLayout, 'timer', formatTime(timeToNext / 1000));
queueDraw(1000);
}
function queueDraw(millisecs) {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
updateTimer();
}, millisecs - (Date.now() % millisecs));
}
function timerRun() {
alarm.setAlarm(TIMER_IDX, {
// msg : "Simple Timer",
timer : minutes*60*1000,
vibrate : ".-.-",
hidden: true,
timer : seconds * 1000
});
alarm.reload();
g.clearRect(Bangle.appRect);
timerLayout.render();
updateTimer();
}
function deleteTimer(){
function timerStop() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
var timeToNext = alarm.getTimeToAlarm(alarm.getAlarm(TIMER_IDX));
if (timeToNext != undefined) {
seconds = timeToNext / 1000;
}
alarm.setAlarm(TIMER_IDX, undefined);
alarm.reload();
g.clearRect(Bangle.appRect);
timePickerLayout.render();
updateTimePicker();
}
setWatch(_=>load(), BTN1);
function draw(){
g.clear(1);
Bangle.drawWidgets();
if (interval) {
clearInterval(interval);
}
interval = undefined;
// Write time
g.setFontAlign(0, 0, 0);
g.setFont("Vector", 32).setFontAlign(0,-1);
var started = isTimerEnabled();
var text = minutes + " min.";
if(started){
var min = getTimerMin();
text = min + " min.";
}
var rectWidth = parseInt(g.stringWidth(text) / 2);
if(started){
interval = setInterval(draw, 1000);
g.setColor("#ff0000");
} else {
g.setColor(g.theme.fg);
}
g.fillRect(cx-rectWidth-5, cy-5, cx+rectWidth, cy+30);
g.setColor(g.theme.bg);
g.drawString(text, cx, cy);
}
Bangle.on('touch', function(btn, e){
var left = parseInt(g.getWidth() * 0.25);
var right = g.getWidth() - left;
var upper = parseInt(g.getHeight() * 0.25);
var lower = g.getHeight() - upper;
var isLeft = e.x < left;
var isRight = e.x > right;
var isUpper = e.y < upper;
var isLower = e.y > lower;
var isMiddle = !isLeft && !isRight && !isUpper && !isLower;
var started = isTimerEnabled();
if(isRight && !started){
minutes += 1;
Bangle.buzz(40, 0.3);
} else if(isLeft && !started){
minutes -= 1;
Bangle.buzz(40, 0.3);
} else if(isUpper && !started){
minutes += 5;
Bangle.buzz(40, 0.3);
} else if(isLower && !started){
minutes -= 5;
Bangle.buzz(40, 0.3);
} else if(isMiddle) {
if(!started){
setTimer(minutes);
} else {
deleteTimer();
}
Bangle.buzz(80, 0.6);
}
minutes = Math.max(0, minutes);
draw();
var timePickerLayout = new Layout({
type:"v", c: [
{type:undefined, height:2},
{type:"h", c: [
{type:"v", width:g.getWidth()/3, c: [
{type:"txt", font:"6x8", label:/*LANG*/"Hours"},
{type:"img", pad:8, src:imgArrow},
{type:"txt", font:"20%", label:"00", id:"hours", filly:1, fillx:1},
{type:"img", pad:8, src:imgArrow, r:2}
]},
{type:"v", width:g.getWidth()/3, c: [
{type:"txt", font:"6x8", label:/*LANG*/"Minutes"},
{type:"img", pad:8, src:imgArrow},
{type:"txt", font:"20%", label:"00", id:"mins", filly:1, fillx:1},
{type:"img", pad:8, src:imgArrow, r:2}
]},
{type:"v", width:g.getWidth()/3, c: [
{type:"txt", font:"6x8", label:/*LANG*/"Seconds"},
{type:"img", pad:8, src:imgArrow},
{type:"txt", font:"20%", label:"00", id:"secs", filly:1, fillx:1},
{type:"img", pad:8, src:imgArrow, r:2}
]},
]},
{type:"btn", src:imgPlay, id:"btnStart", fillx:1 }
], filly:1
});
g.reset();
draw();
var timerLayout = new Layout({
type:"v", c: [
{type:"txt", font:"22%", label:"0:00", id:"timer", fillx:1, filly:1 },
{type:"btn", src:imgPause, id:"btnStart", cb: l=>timerStop(), fillx:1 }
], filly:1
});
function updateLayoutField(layout, field, value) {
layout.clear(layout[field]);
layout[field].label = value;
layout.render(layout[field]);
}
Bangle.loadWidgets();
Bangle.drawWidgets();
Bangle.setUI({
mode : "custom",
touch : function(n,e) {onTouch(n,e);},
drag : function(e) {onDrag(e);},
btn : function(n) {onButton();},
});
g.clearRect(Bangle.appRect);
if (timerRunning()) {
timerLayout.render();
updateTimer();
} else {
timePickerLayout.render();
updateTimePicker();
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -2,13 +2,13 @@
"id": "smpltmr",
"name": "Simple Timer",
"shortName": "Simple Timer",
"version": "0.01",
"version": "0.02",
"description": "A very simple app to start a timer.",
"icon": "app.png",
"tags": "tool",
"tags": "tool,alarm,timer",
"dependencies": {"scheduler":"type"},
"supports": ["BANGLEJS2"],
"screenshots": [{"url":"screenshot.png"}, {"url": "screenshot_2.png"}],
"screenshots": [{"url":"screenshot_1.png"}, {"url": "screenshot_2.png"}, {"url": "screenshot_3.png"}, {"url": "screenshot_4.png"}],
"readme": "README.md",
"storage": [
{"name":"smpltmr.app.js","url":"app.js"},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -5,7 +5,7 @@
"version": "0.01",
"description": "Let's you swipe from left to right on any app to return back to the clock face. Please configure in the settings app after installing to activate, since its disabled by default.",
"icon": "app.png",
"type": "boot",
"type": "bootloader",
"tags": "tools",
"supports": ["BANGLEJS2"],
"readme": "README.md",

View File

@ -1,2 +1,3 @@
0.01: Initial version
0.02: Do not warn multiple times for the same exceedance
0.03: Fix crash

View File

@ -2,7 +2,7 @@
"id": "widbaroalarm",
"name": "Barometer Alarm Widget",
"shortName": "Barometer Alarm",
"version": "0.02",
"version": "0.03",
"description": "A widget that can alarm on when the pressure reaches defined thresholds.",
"icon": "widget.png",
"type": "widget",

View File

@ -104,7 +104,7 @@
saveSetting("lastHighWarningTs", 0);
}
if (!alreadyWarned) {
if (history3.length > 0 && !alreadyWarned) {
// 3h change detection
const drop3halarm = setting("drop3halarm");
const raise3halarm = setting("raise3halarm");

View File

@ -65,6 +65,7 @@ const APP_KEYS = [
const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite', 'supports'];
const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate'];
const SUPPORTS_DEVICES = ["BANGLEJS","BANGLEJS2"]; // device IDs allowed for 'supports'
const METADATA_TYPES = ["app","clock","widget","bootloader","RAM","launch","textinput","scheduler","notify","locale"]; // values allowed for "type" field
const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info
const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ];
const GRANDFATHERED_ICONS = ["s7clk", "snek", "astral", "alpinenav", "slomoclock", "arrow", "pebble", "rebble"];
@ -94,6 +95,8 @@ apps.forEach((app,appIdx) => {
if (!app.name) ERROR(`App ${app.id} has no name`);
var isApp = !app.type || app.type=="app";
if (app.name.length>20 && !app.shortName && isApp) ERROR(`App ${app.id} has a long name, but no shortName`);
if (app.type && !METADATA_TYPES.includes(app.type))
ERROR(`App ${app.id} 'type' is one one of `+METADATA_TYPES);
if (!Array.isArray(app.supports)) ERROR(`App ${app.id} has no 'supports' field or it's not an array`);
else {
app.supports.forEach(dev => {
@ -135,6 +138,9 @@ apps.forEach((app,appIdx) => {
Object.keys(app.dependencies).forEach(dependency => {
if (!["type","app"].includes(app.dependencies[dependency]))
ERROR(`App ${app.id} 'dependencies' must all be tagged 'type' or 'app' right now`);
if (app.dependencies[dependency]=="type" && !METADATA_TYPES.includes(dependency))
ERROR(`App ${app.id} 'type' dependency must be one of `+METADATA_TYPES);
});
} else
ERROR(`App ${app.id} 'dependencies' must be an object`);

2
core

@ -1 +1 @@
Subproject commit 6fc78fc39531a43148ae8d515efaeff9404d1daf
Subproject commit 32d01b5b3d8e013ca0364671e2352b7b0dd48bb4

View File

@ -25,7 +25,7 @@ DEVICEINFO = DEVICEINFO.filter(x=>x.id.startsWith("BANGLEJS"));
// Set up source code URL
(function() {
let username = "espruino";
let githubMatch = window.location.href.match(/\/(\w+)\.github\.io/);
let githubMatch = window.location.href.match(/\/([\w-]+)\.github\.io/);
if (githubMatch) username = githubMatch[1];
Const.APP_SOURCECODE_URL = `https://github.com/${username}/BangleApps/tree/master/apps`;
})();

View File

@ -1,18 +1,13 @@
/* Copyright (c) 2022 Bangle.js contributors. See the file LICENSE for copying permission. */
/*
Take a look at README.md for hints on developing with this library.
Usage:
```
var Layout = require("Layout");
var layout = new Layout( layoutObject, options )
layout.render(optionalObject);
```
For example:
```
var Layout = require("Layout");
var layout = new Layout( {
@ -24,23 +19,22 @@ var layout = new Layout( {
g.clear();
layout.render();
```
layoutObject has:
* A `type` field of:
* `undefined` - blank, can be used for padding
* `"txt"` - a text label, with value `label` and `r` for text rotation. 'font' is required
* `"txt"` - a text label, with value `label`. 'font' is required
* `"btn"` - a button, with value `label` and callback `cb`
optional `src` specifies an image (like img) in which case label is ignored
Default font is `6x8`, scale 2. This can be overridden with the `font` or `scale` fields.
* `"img"` - an image where `src` is an image, or a function which is called to return an image to draw.
optional `scale` specifies if image should be scaled up or not
* `"custom"` - a custom block where `render(layoutObj)` is called to render
* `"h"` - Horizontal layout, `c` is an array of more `layoutObject`
* `"v"` - Vertical layout, `c` is an array of more `layoutObject`
* A `id` field. If specified the object is added with this name to the
returned `layout` object, so can be referenced as `layout.foo`
* A `font` field, eg `6x8` or `30%` to use a percentage of screen height
* A `font` field, eg `6x8` or `30%` to use a percentage of screen height. Set scale with :, e.g. `6x8:2`.
* A `scale` field, eg `2` to set scale of an image
* A `r` field to set rotation of text or images (0: 0°, 1: 90°, 2: 180°, 3: 270°).
* A `wrap` field to enable line wrapping. Requires some combination of `width`/`height`
and `fillx`/`filly` to be set. Not compatible with text rotation.
* A `col` field, eg `#f00` for red
@ -51,34 +45,25 @@ layoutObject has:
* A `fillx` int to choose if the object should fill available space in x. 0=no, 1=yes, 2=2x more space
* A `filly` int to choose if the object should fill available space in y. 0=no, 1=yes, 2=2x more space
* `width` and `height` fields to optionally specify minimum size
options is an object containing:
* `lazy` - a boolean specifying whether to enable automatic lazy rendering
* `btns` - array of objects containing:
* `label` - the text on the button
* `cb` - a callback function
* `cbl` - a callback function for long presses
* `back` - a callback function, passed as `back` into Bangle.setUI
If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically
determine what objects have changed or moved, clear their previous locations, and re-render just those objects.
Once `layout.update()` is called, the following fields are added
to each object:
* `x` and `y` for the top left position
* `w` and `h` for the width and height
* `_w` and `_h` for the **minimum** width and height
Other functions:
* `layout.update()` - update positions of everything if contents have changed
* `layout.debug(obj)` - draw outlines for objects on screen
* `layout.clear(obj)` - clear the given object (you can also just specify `bgCol` to clear before each render)
* `layout.forgetLazyState()` - if lazy rendering is enabled, makes the next call to `render()` perform a full re-render
*/
@ -259,12 +244,22 @@ Layout.prototype.render = function (l) {
x,y+h-5,
x,y+4
], bg = l.selected?g.theme.bgH:g.theme.bg2;
g.setColor(bg).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly);
g.setColor(bg).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly);
if (l.col!==undefined) g.setColor(l.col);
if (l.src) g.setBgColor(bg).drawImage("function"==typeof l.src?l.src():l.src, l.x + 10 + (0|l.pad), l.y + 8 + (0|l.pad));
else g.setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2);
if (l.src) g.setBgColor(bg).drawImage(
"function"==typeof l.src?l.src():l.src,
l.x + l.w/2,
l.y + l.h/2,
{scale: l.scale||undefined, rotate: Math.PI*0.5*(l.r||0)}
);
else g.setFont(l.font||"6x8:2").setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2);
}, "img":function(l){
g.drawImage("function"==typeof l.src?l.src():l.src, l.x + (0|l.pad), l.y + (0|l.pad), l.scale?{scale:l.scale}:undefined);
g.drawImage(
"function"==typeof l.src?l.src():l.src,
l.x + l.w/2,
l.y + l.h/2,
{scale: l.scale||undefined, rotate: Math.PI*0.5*(l.r||0)}
);
}, "custom":function(l){
l.render(l);
},"h":function(l) { l.c.forEach(render); },
@ -365,7 +360,9 @@ Layout.prototype.update = function() {
l._w = m.width; l._h = m.height;
}
}, "btn": function(l) {
var m = l.src?g.imageMetrics("function"==typeof l.src?l.src():l.src):g.setFont("6x8",2).stringMetrics(l.label);
if (l.font && l.font.endsWith("%"))
l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100);
var m = l.src?g.imageMetrics("function"==typeof l.src?l.src():l.src):g.setFont(l.font||"6x8:2").stringMetrics(l.label);
l._h = 16 + m.height;
l._w = 20 + m.width;
}, "img": function(l) {

View File

@ -1,39 +1,65 @@
/* Utility functions that use the 'locale' module so can produce text
in the currently selected language. */
// module "date_utils"
//
// Utility functions that use the "locale" module so can produce
// date-related text in the currently selected language.
//
// Some functions have a "firstDayOfWeek" parameter.
// Most used values are:
// - 0/undefined --> Sunday
// - 1 --> Monday
// but you can start the week from any day if you need it.
//
// Some functions have an "abbreviated" parameter.
// It supports the following 3 values:
// - 0/undefined --> get the full value, without abbreviation (eg.: "Monday", "January", etc.)
// - 1 --> get the short value (eg.: "Mon", "Jan", etc.)
// - 2 --> get only the first char (eg.: "M", "J", etc.)
//
/** Return the day of the week (0=Sunday)
short==0/undefined -> "Sunday"
short==1 -> "Sun"
*/
exports.getDOW = (dow, short) => require("locale").dow({getDay:()=>dow},short);
/** Return the month (1=January)
short==0/undefined -> "January"
short==1 -> "Jan"
*/
exports.getMonth = (month, short) => require("locale").month({getMonth:()=>month-1},short);
/** Return all 7 days of the week as an array ["Sunday","Monday",...].
short==0/undefined -> ["Sunday",...
short==1 -> ["Sun",...
short==2 -> ["S",...
*/
exports.getDOWs = (short) => {
var locale = require("locale");
var days = [];
for (var i=0;i<7;i++)
days.push(locale.dow({getDay:()=>i},short).slice(0,(short==2)?1:100));
return days;
/**
* @param {int} i The index of the day of the week (0 = Sunday)
* @param {int} abbreviated
* @returns The localized name of the i-th day of the week
*/
exports.dow = (i, abbreviated) => {
var dow = require("locale").dow(new Date(((i || 0) + 3.5) * 86400000), abbreviated).slice(0, (abbreviated == 2) ? 1 : 100);
return abbreviated == 2 ? dow.toUpperCase() : dow;
}
/** Return all 12 months as an array ["January","February",...]
short==0/undefined -> ["January",...
short==1 -> ["Jan",...
*/
exports.getMonths = (short) => {
/**
* @param {int} firstDayOfWeek 0/undefined -> Sunday,
* 1 -> Monday
* @param {int} abbreviated
* @returns All 7 days of the week (localized) as an array
*/
exports.dows = (firstDayOfWeek, abbreviated) => {
var dows = [];
var locale = require("locale");
for (var i = 0; i < 7; i++) {
dows.push(exports.dow(i + (firstDayOfWeek || 0), abbreviated))
}
return abbreviated == 2 ? dows.map(dow => dow.toUpperCase()) : dows;
};
/**
* @param {int} i The index of the month (1 = January)
* @param {int} abbreviated
* @returns The localized name of the i-th month
*/
exports.month = (i, abbreviated) => {
var month = require("locale").month(new Date((i - 0.5) * 2628000000), abbreviated).slice(0, (abbreviated == 2) ? 1 : 100);
return abbreviated == 2 ? month.toUpperCase() : month;
}
/**
* @param {int} abbreviated
* @returns All 12 months (localized) as an array
*/
exports.months = (abbreviated) => {
var months = [];
for (var i=0;i<12;i++)
months.push(locale.month({getMonth:()=>i},short));
return months;
}
var locale = require("locale");
for (var i = 1; i <= 12; i++) {
months.push(locale.month(new Date((i - 0.5) * 2628000000), abbreviated).slice(0, (abbreviated == 2) ? 1 : 100));
}
return abbreviated == 2 ? months.map(month => month.toUpperCase()) : months;
};

View File

@ -19,7 +19,7 @@ SRCBMP=$SRCDIR/`basename $SRCJS .js`.bmp
echo "TEST $SRCJS ($SRCBMP)"
cat ../../modules/Layout.js > $TESTJS
echo 'Bangle = { setUI : function(){} };BTN1=0;process.env = process.env;process.env.HWVERSION=2;' >> $TESTJS
echo 'Bangle = { setUI : function(){}, appRect:{x:0,y:0,w:176,h:176,x2:175,y2:175} };BTN1=0;process.env = process.env;process.env.HWVERSION=2;' >> $TESTJS
echo 'g = Graphics.createArrayBuffer(176,176,4);' >> $TESTJS
cat $SRCJS >> $TESTJS || exit 1
echo 'layout.render()' >> $TESTJS

View File

@ -1,6 +1,7 @@
var BTN2 = 1, BTN3=2;
process.env = process.env;process.env.HWVERSION=1;
g = Graphics.createArrayBuffer(240,240,4);
Bangle.appRect = {x:0,y:0,w:240,h:240,x2:239,y2:239};
var layout = new Layout({ type: "v", c: [
{type:"txt", font:"6x8", label:"A test"},

View File

@ -1,6 +1,7 @@
var BTN2 = 1, BTN3=2;
process.env = process.env;process.env.HWVERSION=1;
g = Graphics.createArrayBuffer(240,240,4);
Bangle.appRect = {x:0,y:0,w:240,h:240,x2:239,y2:239};
var layout = new Layout({ type: "v", c: [
{type:"txt", font:"6x8", label:"A test"},

View File

@ -1,6 +1,7 @@
var BTN2 = 1, BTN3=2;
process.env = process.env;process.env.HWVERSION=1;
g = Graphics.createArrayBuffer(240,240,4);
Bangle.appRect = {x:0,y:0,w:240,h:240,x2:239,y2:239};
/* When displaying OSD buttons on Bangle.js 1 we should turn
the side buttons into 'soft' buttons and then use the physical

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB