Merge remote-tracking branch 'upstream/master'

master
Danny 2022-04-09 12:29:49 +02:00
commit 9ce4883e78
170 changed files with 77977 additions and 496 deletions

2
apps/90sclk/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Fullscreen settings.

13
apps/90sclk/README.md Normal file
View File

@ -0,0 +1,13 @@
# 90s Clock
A watch face in 90s style:
![](screenshot_2.png)
Fullscreen mode can be enabled in the settings:
![](screenshot.png)
## Creator
- [David Peer](https://github.com/peerdavid)

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgc8+fAgEgwAMDvPnz99BYdl2weHtu27ft2AGBiEcuEAhAPDg4jGgECIRMN23fthUNgP374vBAB3gAgc/gAXNjlx4EDxwJEpAjG/6IBjkBL4UAjVgBAJuCgPHBQMFEIkkyQjFhwEClgXBEYNBwkQJoibCBwNFBAUCEAVAQZAjC/8euPHDon//hKB//xEYMP//jBYP/+ARDNYM///+EYIgBj1B/8fCIUhEYQRB//FUIM/EZU4EYMkEYP/8VhEYUH/gRBWAUfI4MD+AjBoAsBwEH8EB/EDwE4HwYjCuEHWAOHgExEYKbBCIZNB8fAEYQHByE/EwPABAY+BgRHDBANyJQXHNwIjD8CSBj/+BwMSTwOOBYK2D/4CCNYZQB/iJBQwYjCCIcAgeBSoOAWYQjEVoIRCNAIjKAQKJBgAFC8ZoCWwJbDABMHGQPAAoMQB5EDx/4A4gqBZwIGCWwIABuBWC4EBZwPgv/AcwS/EAAcIU4IRBVQIRKEwIjBv0ARIUDCJIjD//x/ARK/5HC/+BCJkcI45uDgECUgQjCWAM4WwUBWYanEAA8cTARWBEYUC5RAHw1YgEOFQXADQPHIIkAhgICuARBh0A23blhHBagIKBsOGjNswhHDEYUUAoTUBhkxEYMwKwU503bvuwXILmCEYMYsumWYYjB85lDEYovBEYXm7fs25EBI4kYtOWNwIjD4+8NYsw4YjGz9/2hrEoOGjVBwE4NYdzNYSwBuEDEYcxaIUA8+atugGogjBiVgWAI"))

144
apps/90sclk/app.js Normal file

File diff suppressed because one or more lines are too long

BIN
apps/90sclk/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
apps/90sclk/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

18
apps/90sclk/metadata.json Normal file
View File

@ -0,0 +1,18 @@
{
"id": "90sclk",
"name": "90s Clock",
"version": "0.02",
"description": "A 90s style watch-face",
"readme": "README.md",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_2.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"90sclk.app.js","url":"app.js"},
{"name":"90sclk.img","url":"app-icon.js","evaluate":true},
{"name":"90sclk.settings.js","url":"settings.js"}
]
}

BIN
apps/90sclk/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

31
apps/90sclk/settings.js Normal file
View File

@ -0,0 +1,31 @@
(function(back) {
const SETTINGS_FILE = "90sclk.setting.json";
// initialize with default settings...
const storage = require('Storage')
let settings = {
fullscreen: false,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
settings[key] = saved_settings[key]
}
function save() {
storage.write(SETTINGS_FILE, settings)
}
E.showMenu({
'': { 'title': '90s Clock' },
'< Back': back,
'Full Screen': {
value: settings.fullscreen,
format: () => (settings.fullscreen ? 'Yes' : 'No'),
onchange: () => {
settings.fullscreen = !settings.fullscreen;
save();
},
}
});
})

View File

@ -1,12 +1,34 @@
// place your const, vars, functions or classes here
// special function to handle display switch on
Bangle.on('lcdPower', (on) => {
if (on) {
// call your app function here
// If you clear the screen, do Bangle.drawWidgets();
// clear the screen
g.clear();
var n = 0;
// redraw the screen
function draw() {
g.reset().clearRect(Bangle.appRect);
g.setFont("6x8").setFontAlign(0,0).drawString("Up / Down",g.getWidth()/2,g.getHeight()/2 - 20);
g.setFont("Vector",60).setFontAlign(0,0).drawString(n,g.getWidth()/2,g.getHeight()/2 + 30);
}
// Respond to user input
Bangle.setUI({mode: "updown"}, function(dir) {
if (dir<0) {
n--;
draw();
} else if (dir>0) {
n++;
draw();
} else {
n = 0;
draw();
}
});
g.clear();
// call your app function here
// First draw...
draw();
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();

View File

@ -8,6 +8,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"aclock.app.js","url":"clock-analog.js"},

View File

@ -14,3 +14,7 @@
0.13: Alarm widget state now updates when setting/resetting an alarm
0.14: Order of 'back' menu item
0.15: Fix hour/minute wrapping code for new menu system
0.16: Adding alarm library
0.17: Moving alarm internals to 'sched' library
0.18: Cope with >1 identical alarm at once (#1667)
0.19: Ensure rescheduled alarms that already fired have 'last' reset

6
apps/alarm/README.md Normal file
View File

@ -0,0 +1,6 @@
Default Alarm & Timer
======================
This allows you to add/modify any running 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.

View File

@ -1,72 +0,0 @@
// Chances are boot0.js got run already and scheduled *another*
// 'load(alarm.js)' - so let's remove it first!
clearInterval();
function formatTime(t) {
var hrs = 0|t;
var mins = Math.round((t-hrs)*60);
return hrs+":"+("0"+mins).substr(-2);
}
function getCurrentHr() {
var time = new Date();
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
}
function showAlarm(alarm) {
var msg = formatTime(alarm.hr);
var buzzCount = 10;
if (alarm.msg)
msg += "\n"+alarm.msg;
Bangle.loadWidgets();
Bangle.drawWidgets();
E.showPrompt(msg,{
title:alarm.timer ? /*LANG*/"TIMER!" : /*LANG*/"ALARM!",
buttons : {/*LANG*/"Sleep":true,/*LANG*/"Ok":false} // default is sleep so it'll come back in 10 mins
}).then(function(sleep) {
buzzCount = 0;
if (sleep) {
if(alarm.ohr===undefined) alarm.ohr = alarm.hr;
alarm.hr += 10/60; // 10 minutes
} else {
alarm.last = (new Date()).getDate();
if (alarm.ohr!==undefined) {
alarm.hr = alarm.ohr;
delete alarm.ohr;
}
if (!alarm.rp) alarm.on = false;
}
require("Storage").write("alarm.json",JSON.stringify(alarms));
load();
});
function buzz() {
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
Bangle.buzz(100).then(()=>{
setTimeout(()=>{
Bangle.buzz(100).then(function() {
if (buzzCount--)
setTimeout(buzz, 3000);
else if(alarm.as) { // auto-snooze
buzzCount = 10;
setTimeout(buzz, 600000);
}
});
},100);
});
}
buzz();
}
// Check for alarms
var day = (new Date()).getDate();
var hr = getCurrentHr()+10000; // get current time - 10s in future to ensure we alarm if we've started the app a tad early
var alarms = require("Storage").readJSON("alarm.json",1)||[];
var active = alarms.filter(a=>a.on&&(a.hr<hr)&&(a.last!=day));
if (active.length) {
// if there's an alarm, show it
active = active.sort((a,b)=>a.hr-b.hr);
showAlarm(active[0]);
} else {
// otherwise just go back to default app
setTimeout(load, 100);
}

View File

@ -1,36 +1,43 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
var alarms = require("Storage").readJSON("alarm.json",1)||[];
/*alarms = [
{ on : true,
hr : 6.5, // hours + minutes/60
msg : "Eat chocolate",
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!
rp : true, // repeat
as : false, // auto snooze
timer : 5, // OPTIONAL - if set, this is a timer and it's the time in minutes
}
];*/
var alarms = require("sched").getAlarms();
// An array of alarm objects (see sched/README.md)
// time in ms -> { hrs, mins }
function decodeTime(t) {
t = 0|t; // sanitise
var hrs = 0|(t/3600000);
return { hrs : hrs, mins : Math.round((t-hrs*3600000)/60000) };
}
// time in { hrs, mins } -> ms
function encodeTime(o) {
return o.hrs*3600000 + o.mins*60000;
}
function formatTime(t) {
var hrs = 0|t;
var mins = Math.round((t-hrs)*60);
return hrs+":"+("0"+mins).substr(-2);
var o = decodeTime(t);
return o.hrs+":"+("0"+o.mins).substr(-2);
}
function formatMins(t) {
mins = (0|t)%60;
hrs = 0|(t/60);
return hrs+":"+("0"+mins).substr(-2);
}
function getCurrentHr() {
function getCurrentTime() {
var time = new Date();
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
return (
time.getHours() * 3600000 +
time.getMinutes() * 60000 +
time.getSeconds() * 1000
);
}
function saveAndReload() {
require("sched").setAlarms(alarms);
require("sched").reload();
}
function showMainMenu() {
// Timer img "\0"+atob("DhKBAP////MDDAwwMGGBzgPwB4AeAPwHOBhgwMMzDez////w")
// Alarm img "\0"+atob("FBSBAABgA4YcMPDGP8Zn/mx/48//PP/zD/8A//AP/wD/8A//AP/wH/+D//w//8AAAADwAAYA")
const menu = {
'': { 'title': 'Alarm/Timer' },
/*LANG*/'< Back' : ()=>{load();},
@ -38,141 +45,161 @@ function showMainMenu() {
/*LANG*/'New Timer': ()=>editTimer(-1)
};
alarms.forEach((alarm,idx)=>{
var type,txt; // a leading space is currently required (JS error in Espruino 2v12)
if (alarm.timer) {
txt = /*LANG*/"TIMER "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatMins(alarm.timer);
type = /*LANG*/"Timer";
txt = " "+formatTime(alarm.timer);
} else {
txt = /*LANG*/"ALARM "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatTime(alarm.hr);
if (alarm.rp) txt += /*LANG*/" (repeat)";
type = /*LANG*/"Alarm";
txt = " "+formatTime(alarm.t);
}
menu[txt] = function() {
if (alarm.timer) editTimer(idx);
else editAlarm(idx);
if (alarm.rp) txt += "\0"+atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA=");
// rename duplicate alarms
if (menu[type+txt]) {
var n = 2;
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() {
if (alarm.timer) editTimer(idx, alarm);
else editAlarm(idx, alarm);
}
};
});
if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
return E.showMenu(menu);
}
function editAlarm(alarmIndex) {
function editDOW(dow, onchange) {
const menu = {
'': { 'title': /*LANG*/'Days of Week' },
'< Back' : () => onchange(dow)
};
for (var i = 0; i < 7; i++) (i => {
var dayOfWeek = require("locale").dow({ getDay: () => i });
menu[dayOfWeek] = {
value: !!(dow&(1<<i)),
format: v => v ? "Yes" : "No",
onchange: v => v ? dow |= 1<<i : dow &= ~(1<<i),
};
})(i);
E.showMenu(menu);
}
function editAlarm(alarmIndex, alarm) {
var newAlarm = alarmIndex<0;
var hrs = 12;
var mins = 0;
var en = true;
var repeat = true;
var as = false;
if (!newAlarm) {
var a = alarms[alarmIndex];
hrs = 0|a.hr;
mins = Math.round((a.hr-hrs)*60);
en = a.on;
repeat = a.rp;
as = a.as;
var a = {
t : 12*3600000, // 12 o clock default
on : true,
rp : false, // repeat not the default
as : false,
dow : 0b1111111,
last : 0,
vibrate : ".."
}
if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
if (alarm) Object.assign(a,alarm);
var t = decodeTime(a.t);
const menu = {
'': { 'title': /*LANG*/'Alarm' },
/*LANG*/'< Back' : showMainMenu,
'< Back' : () => showMainMenu(),
/*LANG*/'Hours': {
value: hrs, min : 0, max : 23, wrap : true,
onchange: v => hrs=v
value: t.hrs, min : 0, max : 23, wrap : true,
onchange: v => t.hrs=v
},
/*LANG*/'Minutes': {
value: mins, min : 0, max : 59, wrap : true,
onchange: v => mins=v
value: t.mins, min : 0, max : 59, wrap : true,
onchange: v => t.mins=v
},
/*LANG*/'Enabled': {
value: en,
value: a.on,
format: v=>v?"On":"Off",
onchange: v=>en=v
onchange: v=>a.on=v
},
/*LANG*/'Repeat': {
value: en,
value: a.rp,
format: v=>v?"Yes":"No",
onchange: v=>repeat=v
onchange: v=>a.rp=v
},
/*LANG*/'Days': {
value: "SMTWTFS".split("").map((d,n)=>a.dow&(1<<n)?d:".").join(""),
onchange: () => editDOW(a.dow, d=>{a.dow=d;editAlarm(alarmIndex,a)})
},
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
/*LANG*/'Auto snooze': {
value: as,
value: a.as,
format: v=>v?"Yes":"No",
onchange: v=>as=v
onchange: v=>a.as=v
}
};
function getAlarm() {
var hr = hrs+(mins/60);
var day = 0;
// If alarm is for tomorrow not today (eg, in the past), set day
if (hr < getCurrentHr())
day = (new Date()).getDate();
// Save alarm
return {
on : en, hr : hr,
last : day, rp : repeat, as: as
};
}
menu[/*LANG*/"> Save"] = function() {
if (newAlarm) alarms.push(getAlarm());
else alarms[alarmIndex] = getAlarm();
require("Storage").write("alarm.json",JSON.stringify(alarms));
menu[/*LANG*/"Save"] = function() {
a.t = encodeTime(t);
a.last = (a.t < getCurrentTime()) ? (new Date()).getDate() : 0;
if (newAlarm) alarms.push(a);
else alarms[alarmIndex] = a;
saveAndReload();
showMainMenu();
};
if (!newAlarm) {
menu[/*LANG*/"> Delete"] = function() {
menu[/*LANG*/"Delete"] = function() {
alarms.splice(alarmIndex,1);
require("Storage").write("alarm.json",JSON.stringify(alarms));
saveAndReload();
showMainMenu();
};
}
return E.showMenu(menu);
}
function editTimer(alarmIndex) {
function editTimer(alarmIndex, alarm) {
var newAlarm = alarmIndex<0;
var hrs = 0;
var mins = 5;
var en = true;
if (!newAlarm) {
var a = alarms[alarmIndex];
mins = (0|a.timer)%60;
hrs = 0|(a.timer/60);
en = a.on;
var a = {
timer : 5*60*1000, // 5 minutes
on : true,
rp : false,
as : false,
dow : 0b1111111,
last : 0,
vibrate : ".."
}
if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
if (alarm) Object.assign(a,alarm);
var t = decodeTime(a.timer);
const menu = {
'': { 'title': /*LANG*/'Timer' },
/*LANG*/'< Back' : showMainMenu,
'< Back' : () => showMainMenu(),
/*LANG*/'Hours': {
value: hrs, min : 0, max : 23, wrap : true,
onchange: v => hrs=v
value: t.hrs, min : 0, max : 23, wrap : true,
onchange: v => t.hrs=v
},
/*LANG*/'Minutes': {
value: mins, min : 0, max : 59, wrap : true,
onchange: v => mins=v
value: t.mins, min : 0, max : 59, wrap : true,
onchange: v => t.mins=v
},
/*LANG*/'Enabled': {
value: en,
format: v=>v?/*LANG*/"On":/*LANG*/"Off",
onchange: v=>en=v
}
value: a.on,
format: v=>v?"On":"Off",
onchange: v=>a.on=v
},
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
};
function getTimer() {
var d = new Date(Date.now() + ((hrs*60)+mins)*60000);
var hr = d.getHours() + (d.getMinutes()/60) + (d.getSeconds()/3600);
// Save alarm
return {
on : en,
timer : (hrs*60)+mins,
hr : hr,
rp : false, as: false
};
}
menu["> Save"] = function() {
if (newAlarm) alarms.push(getTimer());
else alarms[alarmIndex] = getTimer();
require("Storage").write("alarm.json",JSON.stringify(alarms));
menu[/*LANG*/"Save"] = function() {
a.timer = encodeTime(t);
a.t = getCurrentTime() + a.timer;
a.last = 0;
if (newAlarm) alarms.push(a);
else alarms[alarmIndex] = a;
saveAndReload();
showMainMenu();
};
if (!newAlarm) {
menu["> Delete"] = function() {
menu[/*LANG*/"Delete"] = function() {
alarms.splice(alarmIndex,1);
require("Storage").write("alarm.json",JSON.stringify(alarms));
saveAndReload();
showMainMenu();
};
}

View File

@ -1,25 +0,0 @@
// check for alarms
(function() {
var alarms = require('Storage').readJSON('alarm.json',1)||[];
var time = new Date();
var active = alarms.filter(a=>a.on);
if (active.length) {
active = active.sort((a,b)=>(a.hr-b.hr)+(a.last-b.last)*24);
var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
if (!require('Storage').read("alarm.js")) {
console.log("No alarm app!");
require('Storage').write('alarm.json',"[]");
} else {
var t = 3600000*(active[0].hr-hr);
if (active[0].last == time.getDate() || t < 0) t += 86400000;
if (t<1000) t=1000;
/* execute alarm at the correct time. We avoid execing immediately
since this code will get called AGAIN when alarm.js is loaded. alarm.js
will then clearInterval() to get rid of this call so it can proceed
normally. */
setTimeout(function() {
load("alarm.js");
},t);
}
}
})();

View File

@ -1,18 +1,17 @@
{
"id": "alarm",
"name": "Default Alarm & Timer",
"name": "Alarm & Timer",
"shortName": "Alarms",
"version": "0.15",
"description": "Set and respond to alarms and timers",
"version": "0.19",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm,widget",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"dependencies": {"scheduler":"type"},
"storage": [
{"name":"alarm.app.js","url":"app.js"},
{"name":"alarm.boot.js","url":"boot.js"},
{"name":"alarm.js","url":"alarm.js"},
{"name":"alarm.img","url":"app-icon.js","evaluate":true},
{"name":"alarm.wid.js","url":"widget.js"}
],
"data": [{"name":"alarm.json"}]
]
}

View File

@ -1,7 +1,8 @@
WIDGETS["alarm"]={area:"tl",width:0,draw:function() {
if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
},reload:function() {
WIDGETS["alarm"].width = (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0;
// don't include library here as we're trying to use as little RAM as possible
WIDGETS["alarm"].width = (require('Storage').readJSON('sched.json',1)||[]).some(alarm=>alarm.on&&(alarm.hidden!==false)) ? 24 : 0;
}
};
WIDGETS["alarm"].reload();

2
apps/altimeter/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Actually upload correct code

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4UA///t9TmuV3+GJf4AN+ALVgf8BasP/4LVn//4ALUWgJUJBZUDBYJUIBZcP3/nKhEOt/WBZE5r+VKg0KgEVr9V3wLHqtaqt9sALElWAqoABt1QBZNeBYuq0ILCrVUBYulBYVWBYkCBYgABBZ8K1WVBYlABZegKQWqBQlVqALKqWoKQWpBYtWBZeqKRAAB1WABZZSHAANq0ALLKQ6qC1ALLKQ5UEAH4AG"))

30
apps/altimeter/app.js Normal file
View File

@ -0,0 +1,30 @@
Bangle.setBarometerPower(true, "app");
g.clear(1);
Bangle.loadWidgets();
Bangle.drawWidgets();
var zero = 0;
var R = Bangle.appRect;
var y = R.y + R.h/2;
var MEDIANLENGTH = 20;
var avr = [], median;
var value = 0;
Bangle.on('pressure', function(e) {
while (avr.length>MEDIANLENGTH) avr.pop();
avr.unshift(e.altitude);
median = avr.slice().sort();
g.reset().clearRect(0,y-30,g.getWidth()-10,y+30);
if (median.length>10) {
var mid = median.length>>1;
value = E.sum(median.slice(mid-4,mid+5)) / 9;
g.setFont("Vector",50).setFontAlign(0,0).drawString((value-zero).toFixed(1), g.getWidth()/2, y);
}
});
g.reset();
g.setFont("6x8").setFontAlign(0,0).drawString(/*LANG*/"ALTITUDE (m)", g.getWidth()/2, y-40);
g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"ZERO", g.getWidth()-5, g.getHeight()/2);
setWatch(function() {
zero = value;
}, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat:true});

BIN
apps/altimeter/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,12 @@
{ "id": "altimeter",
"name": "Altimeter",
"version":"0.02",
"description": "Simple altimeter that can display height changed using Bangle.js 2's built in pressure sensor.",
"icon": "app.png",
"tags": "tool,outdoors",
"supports" : ["BANGLEJS2"],
"storage": [
{"name":"altimeter.app.js","url":"app.js"},
{"name":"altimeter.img","url":"app-icon.js","evaluate":true}
]
}

3
apps/bee/ChangeLog Normal file
View File

@ -0,0 +1,3 @@
0.01: New app!
0.02: Fix bug with regenerating index, fix bug in word lookups
0.03: Improve word search performance

33
apps/bee/README.md Normal file
View File

@ -0,0 +1,33 @@
# Spelling bee game
Word finding game inspired by the NYT spelling bee. Find as many words with 4 or more letters (must include the
letter at the center of the 'hive') as you can.
## Usage
- tap on letters to type out word
- swipe left to delete last letter
- swipe right to enter; the word will turn blue while it is being checked against the internal dictionary; once
checked, it will turn red if the word is invalid, does not contain the central letter or has been guessed before or
will turn green if it is a valid word; in the latter case, points will be awarded
- swipe down to shuffle the 6 outer letters
- swipe up to view a list of already guessed words; tap on any of them to return to the regular game.
## Scoring
The number of correctly guessed words is displayed on the bottom left, the score on the bottom right. A single point
is awarded for a 4 letter word, or the number of letters if longer. A pangram is a word that contains all 7 letters at
least once and yields an additional 7 points. Each game contains at least one pangram.
## Technical remarks
The game uses an internal dictionary consisting of a newline separated list of English words ('bee.words', using the '2of12inf' word list).
The dictionary is fairly large (~700kB of flash space) and thus requires appropriate space on the watch and will make installing the app somewhat
slow. Because of its size it cannot be compressed (heatshrink needs to hold the compressed/uncompressed data in memory).
This file can be replaced with a custom dictionary, an ASCII file containing a newline-separated (single "\n", not DOS-style "\r\n") alphabetically
sorted (sorting is important for the word lookup algorithm) list of words.
![Screenshot](./bee_screenshot.png)

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AE2JAAIKHnc7DyNPp4vRGAwuBGB4sBAAQvSGIovPFqYvHGAYvDGBYsGGhwvGGIQvEGBQnDMYhkNGBAvOvQABqyRTF5GJr4wLFwQACX6IwLsowJLYMrldVGAQvTsoADGBITD0YvDldPF6n+F4gyGGAdP5nMF4KKBGDJZDGI7EBcoOiGAK7DGAQvYRogxEr1Pp9VMAiSBBILBWeJIxCromBMAQwDAAZfTGBQyCxOCGAIvBGIV/F7AwMAAOIp95GAYACFqoyQMAIwGF7QADEQd5FgIADqvGF8DnEAAIvFGIWjF8CFE0QwHAAQudAAK0EGBQuecw3GqpemYIxiCGIa8cF4wwHdTwvJp9/F82jGA9VMQovf5jkHGIwvg4wvIAAgvg5miF9wwNF8QABF9QwF0YuoF4oxCqoulGBAAB42i0QvjGBPMF0gwIFswwHF1IA/AH4A/AH4AL"))

BIN
apps/bee/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

181
apps/bee/bee.app.js Normal file
View File

@ -0,0 +1,181 @@
const S = require("Storage");
const words = S.read("bee.words");
var letters = [];
var centers = [];
var word = '';
var foundWords = [];
var score = 0;
var intervalID = -1;
function biSearch(w, ws, start, end, count) {
"compile"
if (start>end-w.legnth || count--<=0) return ws.substr(start, end-start).indexOf("\n"+w+"\n");
var mid = (end+start)>>1;
if (ws[mid-1]==="\n") --mid;
else while (mid<end && ws[mid]!=="\n") mid++;
var i = 0;
while (i<w.length && ws[mid+i+1]==w[i]) ++i;
if (i==w.length && ws[mid+i+1]==="\n") return mid+1;
if (i==w.length || w[i]<ws[mid+i+1]) return biSearch(w, ws, start, mid+1, count);
if (w[i]>ws[mid+i+1]) return biSearch(w, ws, mid+1, end, count);
}
function isPangram(w) {
var ltrs = '';
for (var i=0; i<w.length; ++i) if (ltrs.indexOf(w[i])===-1) ltrs += w[i];
return ltrs.length==7;
}
function checkWord (w) {
if (w.indexOf(String.fromCharCode(97+letters[0]))==-1) return false; // does it contain central letter?
if (foundWords.indexOf(w)>=0) return false; // already found
if (biSearch(w, words, 0, words.length, 20)>-1) {
foundWords.push(w);
foundWords.sort();
if (w.length==4) score++;
else score += w.length;
if (isPangram(w)) score += 7;
return true;
}
return false;
}
function getHexPoly(cx, cy, r, a) {
var p = [];
for (var i=0; i<6; ++i) p.push(cx+r*Math.sin((i+a)/3*Math.PI), cy+r*Math.cos((i+a)/3*Math.PI));
return p;
}
function drawHive() {
w = g.getWidth();
h = g.getHeight();
const R = w/3.3;
centers = getHexPoly(w/2, h/2+10, R, 0);
centers.push(w/2, h/2+10);
g.clear();
g.setFont("Vector", w/7).setFontAlign(0, 0, 0);
g.setColor(g.theme.fg);
for (var i=0; i<6; ++i) {
g.drawPoly(getHexPoly(centers[2*i], centers[2*i+1], 0.9*R/Math.sqrt(3), 0.5), {closed:true});
g.drawString(String.fromCharCode(65+letters[i+1]), centers[2*i]+2, centers[2*i+1]+2);
}
g.setColor(1, 1, 0).fillPoly(getHexPoly(w/2, h/2+10, 0.9*R/Math.sqrt(3), 0.5));
g.setColor(0).drawString(String.fromCharCode(65+letters[0]), w/2+2, h/2+10+2);
}
function shuffleLetters(qAll) {
for (var i=letters.length-1; i > 0; i--) {
var j = (1-qAll) + Math.floor(Math.random()*(i+qAll));
var temp = letters[i];
letters[i] = letters[j];
letters[j] = temp;
}
}
function pickLetters() {
var ltrs = "";
while (ltrs.length!==7) {
ltrs = [];
var i = Math.floor((words.length-10)*Math.random());
while (words[i]!="\n" && i<words.length) ++i;
if (i<words.length-1) {
++i;
while (words[i]!=="\n") {
var c = words[i];
if (ltrs.indexOf(c)===-1) ltrs += c;
++i;
}
}
}
for (var i=0; i<7; ++i) letters.push(ltrs.charCodeAt(i)-97);
shuffleLetters(1);
}
function drawWord(c) {
g.clearRect(0, 0, g.getWidth()-1, 19);
g.setColor(c).setFont("Vector", 20).setFontAlign(0, 0, 0).drawString(word, g.getWidth()/2, 11).flip();
}
function touchHandler(e, x) {
var hex = 0;
var hex_d = 1e6;
for (var i=0; i<7; ++i) {
var d = (x.x-centers[2*i])*(x.x-centers[2*i]) + (x.y-centers[2*i+1])*(x.y-centers[2*i+1]);
if (d < hex_d) {
hex_d = d;
hex = i+1;
}
}
hex = hex%7;
if (word.length <= 15) word += String.fromCharCode(letters[hex]+65);
drawWord(g.theme.fg);
}
function drawScore() {
g.setColor(g.theme.fg).setFont("Vector", 20).setFontAlign(0, 0, 0);
g.clearRect(0, g.getHeight()-22, 60, g.getHeight()-1);
g.clearRect(g.getWidth()-60, g.getHeight()-22, g.getWidth(), g.getHeight()-1);
g.drawString(foundWords.length.toString(), 30, g.getHeight()-11);
g.drawString(score.toString(), g.getWidth()-30, g.getHeight()-11);
}
function wordFound (c) {
word = "";
drawWord(g.theme.fg);
drawScore();
clearInterval(intervalID);
intervalID = -1;
}
function swipeHandler(d, e) {
if (d==-1 && word.length>0) {
word = word.slice(0, -1);
drawWord(g.theme.fg);
}
if (d==1 && word.length>=4) {
drawWord("#00f");
drawWord((checkWord(word.toLowerCase()) ? "#0f0" : "#f00"));
if (intervalID===-1) intervalID = setInterval(wordFound, 800);
}
if (e===1) {
shuffleLetters(0);
drawHive();
drawScore();
drawWord(g.theme.fg);
}
if (e===-1 && foundWords.length>0) showWordList();
}
function showWordList() {
Bangle.removeListener("touch", touchHandler);
Bangle.removeListener("swipe", swipeHandler);
E.showScroller({
h : 20, c : foundWords.length,
draw : (idx, r) => {
g.clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1).setFont("6x8:2");
g.setColor(isPangram(foundWords[idx])?'#0f0':g.theme.fg).drawString(foundWords[idx].toUpperCase(),r.x+10,r.y+4);
},
select : (idx) => {
setInterval(()=> {
E.showScroller();
drawHive();
drawScore();
drawWord(g.theme.fg);
Bangle.on("touch", touchHandler);
Bangle.on("swipe", swipeHandler);
clearInterval();
}, 100);
}
});
}
pickLetters();
drawHive();
drawScore();
Bangle.on("touch", touchHandler);
Bangle.on("swipe", swipeHandler);

BIN
apps/bee/bee_screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

74578
apps/bee/bee_words_2of12 Normal file

File diff suppressed because it is too large Load Diff

15
apps/bee/metadata.json Normal file
View File

@ -0,0 +1,15 @@
{ "id": "bee",
"name": "Bee",
"shortName":"Bee",
"icon": "app.png",
"version":"0.03",
"description": "Spelling bee",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"tags": "game,text",
"storage": [
{"name":"bee.app.js","url":"bee.app.js"},
{"name":"bee.words","url":"bee_words_2of12"},
{"name":"bee.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -7,6 +7,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"screenshots": [{"url":"berlin-clock-screenshot.png"}],
"storage": [

2
apps/bordle/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App
0.02: app keeps track of statistics now

View File

@ -21,7 +21,8 @@ function buttonPushed(b) {
layout.bt1.bgCol = wordle.keyColors.Z||g.theme.bg;
layout.bt2.label = "<del>";
layout.bt4.label = "<ent>";
layout.bt3.label = layout.bt5.label = " ";
layout.bt3.label = " ";
layout.bt5.label = "<stat>";
layout.bt6.label = "<";
}
}
@ -30,6 +31,10 @@ function buttonPushed(b) {
if (b!=6) {
if ((keyStateIdx<=5 || b<=1) && inp.length<5) inp += String.fromCharCode(b+(keyStateIdx-1)*5+64);
else if (layout.input.label.length>0 && b==2) inp = inp.slice(0,-1);
if (keyStateIdx==6 && b==5) {
wordle.drawStats();
return;
}
layout.input.label = inp;
}
layout = getKeyLayout(inp);
@ -82,6 +87,7 @@ class Wordle {
this.word = this.words.slice(i, i+5).toUpperCase();
}
console.log(this.word);
this.stats = require("Storage").readJSON("bordlestats.json") || {'1':0, '2':0, '3':0, '4':0, '5':0, '6':0, 'p':0, 'w':0, 's':0, 'ms':0};
}
render(clear) {
h = g.getHeight();
@ -109,7 +115,7 @@ class Wordle {
layout = getKeyLayout("");
wordle.render(true);
});
return 3;
return 1;
}
this.guesses.push(w);
this.nGuesses++;
@ -130,13 +136,39 @@ class Wordle {
this.guessColors[this.nGuesses].push(col);
}
if (correct==5) {
E.showAlert("The word is\n"+this.word, "You won in "+(this.nGuesses+1)+" guesses!").then(function(){load();});
return 1;
}
if (this.nGuesses==5) {
E.showAlert("The word was\n"+this.word, "You lost!").then(function(){load();});
E.showAlert("The word is\n"+this.word, "You won in "+(this.nGuesses+1)+" guesses!").then(function(){
wordle.stats['p']++; wordle.stats['w']++; wordle.stats['s']++; wordle.stats[wordle.nGuesses+1]++;
if (wordle.stats['s']>wordle.stats['ms']) wordle.stats['ms'] = wordle.stats['s'];
require("Storage").writeJSON("bordlestats.json", wordle.stats);
wordle.drawStats();
});
return 2;
}
if (this.nGuesses==5) {
E.showAlert("The word was\n"+this.word, "You lost!").then(function(){
wordle.stats['p']++; wordle.stats['s'] = 0;
require("Storage").writeJSON("bordlestats.json", wordle.stats);
wordle.drawStats();
});
return 3;
}
}
drawStats() {
E.showMessage(" ", "Statistics");
var max = 1;
for (i=1; i<=6; ++i) if (max<this.stats[i]) max = this.stats[i];
var h = g.getHeight();
var w = g.getWidth();
g.setColor('#00f').setFontVector((h-40)/8).setFontAlign(-1, 0, 0);
for (i=1; i<=6; ++i) {
tw = this.stats[i]*(w-24)/max;
g.setColor("#00f").fillRect(20, 52+(i-1)*(h-52)/6+2, 20+tw, 52+i*(h-52)/6-2);
g.setColor("#fff").drawString(i.toString(), 1, 52+(i-0.5)*(h-52)/6);
g.drawString(this.stats[i].toString(), tw>20 ? 25 : 25+tw, 52+(i-0.5)*(h-52)/6);
}
g.setFontVector((h-40)/9).setColor("#fff").drawString("P:"+this.stats["p"]+" W:"+this.stats["w"]+" S:"+this.stats["s"]+" M:"+this.stats["ms"], 4, 34);
Bangle.setUI();
Bangle.on("touch", (e) => { load(); });
}
}

View File

@ -2,7 +2,7 @@
"name": "Bordle",
"shortName":"Bordle",
"icon": "app.png",
"version":"0.01",
"version":"0.02",
"description": "Bangle version of a popular word search game",
"supports" : ["BANGLEJS2"],
"readme": "README.md",

5
apps/bwclk/ChangeLog Normal file
View File

@ -0,0 +1,5 @@
0.01: New App.
0.02: Use build in function for steps and other improvements.
0.03: Adapt colors based on the theme of the user.
0.04: Steps can be hidden now such that the time is even larger.
0.05: Included icons for information.

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

@ -0,0 +1,16 @@
# Black & White clock
![](screenshot.png)
## Features
- Fullscreen on/off
- The design is adapted to the theme of your bangle.
- Tab left/right of screen to show steps, temperature etc.
- Enable / disable lock icon in the settings.
- If the "sched" app is installed tab top / bottom of the screen to set the timer.
## Thanks to
<a href="https://www.flaticon.com/free-icons/" title="Icons">Icons created by Flaticon</a>
## Creator
- [David Peer](https://github.com/peerdavid)

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgIcah0EgEB/H8iFsAoOY4kMBYMDhmGgXkAoUGiWkAoQQBoAFCjgnCAoM4hgFDuEI+wpC8EKyg1C/0eAoMAsEAiQvBAAeAApQAB/4Ao+P4v/wn0P8Pgn/wnkH4Pjv/j/nn9PH//n/nj/IFF4F88AXBAoM88EcAoPHj//jlDAoOf/+Y+YFHjnnjAjBEIIjD+BHDO9IALA=="))

412
apps/bwclk/app.js Normal file

File diff suppressed because one or more lines are too long

BIN
apps/bwclk/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

18
apps/bwclk/metadata.json Normal file
View File

@ -0,0 +1,18 @@
{
"id": "bwclk",
"name": "BlackWhite Clock",
"version": "0.05",
"description": "Black and white clock.",
"readme": "README.md",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"bwclk.app.js","url":"app.js"},
{"name":"bwclk.img","url":"app-icon.js","evaluate":true},
{"name":"bwclk.settings.js","url":"settings.js"}
]
}

BIN
apps/bwclk/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
apps/bwclk/screenshot_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

40
apps/bwclk/settings.js Normal file
View File

@ -0,0 +1,40 @@
(function(back) {
const SETTINGS_FILE = "bwclk.setting.json";
// initialize with default settings...
const storage = require('Storage')
let settings = {
fullscreen: false,
showLock: true,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
settings[key] = saved_settings[key]
}
function save() {
storage.write(SETTINGS_FILE, settings)
}
E.showMenu({
'': { 'title': 'BlackWhite Clock' },
'< Back': back,
'Fullscreen': {
value: settings.fullscreen,
format: () => (settings.fullscreen ? 'Yes' : 'No'),
onchange: () => {
settings.fullscreen = !settings.fullscreen;
save();
},
},
'Show Lock': {
value: settings.showLock,
format: () => (settings.showLock ? 'Yes' : 'No'),
onchange: () => {
settings.showLock = !settings.showLock;
save();
},
}
});
})

View File

@ -8,6 +8,7 @@
"screenshots": [{"url":"screenshot_calculator.png"}],
"tags": "app,tool",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"calculator.app.js","url":"app.js"},
{"name":"calculator.img","url":"calculator-icon.js","evaluate":true}

View File

@ -9,6 +9,7 @@
"type": "clock",
"tags": "clock,cli,command,bash,shell",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"cliock.app.js","url":"app.js"},

View File

@ -3,3 +3,4 @@
0.03: Eliminate flickering
0.04: Fix for Bangle.js 2 and themes
0.05: Fix bearing not clearing correctly (visible in single or double digit bearings)
0.06: Add button for force compass calibration

29
apps/compass/README.md Normal file
View File

@ -0,0 +1,29 @@
# Compass
This app uses Bangle.js's built-in magnetometer as a compass.
## Usage
Hold your Bangle.js **face up** (so the display is parallel to the ground),
and the red arrow will point north, with the heading in degrees printed at
the top of the screen.
This compass app does not include tilt compensation - so much like a real
compass you should always keep it face up when taking a reading.
The first time you run the compass after your Bangle has booted (or if
you move to an area with a substantially different magnetic field) you will
need to recalibrate your compass (even if a heading is shown).
## Calibration
Press the button next to the `RESET` label on the screen. The North/South marker
will disappear and a message will appear asking you to rotate the watch 360 degrees.
* Hold the watch face up, so the display is parallel to the ground
* Rotate it around slowly, all 360 degrees (with the display still parallel to the ground)
* The `Uncalibrated` message will disappear before you have finished rotating the full 360 degrees - but you should still complete the full rotation in order for the compass to work properly.
Once you've rotated the full 360 degrees your compass should now work fine,
and calibration is stored between runs of the app. However if you go near
to a strong magnet you may still need to recalibrate.

View File

@ -38,7 +38,7 @@ Bangle.on('mag', function(m) {
if (!wasUncalibrated) {
g.clearRect(0,24,W,48);
g.setFontAlign(0,-1).setFont("6x8");
g.drawString("Uncalibrated\nturn 360° around",M,24+4);
g.drawString(/*LANG*/"Uncalibrated\nturn 360° around",M,24+4);
wasUncalibrated = true;
}
} else {
@ -64,7 +64,12 @@ Bangle.on('mag', function(m) {
oldHeading = m.heading;
});
g.clear();
g.clear(1);
g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"RESET", g.getWidth()-5, g.getHeight()/2);
setWatch(function() {
Bangle.resetCompass();
}, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat:true});
Bangle.loadWidgets();
Bangle.drawWidgets();
Bangle.setCompassPower(1);

View File

@ -1,12 +1,13 @@
{
"id": "compass",
"name": "Compass",
"version": "0.05",
"version": "0.06",
"description": "Simple compass that points North",
"icon": "compass.png",
"screenshots": [{"url":"screenshot_compass.png"}],
"tags": "tool,outdoors",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"compass.app.js","url":"compass.js"},
{"name":"compass.img","url":"compass-icon.js","evaluate":true}

View File

@ -3,3 +3,4 @@
0.03: fix metadata.json to allow setting as clock
0.04: added heart rate which is switched on when cycled to it through up/down touch on rhs
0.05: changed text to uppercase, just looks better, removed colons on text
0.06: better contrast for light theme, use fg color instead of dithered for ring

View File

@ -28,5 +28,6 @@ See [#1248](https://github.com/espruino/BangleApps/issues/1248)
## Screenshots
![](screenshot_daisy1.png)
![](screenshot_daisy3.png)
It is worth looking at the real thing though as the screenshot does not do it justice.
It is worth looking at the real thing though as the screenshots do not do it justice.

View File

@ -41,10 +41,17 @@ Graphics.prototype.setFontRoboto20 = function(scale) {
};
function assignPalettes() {
// palette for 0-40%
pal1 = new Uint16Array([g.theme.bg, g.toColor(settings.gy), g.toColor(settings.fg), g.toColor("#00f")]);
// palette for 50-100%
pal2 = new Uint16Array([g.theme.bg, g.toColor(settings.fg), g.toColor(settings.gy), g.toColor("#00f")]);
if (g.theme.dark) {
// palette for 0-40%
pal1 = new Uint16Array([g.theme.bg, g.toColor(settings.gy), g.toColor(settings.fg), g.toColor("#00f")]);
// palette for 50-100%
pal2 = new Uint16Array([g.theme.bg, g.toColor(settings.fg), g.toColor(settings.gy), g.toColor("#00f")]);
} else {
// palette for 0-40%
pal1 = new Uint16Array([g.theme.bg, g.theme.fg, g.toColor(settings.fg), g.toColor("#00f")]);
// palette for 50-100%
pal2 = new Uint16Array([g.theme.bg, g.toColor(settings.fg), g.theme.fg, g.toColor("#00f")]);
}
}
function setSmallFont20() {
@ -109,10 +116,10 @@ function updateSunRiseSunSet(now, lat, lon, line){
const infoData = {
ID_DATE: { calc: () => {var d = (new Date()).toString().split(" "); return d[2] + ' ' + d[1] + ' ' + d[3];} },
ID_DAY: { calc: () => {var d = require("locale").dow(new Date()).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} },
ID_SR: { calc: () => 'Sunrise ' + sunRise },
ID_SS: { calc: () => 'Sunset ' + sunSet },
ID_STEP: { calc: () => 'Steps ' + getSteps() },
ID_BATT: { calc: () => 'Battery ' + E.getBattery() + '%' },
ID_SR: { calc: () => 'SUNRISE ' + sunRise },
ID_SS: { calc: () => 'SUNSET ' + sunSet },
ID_STEP: { calc: () => 'STEPS ' + getSteps() },
ID_BATT: { calc: () => 'BATTERY ' + E.getBattery() + '%' },
ID_HRM: { calc: () => hrmCurrent }
};
@ -225,7 +232,7 @@ function drawSteps() {
setSmallFont();
g.setFontAlign(0,0);
g.setColor(g.theme.fg);
g.drawString('Steps ' + getSteps(), w/2, (3*h/4) - 4);
g.drawString('STEPS ' + getSteps(), w/2, (3*h/4) - 4);
drawingSteps = false;
}

View File

@ -1,13 +1,13 @@
{ "id": "daisy",
"name": "Daisy",
"version":"0.05",
"version":"0.06",
"dependencies": {"mylocation":"app"},
"description": "A clock based on the Pastel clock with large ring guage for steps",
"description": "A beautiful digital clock with large ring guage, idle timer and a cyclic information line that includes, day, date, steps, battery, sunrise and sunset times",
"icon": "app.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"screenshots": [{"url":"screenshot_daisy2.jpg"}],
"screenshots": [{"url":"screenshot_daisy3.png"}],
"readme": "README.md",
"storage": [
{"name":"daisy.app.js","url":"app.js"},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -8,6 +8,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"ffcniftyb.app.js","url":"app.js"},

View File

@ -8,6 +8,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"floralclk.app.js","url":"app.js"},

1
apps/fuzzyw/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: First release

26
apps/fuzzyw/README.md Normal file
View File

@ -0,0 +1,26 @@
# Fuzzy Text Clock
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
* es_ES - Spanish
* fr_FR - French
* no_NO - Norwegian
* sv_SE - Swedish
* de_DE - German
Most translations are taken from the original Fuzzy Text International code.
## TODO
* Bold hour word (as the pebble version has)
* Animation when changing time?
## References
Based on Pebble app Fuzzy Text International: https://github.com/hallettj/Fuzzy-Text-International
![](fuzzyw-light.png)
![](fuzzyw-dark.png)

View File

@ -0,0 +1,186 @@
{
"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
}
}

BIN
apps/fuzzyw/fuzzyw-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

75
apps/fuzzyw/fuzzyw.app.js Normal file
View File

@ -0,0 +1,75 @@
// 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 timeout = 2.5*60;
let drawTimeout;
function queueDraw(seconds) {
let millisecs = seconds * 1000;
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, millisecs - (Date.now() % millisecs));
}
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]);
} else {
f_string = f_string.replace('$2', fuzzy_string.hours[(hour + 1) % 24]);
}
return f_string;
}
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.clearRect(0, 24, w, h-24);
g.setColor(g.theme.fg);
g.drawString(g.wrapString(time_string, w).join("\n"), align_pos, h/2);
queueDraw(timeout);
}
g.clear();
draw();
// Stop updates when LCD is off, restart when on
Bangle.on('lcdPower',on=>{
if (on) {
draw(); // draw immediately, queue redraw
} else { // stop draw timer
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}
});
Bangle.setUI('clock');
Bangle.loadWidgets();
Bangle.drawWidgets();

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgP/ABX8oYFD+AFE8AFE8IXE8YFKwFCj08h4FBocenEHCIPDjk4CoIFBhlwAoeMuIFEuBSBAoOI+AFD4HxGoQFB+AFD4P4uYFC8P4gYFD/w7BAFEfApfEj+B/Ecg/Ah8A+EMg/Dw0YseHj/Dw/8sfHAoPH/lhDoIFBwFwj4FB40AvkPAoU8v4dCAoIdDw04FIMP4EOgFwh47Bj8EvEfw/DJwgFXABY"))

BIN
apps/fuzzyw/fuzzyw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 893 B

View File

@ -0,0 +1,46 @@
(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();
}
},
});
})

20
apps/fuzzyw/metadata.json Normal file
View File

@ -0,0 +1,20 @@
{
"id":"fuzzyw",
"name":"Fuzzy Text Clock",
"shortName": "Fuzzy Text",
"version": "0.01",
"description": "An imprecise clock for when you're not in a rush",
"readme": "README.md",
"icon":"fuzzyw.png",
"screenshots": [{"url":"fuzzyw-light.png"},{"url":"fuzzyw-dark.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS", "BANGLEJS2"],
"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"}
]
}

View File

@ -6,3 +6,4 @@
0.06: Fixed issue 1609 added a message popup state handler to control unwanted screen redraw
0.07: Optimized the mover algorithm for efficiency (work in progress)
0.08: Bug fix at end of the game with victorious splash and glorious orchestra
0.09: Added settings menu, removed symbol selection button (*), added highscore reset

View File

@ -1,7 +1,7 @@
# Play the game of 1024
Move the tiles by swiping to the lefthand, righthand or up- and downward side of the watch.
Move the tiles by swiping left, right, up- or downward over the watchface.
When two tiles with the same number are squashed together they will add up as exponentials:
@ -21,16 +21,28 @@ Use the side **BTN** to exit the game, score and tile positions will be saved.
## Buttons on the screen
- Button **U**: Undo the last move. There are currently a maximum of 4 undo levels. The level is indicated with a small number in the lower righthand corner of the Undo button
- Button **\***: Change the text on the tile to number, capitals or Roman numbers
- Button **R**: Reset the game. The Higscore will be remembered. You will be prompted first.
- Button **U**: Undo the last move. There are currently a maximum of 9 undo levels. The level is indicated with a small number in the lower righthand corner of the Undo button
- You can set the maximum undo level in the Apps settings menu.
- Button **R**: Reset the game. The Highscore will be remembered. You will be prompted first.
- The highscore value can be reset in the Apps settings menu.
Apps setting: ![Screenshot of the apps settings menu](./game1024_sc_dump_app_settings.png)
- Stuff you can change in de 1024 Game settings:
- Symbols on the cells: numerical, alphabetical or Roman
- Undo levels [0-9]
- Exit: how to exit the game: long or short press
- Debug mode: on or off. This will log all kinds of stuff in the console of the Web IDE
- Reset Highsccore: Tired of looking at the old highscore? Now you can set it to 0 again.
### Credits
Game 1024 is based on Saming's 2048 and Misho M. Petkovic 1024game.org and conceptually similar to Threes by Asher Vollmer.
In Dark theme with numbers:
![Screenshot from the Banglejs 2 watch with the game in dark theme](./game1024_sc_dump_dark.png)
![Screenshot from the Banglejs 2 watch with the game in dark theme](./game1024_sc_dump_dark_v0.09.png)
In Light theme with characters:
![Screenshot from the Banglejs 2 watch with the game in light theme](./game1024_sc_dump_light.png)
![Screenshot from the Banglejs 2 watch with the game in light theme](./game1024_sc_dump_light.v0.09.png)

View File

@ -1,6 +1,20 @@
const debugMode = 'off'; // valid values are: off, test, production, development
let settings = Object.assign({
// default values
maxUndoLevels: 4,
charIndex: 0,
clockMode: true,
debugMode: false,
}, require('Storage').readJSON("game1024.settings.json", true) || {});
const clockMode = settings.clockMode!==undefined ? settings.clockMode : true;
const debugMode = settings.debugMode!==undefined ? settings.debugMode : false; // #settings -- valid values are: true or false
const maxUndoLevels = settings.maxUndoLevels!==undefined ? settings.maxUndoLevels : 4; // #settings
const charIndex = settings.charIndex!==undefined ? settings.charIndex : 0; // #settings -- plain numbers on the grid
delete settings; // remove unneeded settings from memory
const middle = {x:Math.floor(g.getWidth()/2)-20, y: Math.floor(g.getHeight()/2)};
const rows = 4, cols = 4;
const rows = 4, cols = 4; // #settings
const borderWidth = 6;
const sqWidth = (Math.floor(Bangle.appRect.w - 48) / rows) - borderWidth;
const cellColors = [{bg:'#00FFFF', fg: '#000000'},
@ -13,12 +27,8 @@ const cellChars = [
['0','A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
['0','I', 'II', 'III', 'IV', 'V', 'VI', 'VII','VIII', 'IX', 'X']
];
// const numInitialCells = 2;
const maxUndoLevels = 4;
const noExceptions = true;
let charIndex = 0; // plain numbers on the grid
const themeBg = g.theme.bg;
const themeBg = g.theme.bg;
const scores = {
currentScore: 0,
@ -78,12 +88,12 @@ const snapshot = {
updCounter: function() {
this.counter = ++this.counter > this.interval ? 0 : this.counter;
},
dump: {gridsize: rows * cols, expVals: [], score: 0, highScore: 0, charIndex: charIndex},
dump: {gridsize: rows * cols, expVals: [], score: 0, highScore: 0},
write: function() {
require("Storage").writeJSON(this.snFileName, this.dump);
},
read: function () {
let sn = require("Storage").readJSON(this.snFileName, noExceptions);
let sn = require("Storage").readJSON(this.snFileName, true);
if ((typeof sn == "undefined") || (sn.gridsize !== rows * cols)) {
require("Storage").writeJSON(this.snFileName, this.dump);
return false;
@ -101,7 +111,6 @@ const snapshot = {
});
this.dump.score = scores.currentScore;
this.dump.highScore = scores.highScore;
this.dump.charIndex = charIndex;
},
make: function () {
this.updCounter();
@ -118,7 +127,7 @@ const snapshot = {
});
scores.currentScore = this.dump.score ? this.dump.score : 0;
scores.highScore = this.dump.highScore ? this.dump.highScore : 0 ;
charIndex = this.dump.charIndex ? this.dump.charIndex : 0 ;
if (this.dump.hasOwnProperty('charIndex')) delete this.dump.charIndex; // depricated in v0.09
}
},
reset: function () {
@ -129,12 +138,11 @@ const snapshot = {
}
this.dump.score = 0;
this.dump.highScore = scores.highScore;
this.dump.charIndex = charIndex;
this.write();
debug(() => console.log("reset D U M P E D!", this.dump));
}
};
const btnAtribs = {x: 134, w: 42, h: 42, fg:'#C0C0C0', bg:'#800000'};
const btnAtribs = {x: 134, w: 42, h: 50, fg:'#C0C0C0', bg:'#800000'};
const buttons = {
all: [],
draw: function () {
@ -314,7 +322,7 @@ class Cell {
}
drawBg() {
debug(()=>console.log("Drawbg!!"));
if (this.isRndm == true) {
if (this.isRndm) {
debug(()=>console.log('Random: (ax)', this.ax));
g.setColor(this.getColor(this.expVal).bg)
.fillRect(this.x0, this.y0, this.x1, this.y1)
@ -365,7 +373,7 @@ class Cell {
this.isRndm = true;
}
drawRndmIndicator(){
if (this.isRndm == true) {
if (this.isRndm) {
debug(()=>console.log('Random: (ax)', this.ax));
g.setColor(this.getColor(0).bg)
.fillPoly(this.ax,this.ay,this.bx,this.by,this.cx,this.cy);
@ -374,8 +382,9 @@ class Cell {
}
function undoGame() {
g.clear();
if (scores.lastScores.length > 0) {
if (scores.lastScores.length) {
g.clear();
allSquares.forEach(sq => {
sq.popFromUndo();
sq.drawBg();
@ -386,9 +395,9 @@ function undoGame() {
buttons.draw();
updUndoLvlIndex();
snapshot.make();
Bangle.loadWidgets();
Bangle.drawWidgets();
}
Bangle.loadWidgets();
Bangle.drawWidgets();
}
function addToUndo() {
allSquares.forEach(sq => {
@ -487,8 +496,8 @@ function initGame() {
drawGrid();
scores.draw();
buttons.draw();
// Clock mode allows short-press on button to exit
Bangle.setUI("clock");
// #settings Clock mode allows short-press on button to exit
if(clockMode) Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
@ -507,8 +516,8 @@ function drawPopUp(message,cb) {
rDims.x+10, rDims.y2-40
]);
buttons.all.forEach(btn => {btn.disable();});
const btnYes = new Button('yes', rDims.x+16, rDims.y2-80, 54, btnAtribs.h, 'YES', btnAtribs.fg, btnAtribs.bg, cb, true);
const btnNo = new Button('no', rDims.x2-80, rDims.y2-80, 54, btnAtribs.h, 'NO', btnAtribs.fg, btnAtribs.bg, cb, true);
const btnYes = new Button('yes', rDims.x+16, rDims.y2-88, 54, btnAtribs.h, 'YES', btnAtribs.fg, btnAtribs.bg, cb, true);
const btnNo = new Button('no', rDims.x2-80, rDims.y2-88, 54, btnAtribs.h, 'NO', btnAtribs.fg, btnAtribs.bg, cb, true);
btnYes.draw();
btnNo.draw();
g.setColor('#000000');
@ -560,14 +569,8 @@ function resetGame() {
* @param {function} func function to call like console.log()
*/
const debug = (func) => {
switch (debugMode) {
case "development":
if (typeof func === 'function') {
func();
}
break;
case "off":
default: break;
if (debugMode) {
if (typeof func === 'function') func();
}
};
@ -690,13 +693,9 @@ function updUndoLvlIndex() {
.drawString(scores.lastScores.length, x, y);
}
}
function incrCharIndex() {
charIndex++;
if (charIndex >= cellChars.length) charIndex = 0;
drawGrid();
}
buttons.add(new Button('undo', btnAtribs.x, 25, btnAtribs.w, btnAtribs.h, 'U', btnAtribs.fg, btnAtribs.bg, undoGame, true));
buttons.add(new Button('chars', btnAtribs.x, 71, btnAtribs.w, 31, '*', btnAtribs.fg, btnAtribs.bg, function(){incrCharIndex();}, true));
buttons.add(new Button('restart', btnAtribs.x, 106, btnAtribs.w, btnAtribs.h, 'R', btnAtribs.fg, btnAtribs.bg, function(){drawPopUp('Do you want\nto restart?',handlePopUpClicks);}, true));
initGame();

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -1,7 +1,7 @@
{ "id": "game1024",
"name": "1024 Game",
"shortName" : "1024 Game",
"version": "0.08",
"version": "0.09",
"icon": "game1024.png",
"screenshots": [ {"url":"screenshot.png" } ],
"readme":"README.md",
@ -12,6 +12,7 @@
"supports" : ["BANGLEJS2"],
"storage": [
{"name":"game1024.app.js","url":"app.js"},
{"name":"game1024.settings.js","url":"settings.js"},
{"name":"game1024.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

70
apps/game1024/settings.js Normal file
View File

@ -0,0 +1,70 @@
(function(back) {
var FILE = "game1024.settings.json";
var scoreFile = "game1024.json";
// Load settings
var settings = Object.assign({
maxUndoLevels: 5,
charIndex: 0,
clockMode: true,
debugMode: false,
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
var symbols = ["1 2 3 ...", "A B C ...", "I II III..."];
var settingsMenu = {
"" : { "title" : "1024 Game" },
"< Back" : () => back(),
"Symbols": {
value: 0|settings.charIndex,
min:0,max:symbols.length-1,
format: v=>symbols[v],
onchange: v=> { settings.charIndex=v; writeSettings();}
}
,
"Undo levels:": {
value: 0|settings.maxUndoLevels, // 0| converts undefined to 0
min: 0, max: 9,
onchange: v => {
settings.maxUndoLevels = v;
writeSettings();
}
},
"Exit press:": {
value: !settings.debugMode, // ! converts undefined to true
format: v => v?"short":"long",
onchange: v => {
settings.debugMode = v;
writeSettings();
},
},
"Debug mode:": {
value: !!settings.debugMode, // !! converts undefined to false
format: v => v?"On":"Off",
onchange: v => {
settings.debugMode = v;
writeSettings();
}
},
"Reset Highscore": () => {
E.showPrompt('Reset Highscore?').then((v) => {
let delay = 50;
if (v) {
delay = 500;
let sF = require("Storage").readJSON(scoreFile, true);
if (typeof sF !== "undefined") {
E.showMessage('Resetting');
sF.highScore = 0;
require("Storage").writeJSON(scoreFile, sF);
} else {
E.showMessage('No highscore!');
}
}
setTimeout(() => E.showMenu(settingsMenu), delay);
});
}
}
// Show the menu
E.showMenu(settingsMenu);
})

View File

@ -8,6 +8,7 @@
"tags": "clock",
"screenshots": [{"url":"bangle1-high-contrast-clock-screenshot.png"}],
"supports": ["BANGLEJS"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"hcclock.app.js","url":"hcclock.app.js"},

View File

@ -7,6 +7,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"screenshots": [{"url":"bangle1-impercise-word-clock-screenshot.png"}],
"allow_emulator": true,
"storage": [

View File

@ -8,6 +8,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"intclock.app.js","url":"app.js"},

View File

@ -7,6 +7,7 @@
"icon": "intervals.png",
"tags": "",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"intervals.app.js","url":"intervals.app.js"},
{"name":"intervals.img","url":"intervals-icon.js","evaluate":true}

View File

@ -7,6 +7,7 @@
"tags": "tool,system,ios,apple,messages,notifications",
"dependencies": {"messages":"app"},
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"ios.app.js","url":"app.js"},
{"name":"ios.img","url":"app-icon.js","evaluate":true},

1
apps/kbswipe/ChangeLog Normal file
View File

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

36
apps/kbswipe/README.md Normal file
View File

@ -0,0 +1,36 @@
# Swipe Keyboard
A library that provides the ability to input text by swiping PalmOS Graffiti-style characters onto the screen.
To get a legend of available characters, just tap the screen.
## Usage
In your app's metadata, add:
```
"dependencies": {"textinput":"type"},
```
From inside your app, call:
```
Bangle.loadWidgets();
Bangle.drawWidgets();
require("textinput").input({text:"Foo"}).then(result => {
console.log("Text input", E.toJS(result));
});
```
The first argument to `input` is an object containing the following:
* `text` - initial text to edit
(in the future, the ability to restrict usage of newline/etc may be added)
## Make your own
You can create your own keyboard input apps. Just ensure that they have
`"type":"textinput",` in their metadata and provide a library called `textinput`
that exports an `input` method.

BIN
apps/kbswipe/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

110
apps/kbswipe/lib.js Normal file
View File

@ -0,0 +1,110 @@
/* To make your own strokes, type:
Bangle.on('stroke',print)
on the left of the IDE, then do a stroke and copy out the Uint8Array line
*/
exports.getStrokes = function(cb) {
cb("a", new Uint8Array([58, 159, 58, 155, 62, 144, 69, 127, 77, 106, 86, 90, 94, 77, 101, 68, 108, 62, 114, 59, 121, 59, 133, 61, 146, 70, 158, 88, 169, 107, 176, 124, 180, 135, 183, 144, 185, 152]));
cb("b", new Uint8Array([51, 47, 51, 77, 56, 123, 60, 151, 65, 163, 68, 164, 68, 144, 67, 108, 67, 76, 72, 43, 104, 51, 121, 74, 110, 87, 109, 95, 131, 117, 131, 140, 109, 152, 88, 157]));
cb("c", new Uint8Array([153, 62, 150, 62, 145, 62, 136, 62, 123, 62, 106, 65, 85, 70, 65, 75, 50, 82, 42, 93, 37, 106, 36, 119, 36, 130, 40, 140, 49, 147, 61, 153, 72, 156, 85, 157, 106, 158, 116, 158]));
cb("d", new Uint8Array([57, 178, 57, 176, 55, 171, 52, 163, 50, 154, 49, 146, 47, 135, 45, 121, 44, 108, 44, 97, 44, 85, 44, 75, 44, 66, 44, 58, 44, 48, 44, 38, 46, 31, 48, 26, 58, 21, 75, 20, 99, 26, 120, 35, 136, 51, 144, 70, 144, 88, 137, 110, 124, 131, 106, 145, 88, 153]));
cb("e", new Uint8Array([150, 72, 141, 69, 114, 68, 79, 69, 48, 77, 32, 81, 31, 85, 46, 91, 73, 95, 107, 100, 114, 103, 83, 117, 58, 134, 66, 143, 105, 148, 133, 148, 144, 148]));
cb("f", new Uint8Array([157, 52, 155, 52, 148, 52, 137, 52, 124, 52, 110, 52, 96, 52, 83, 52, 74, 52, 67, 52, 61, 52, 57, 52, 55, 52, 52, 52, 52, 54, 52, 58, 52, 64, 54, 75, 58, 97, 59, 117, 60, 130]));
cb("g", new Uint8Array([160, 66, 153, 62, 129, 58, 90, 56, 58, 57, 38, 65, 31, 86, 43, 125, 69, 152, 116, 166, 145, 154, 146, 134, 112, 116, 85, 108, 97, 106, 140, 106, 164, 106]));
cb("h", new Uint8Array([58, 50, 58, 55, 58, 64, 58, 80, 58, 102, 58, 122, 58, 139, 58, 153, 58, 164, 58, 171, 58, 177, 58, 179, 58, 181, 58, 180, 58, 173, 58, 163, 59, 154, 61, 138, 64, 114, 68, 95, 72, 84, 80, 79, 91, 79, 107, 82, 123, 93, 137, 111, 145, 130, 149, 147, 150, 154, 150, 159]));
cb("i", new Uint8Array([89, 48, 89, 49, 89, 51, 89, 55, 89, 60, 89, 68, 89, 78, 89, 91, 89, 103, 89, 114, 89, 124, 89, 132, 89, 138, 89, 144, 89, 148, 89, 151, 89, 154, 89, 156, 89, 157, 89, 158]));
cb("j", new Uint8Array([130, 57, 130, 61, 130, 73, 130, 91, 130, 113, 130, 133, 130, 147, 130, 156, 130, 161, 130, 164, 130, 166, 129, 168, 127, 168, 120, 168, 110, 168, 91, 167, 81, 167, 68, 167]));
cb("k", new Uint8Array([149, 63, 147, 68, 143, 76, 136, 89, 126, 106, 114, 123, 100, 136, 86, 147, 72, 153, 57, 155, 45, 152, 36, 145, 29, 131, 26, 117, 26, 104, 27, 93, 30, 86, 35, 80, 45, 77, 62, 80, 88, 96, 113, 116, 130, 131, 140, 142, 145, 149, 148, 153]));
cb("l", new Uint8Array([42, 55, 42, 59, 42, 69, 44, 87, 44, 107, 44, 128, 44, 143, 44, 156, 44, 163, 44, 167, 44, 169, 45, 170, 49, 170, 59, 169, 76, 167, 100, 164, 119, 162, 139, 160, 163, 159]));
cb("m", new Uint8Array([49, 165, 48, 162, 46, 156, 44, 148, 42, 138, 42, 126, 42, 113, 43, 101, 45, 91, 47, 82, 49, 75, 51, 71, 54, 70, 57, 70, 61, 74, 69, 81, 75, 91, 84, 104, 94, 121, 101, 132, 103, 137, 106, 130, 110, 114, 116, 92, 125, 75, 134, 65, 139, 62, 144, 66, 148, 83, 151, 108, 155, 132, 157, 149]));
cb("n", new Uint8Array([50, 165, 50, 160, 50, 153, 50, 140, 50, 122, 50, 103, 50, 83, 50, 65, 50, 52, 50, 45, 50, 43, 52, 52, 57, 67, 66, 90, 78, 112, 93, 131, 104, 143, 116, 152, 127, 159, 135, 160, 141, 150, 148, 125, 154, 96, 158, 71, 161, 56, 162, 49]));
cb("o", new Uint8Array([107, 58, 104, 58, 97, 61, 87, 68, 75, 77, 65, 88, 58, 103, 54, 116, 53, 126, 55, 135, 61, 143, 75, 149, 91, 150, 106, 148, 119, 141, 137, 125, 143, 115, 146, 104, 146, 89, 142, 78, 130, 70, 116, 65, 104, 62]));
cb("p", new Uint8Array([52, 59, 52, 64, 54, 73, 58, 88, 61, 104, 65, 119, 67, 130, 69, 138, 71, 145, 71, 147, 71, 148, 71, 143, 70, 133, 68, 120, 67, 108, 67, 97, 67, 89, 68, 79, 72, 67, 83, 60, 99, 58, 118, 58, 136, 63, 146, 70, 148, 77, 145, 84, 136, 91, 121, 95, 106, 97, 93, 97, 82, 97]));
cb("q", new Uint8Array([95, 59, 93, 59, 88, 59, 79, 59, 68, 61, 57, 67, 50, 77, 48, 89, 48, 103, 50, 117, 55, 130, 65, 140, 76, 145, 85, 146, 94, 144, 101, 140, 105, 136, 106, 127, 106, 113, 100, 98, 92, 86, 86, 79, 84, 75, 84, 72, 91, 69, 106, 67, 126, 67, 144, 67, 158, 67, 168, 67, 173, 67, 177, 67]));
cb("r", new Uint8Array([53, 49, 53, 62, 53, 91, 53, 127, 53, 146, 53, 147, 53, 128, 53, 94, 53, 69, 62, 44, 82, 42, 94, 50, 92, 68, 82, 85, 77, 93, 80, 102, 95, 119, 114, 134, 129, 145, 137, 150]));
cb("s", new Uint8Array([159, 72, 157, 70, 155, 68, 151, 66, 145, 63, 134, 60, 121, 58, 108, 56, 96, 55, 83, 55, 73, 55, 64, 56, 57, 60, 52, 65, 49, 71, 49, 76, 50, 81, 55, 87, 71, 94, 94, 100, 116, 104, 131, 108, 141, 114, 145, 124, 142, 135, 124, 146, 97, 153, 70, 157, 52, 158]));
cb("t", new Uint8Array([45, 55, 48, 55, 55, 55, 72, 55, 96, 55, 120, 55, 136, 55, 147, 55, 152, 55, 155, 55, 157, 55, 158, 56, 158, 60, 156, 70, 154, 86, 151, 102, 150, 114, 148, 125, 148, 138, 148, 146]));
cb("u", new Uint8Array([35, 52, 35, 59, 35, 73, 35, 90, 36, 114, 38, 133, 42, 146, 49, 153, 60, 157, 73, 158, 86, 156, 100, 152, 112, 144, 121, 131, 127, 114, 132, 97, 134, 85, 135, 73, 136, 61, 136, 56]));
cb("v", new Uint8Array([36, 55, 37, 59, 40, 68, 45, 83, 51, 100, 58, 118, 64, 132, 69, 142, 71, 149, 73, 156, 76, 158, 77, 160, 77, 159, 80, 151, 82, 137, 84, 122, 86, 111, 90, 91, 91, 78, 91, 68, 91, 63, 92, 61, 97, 61, 111, 61, 132, 61, 150, 61, 162, 61]));
cb("w", new Uint8Array([33, 58, 34, 81, 39, 127, 44, 151, 48, 161, 52, 162, 57, 154, 61, 136, 65, 115, 70, 95, 76, 95, 93, 121, 110, 146, 119, 151, 130, 129, 138, 84, 140, 56, 140, 45]));
cb("x", new Uint8Array([56, 63, 56, 67, 57, 74, 60, 89, 66, 109, 74, 129, 85, 145, 96, 158, 107, 164, 117, 167, 128, 164, 141, 155, 151, 140, 159, 122, 166, 105, 168, 89, 170, 81, 170, 73, 169, 66, 161, 63, 141, 68, 110, 83, 77, 110, 55, 134, 47, 145]));
cb("y", new Uint8Array([42, 56, 42, 70, 48, 97, 62, 109, 85, 106, 109, 90, 126, 65, 134, 47, 137, 45, 137, 75, 127, 125, 98, 141, 70, 133, 65, 126, 92, 137, 132, 156, 149, 166]));
cb("z", new Uint8Array([29, 62, 35, 62, 43, 62, 63, 62, 87, 62, 110, 62, 125, 62, 134, 62, 138, 62, 136, 63, 122, 68, 103, 77, 85, 91, 70, 107, 59, 120, 50, 132, 47, 138, 43, 143, 41, 148, 42, 151, 53, 155, 80, 157, 116, 158, 146, 158, 163, 158]));
cb("\b", new Uint8Array([183, 103, 182, 103, 180, 103, 176, 103, 169, 103, 159, 103, 147, 103, 133, 103, 116, 103, 101, 103, 85, 103, 73, 103, 61, 103, 52, 103, 38, 103, 34, 103, 29, 103, 27, 103, 26, 103, 25, 103, 24, 103]));
cb(" ", new Uint8Array([39, 118, 40, 118, 41, 118, 44, 118, 47, 118, 52, 118, 58, 118, 66, 118, 74, 118, 84, 118, 94, 118, 104, 117, 114, 116, 123, 116, 130, 116, 144, 116, 149, 116, 154, 116, 158, 116, 161, 116, 163, 116]));
};
exports.input = function(options) {
options = options||{};
var text = options.text;
if ("string"!=typeof text) text="";
Bangle.strokes = {};
exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) );
var flashToggle = false;
const R = Bangle.appRect;
function draw(noclear) {
g.reset();
if (!noclear) g.clearRect(R);
var l = g.setFont("6x8:4").wrapString(text+(flashToggle?"_":" "), R.w-8);
if (l.length>4) l=l.slice(-4);
g.drawString(l.join("\n"),R.x+4,R.y+4);
}
function show() {
g.reset();
g.clearRect(R).setColor("#f00");
var n=0;
exports.getStrokes((id,s) => {
var x = n%6;
var y = (n-x)/6;
s = g.transformVertices(s, {scale:0.16, x:R.x+x*30-4, y:R.y+y*30-4});
g.fillRect(s[0]-1,s[1]-2,s[0]+1,s[1]+1);
g.drawPoly(s);
n++;
});
}
function strokeHandler(o) {
//print(o);
if (!flashInterval)
flashInterval = setInterval(() => {
flashToggle = !flashToggle;
draw();
}, 1000);
if (o.stroke!==undefined) {
var ch = o.stroke;
if (ch=="\b") text = text.slice(0,-1);
else text += ch;
}
flashToggle = true;
draw();
}
Bangle.on('stroke',strokeHandler);
g.reset().clearRect(R);
show();
draw(true);
var flashInterval;
return new Promise((resolve,reject) => {
var l;//last event
Bangle.setUI({mode:"custom", drag:e=>{
if (l) g.reset().setColor("#f00").drawLine(l.x,l.y,e.x,e.y);
l = e.b ? e : 0;
},touch:() => {
if (flashInterval) clearInterval(flashInterval);
flashInterval = undefined;
show();
}, back:()=>{
Bangle.removeListener("stroke", strokeHandler);
clearInterval(flashInterval);
Bangle.setUI();
g.clearRect(Bangle.appRect);
resolve(text);
}});
});
};

View File

@ -0,0 +1,14 @@
{ "id": "kbswipe",
"name": "Swipe keyboard",
"version":"0.01",
"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"}],
"readme": "README.md",
"storage": [
{"name":"textinput","url":"lib.js"}
]
}

BIN
apps/kbswipe/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

1
apps/kbtouch/ChangeLog Normal file
View File

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

37
apps/kbtouch/README.md Normal file
View File

@ -0,0 +1,37 @@
# Touch Keyboard
A library that provides an on-screen keyboard for text input.
## Usage
In your app's metadata, add:
```
"dependencies": {"textinput":"type"},
```
From inside your app, call:
```
Bangle.loadWidgets();
Bangle.drawWidgets();
require("textinput").input({text:"Foo"}).then(result => {
console.log("Text input", E.toJS(result));
});
```
The first argument to `input` is an object containing the following:
* `text` - initial text to edit
(in the future, the ability to restrict usage of newline/etc may be added)
## Make your own
You can create your own keyboard input apps. Just ensure that they have
`"type":"textinput",` in their metadata and provide a library called `textinput`
that exports an `input` method.
## To-do
Make this Bangle.js 1 compatible (use left/right touch and up/down buttons)

BIN
apps/kbtouch/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

132
apps/kbtouch/lib.js Normal file
View File

@ -0,0 +1,132 @@
exports.input = function(options) {
options = options||{};
var text = options.text;
if ("string"!=typeof text) text="";
// Key Maps for Keyboard
var KEYMAPLOWER = [
"`1234567890-=\b",
"\2qwertyuiop[]\n",
"\2asdfghjkl;'#\n",
" \\zxcvbnm,./ ",
];
var KEYMAPUPPER = [
"¬!\"£$%^&*()_+\b",
"\2QWERTYUIOP{}\n",
"\2ASDFGHJKL:@~\n",
" |ZXCVBNM<>? ",
];
var KEYIMGL = Graphics.createImage(`
#
###
#####
#
#
#
#
#
#
#
#
#
#
#
#
#
`);KEYIMGL.transparent=0;
var KEYIMGR = Graphics.createImage(`
#
##
#####
##
#
###
#
#
#
#
#
#####
###
#
#`);KEYIMGR.transparent=0;
/* If a char in the keymap is >=128,
subtract 128 and look in this array for
multi-character key codes*/
var KEYEXTRA = [
String.fromCharCode(27,91,68), // 0x80 left
String.fromCharCode(27,91,67), // 0x81 right
String.fromCharCode(27,91,65), // 0x82 up
String.fromCharCode(27,91,66), // 0x83 down
String.fromCharCode(27,91,53,126), // 0x84 page up
String.fromCharCode(27,91,54,126), // 0x85 page down
];
// 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;
function draw() {
var map = kbShift ? KEYMAPUPPER : KEYMAPLOWER;
//g.drawImage(KEYIMG,0,yoff);
g.reset().setFont("6x8:2");
g.clearRect(R);
if (kbx>=0)
g.setColor(g.theme.bgH).fillRect(xoff+kbx*PX,yoff+kby*PY, xoff+(kbx+1)*PX-1,yoff+(kby+1)*PY-1).setColor(g.theme.fg);
g.drawImage(KEYIMGL,xoff,yoff+PY,{scale:2});
g.drawImage(KEYIMGR,xoff+PX*13,yoff,{scale:2});
g.drawString(map[0],xoff,yoff);
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);
g.flip();
}
g.reset().clearRect(R);
draw();
var flashInterval = setInterval(() => {
flashToggle = !flashToggle;
draw();
}, 1000);
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;
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();
},back:()=>{
clearInterval(flashInterval);
Bangle.setUI();
g.clearRect(Bangle.appRect);
resolve(text);
}});
});
};

View File

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

BIN
apps/kbtouch/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -17,3 +17,4 @@
0.17: Settings for mph/kph and other minor improvements.
0.18: Fullscreen mode can now be enabled or disabled in the settings.
0.19: Alarms can not go bigger than 100.
0.20: Use alarm for alarm functionality instead of own implementation.

View File

@ -3,7 +3,8 @@
A simple LCARS inspired clock.
Note: To display the steps, the wpedom app is required. To show weather data
such as temperature, humidity or window you BangleJS must be connected
with Gadgetbride and the weather app must be installed.
with Gadgetbride and the weather app must be installed. To use the timer
the "sched" app must be installed on your device.
## Control
* Tap left / right to change between screens.
@ -15,7 +16,7 @@ with Gadgetbride and the weather app must be installed.
* Tab on left/right to switch between different screens.
* Cusomizable data that is shown on screen 1 (steps, weather etc.)
* Shows random and real images of planets.
* Tap on top/bottom of screen 1 to activate an alarm.
* Tap on top/bottom of screen 1 to activate an alarm. Depends on widtmr.
* The lower orange line indicates the battery level.
* Display graphs (day or month) for steps + hrm on the second screen.
@ -36,8 +37,9 @@ Access different screens via tap on the left/ right side of the screen
![](screenshot_1.png)
![](screenshot_2.png)
## Creator
- [David Peer](https://github.com/peerdavid)
## Contributors
- [David Peer](https://github.com/peerdavid).
- [Adam Schmalhofer](https://github.com/adamschmalhofer).
- [Jon Warrington](https://github.com/BartokW).
- [Adam Schmalhofer](https://github.com/adamschmalhofer)
- [Jon Warrington](https://github.com/BartokW)

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