Merge branch 'master' into master

pull/407/head
Gordon Williams 2020-05-12 07:58:38 +01:00 committed by GitHub
commit 2c6248c031
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 334 additions and 66 deletions

View File

@ -1,2 +1,4 @@
0.02: Fix deletion of apps - now use files list in app.info (fix #262)
0.03: Add support for data files
0.04: Add functionality to sort apps manually or alphabetically ascending/descending.
0.05: Tweaks to help with memory usage

View File

@ -1,9 +1,7 @@
const storage = require('Storage');
const store = require('Storage');
const boolFormat = (v) => v ? "On" : "Off";
let m;
function showMainMenu() {
const mainmenu = {
'': {
@ -12,25 +10,29 @@ function showMainMenu() {
'Free': {
value: undefined,
format: (v) => {
return storage.getFree();
return store.getFree();
},
onchange: () => {}
},
'Compact': () => {
E.showMessage('Compacting...');
try {
storage.compact();
store.compact();
} catch (e) {
}
m = showMainMenu();
showMainMenu();
},
'Apps': ()=> m = showApps(),
'Apps': ()=> showApps(),
'Sort Apps': () => showSortAppsMenu(),
'< Back': ()=> {load();}
};
return E.showMenu(mainmenu);
E.showMenu(mainmenu);
}
function isGlob(f) {
return /[?*]/.test(f);
}
function isGlob(f) {return /[?*]/.test(f)}
function globToRegex(pattern) {
const ESCAPE = '.*+-?^${}()|[]\\';
const regex = pattern.replace(/./g, c => {
@ -44,40 +46,41 @@ function globToRegex(pattern) {
}
function eraseFiles(app) {
app.files.split(",").forEach(f=>storage.erase(f));
app.files.split(",").forEach(f=>store.erase(f));
}
function eraseData(app) {
if(!app.data) return;
const d=app.data.split(';'),
files=d[0].split(','),
sFiles=(d[1]||'').split(',');
let erase = f=>storage.erase(f);
let erase = f=>store.erase(f);
files.forEach(f=>{
if (!isGlob(f)) erase(f);
else storage.list(globToRegex(f)).forEach(erase);
})
erase = sf=>storage.open(sf,'r').erase();
else store.list(globToRegex(f)).forEach(erase);
});
erase = sf=>store.open(sf,'r').erase();
sFiles.forEach(sf=>{
if (!isGlob(sf)) erase(sf);
else storage.list(globToRegex(sf+'\u0001'))
else store.list(globToRegex(sf+'\u0001'))
.forEach(fs=>erase(fs.substring(0,fs.length-1)));
})
});
}
function eraseApp(app, files,data) {
E.showMessage('Erasing\n' + app.name + '...');
if (files) eraseFiles(app)
if (data) eraseData(app)
if (files) eraseFiles(app);
if (data) eraseData(app);
}
function eraseOne(app, files,data){
E.showPrompt('Erase\n'+app.name+'?').then((v) => {
if (v) {
Bangle.buzz(100, 1);
eraseApp(app, files,data)
eraseApp(app, files, data);
showApps();
} else {
showAppMenu(app)
showAppMenu(app);
}
})
});
}
function eraseAll(apps, files,data) {
E.showPrompt('Erase all?').then((v) => {
@ -87,7 +90,7 @@ function eraseAll(apps, files,data) {
eraseApp(apps[n], files,data);
}
showApps();
})
});
}
function showAppMenu(app) {
@ -95,16 +98,16 @@ function showAppMenu(app) {
'': {
'title': app.name,
},
'< Back': () => m = showApps(),
}
'< Back': () => showApps(),
};
if (app.data) {
appmenu['Erase Completely'] = () => eraseOne(app, true, true)
appmenu['Erase App,Keep Data'] = () => eraseOne(app,true, false)
appmenu['Only Erase Data'] = () => eraseOne(app,false, true)
appmenu['Erase Completely'] = () => eraseOne(app, true, true);
appmenu['Erase App,Keep Data'] = () => eraseOne(app, true, false);
appmenu['Only Erase Data'] = () => eraseOne(app, false, true);
} else {
appmenu['Erase'] = () => eraseOne(app,true, false)
appmenu['Erase'] = () => eraseOne(app, true, false);
}
return E.showMenu(appmenu);
E.showMenu(appmenu);
}
function showApps() {
@ -112,20 +115,20 @@ function showApps() {
'': {
'title': 'Apps',
},
'< Back': () => m = showMainMenu(),
'< Back': () => showMainMenu(),
};
var list = storage.list(/\.info$/).filter((a)=> {
var list = store.list(/\.info$/).filter((a)=> {
return a !== 'setting.info';
}).sort().map((app) => {
var ret = storage.readJSON(app,1)||{};
var ret = store.readJSON(app,1)||{};
ret[''] = app;
return ret;
});
if (list.length > 0) {
list.reduce((menu, app) => {
menu[app.name] = () => m = showAppMenu(app);
menu[app.name] = () => showAppMenu(app);
return menu;
}, appsmenu);
appsmenu['Erase All'] = () => {
@ -144,7 +147,82 @@ function showApps() {
onchange: ()=> {}
};
}
return E.showMenu(appsmenu);
E.showMenu(appsmenu);
}
m = showMainMenu();
function showSortAppsMenu() {
const sorterMenu = {
'': {
'title': 'App Sorter',
},
'< Back': () => showMainMenu(),
'Sort: manually': ()=> showSortAppsManually(),
'Sort: alph. ASC': () => {
E.showMessage('Sorting:\nAlphabetically\nascending ...');
sortAlphabet(false);
},
'Sort: alph. DESC': () => {
E.showMessage('Sorting:\nAlphabetically\ndescending ...');
sortAlphabet(true);
}
};
E.showMenu(sorterMenu);
}
function showSortAppsManually() {
const appsSorterMenu = {
'': {
'title': 'Sort: manually',
},
'< Back': () => showSortAppsMenu(),
};
let appList = getAppsList();
if (appList.length > 0) {
appList.reduce((menu, app) => {
menu[app.name] = {
value: app.sortorder || 0,
min: 0,
max: appList.length,
step: 1,
onchange: val => setSortorder(app, val)
};
return menu;
}, appsSorterMenu);
} else {
appsSorterMenu['...No Apps...'] = {
value: undefined,
format: ()=> '',
onchange: ()=> {}
};
}
E.showMenu(appsSorterMenu);
}
function setSortorder(app, val) {
app = store.readJSON(app.id + '.info', 1);
app.sortorder = val;
store.writeJSON(app.id + '.info', app);
}
function getAppsList() {
return store.list('.info').map((a)=> {
let app = store.readJSON(a, 1) || {};
if (app.type !== 'widget') {
return {id: app.id, name: app.name, sortorder: app.sortorder};
}
}).filter((a) => a).sort(sortHelper());
}
function sortAlphabet(desc) {
let appsSorted = desc ? getAppsList().reverse() : getAppsList();
appsSorted.forEach((a, i) => {
setSortorder(a, i);
});
showSortAppsMenu();
}
function sortHelper() {
return (a, b) => (a.name > b.name) - (a.name < b.name);
}
showMainMenu();

1
apps/getup/ChangeLog Normal file
View File

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

7
apps/getup/README.md Normal file
View File

@ -0,0 +1,7 @@
# Get Up
Reminds you to getup every x minutes (default: 20).
Sitting to long is dangerous!
Sit and move time configurable in settings.

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A75wACCJugAAguaGBouFGCwuF53NFxem6PX6/R0wwVF4xgJEwOsFoMrlYDB1gwUL55dCFQIvE65hUL54jBRgQvF6JgaRxQpCF4SUC67BV5ouLF40yGAOBF64ANR4vXwJhCR6oABq4ACF5TvDAAOsL4LvS4wuGGBi6DGIYuSAAQvGMJiSC6JdSGAovPGAQAFXSQvDrgrBqwvMGAzqTF4d/F4owLADKQGmQv/F7eAF4UySQQwn0ZcCq0ylkySFYyDMEgvDvQwFAAYvk0aLBqy/CAAaUhSAi+BX4QzCAwJkgF4eAX4gzDSsIvDeIzFlGAhhEF9QAHBwIwvF8IwSF7oxMF8gALSEQwRF/4v/YH4v/GD4usAH4A/AH4ARA="))

45
apps/getup/app.js Normal file
View File

@ -0,0 +1,45 @@
//init settings
const storage = require("Storage");
const SETTINGS_FILE = 'getup.settings.json';
function setting(key) {
const DEFAULTS = {
'sitTime' : 20,
'moveTime' : 1
}
if (!settings) {
loadSettings();
}
return (key in settings) ? settings[key] : DEFAULTS[key];
}
let settings;
function loadSettings() {
settings = storage.readJSON(SETTINGS_FILE, 1) || {};
}
//vibrate, draw move message and start timer for sitting message
function remind() {
Bangle.buzz(1000,1);
g.clear();
g.setFont("8x12",4);
g.setColor(0x03E0);
g.drawString("MOVE!", g.getWidth()/2, g.getHeight()/2);
setTimeout(print_message,setting("moveTime") * 60000);
}
//draw sitting message and start timer for reminder
function print_message(){
g.clear();
g.setFont("8x12",2);
g.setColor(0xF800);
g.drawString("sitting is dangerous!", g.getWidth()/2, g.getHeight()/2);
setTimeout(remind,setting("sitTime") * 60000);
}
//init graphics
require("Font8x12").add(Graphics);
g.setFontAlign(0,0);
g.flip();
print_message();

BIN
apps/getup/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

48
apps/getup/settings.js Normal file
View File

@ -0,0 +1,48 @@
// This file should contain exactly one function, which shows the app's settings
/**
* @param {function} back Use back() to return to settings menu
*/
(function(back) {
const SETTINGS_FILE = 'getup.settings.json';
// initialize with default settings...
let s = {
'sitTime' : 20,
'moveTime' : 1
};
// ...and overwrite them with any saved values
// This way saved values are preserved if a new version adds more settings
const storage = require('Storage');
const saved = storage.readJSON(SETTINGS_FILE, 1) || {};
for (const key in saved) {
s[key] = saved[key];
}
// creates a function to safe a specific setting, e.g. save('color')(1)
function save(key) {
return function (value) {
s[key] = value;
storage.write(SETTINGS_FILE, s);
};
}
const menu = {
'': { 'title': 'Get Up' },
'< Back': back,
'Sit time (min)': {
value: s.sitTime,
min: 0,
max: 10000,
step: 1,
onchange: save('sitTime'),
},
'Move time (min)': {
value: s.moveTime,
min: 0,
max: 5000,
step: 1,
onchange: save('moveTime'),
},
};
E.showMenu(menu);
});

View File

@ -3,3 +3,5 @@
0.03: Add {msb:true} so that on new builds, color is correct for 16 bit
0.04: Fix hour alignment for single digits
Scaling for background images <240px wide
0.05: Fix memory/interval leak when LCD turns on
0.06: Support 12 hour time

View File

@ -3,6 +3,7 @@ Draws a fullscreen image from flash memory
Saves a small image to flash which is just the area where the clock is
Keeps an offscreen buffer and draws the time to that
*/
var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
var inf = require("Storage").readJSON("imgclock.face.json");
var img = require("Storage").read("imgclock.face.img");
var IX = inf.x, IY = inf.y, IBPP = inf.bpp;
@ -29,6 +30,12 @@ if (!bgimg || !bgimg.length) createBgImg();
function draw() {
var t = new Date();
var hours = t.getHours();
var meridian = "";
if (is12Hour) {
meridian = (hours < 12) ? "AM" : "PM";
hours = ((hours + 11) % 12) + 1;
}
// quickly set background image
new Uint8Array(cg.buffer).set(bgimg);
// draw time
@ -36,7 +43,7 @@ function draw() {
var x = 40;
cg.setFont("7x11Numeric7Seg",3);
cg.setFontAlign(1,-1);
cg.drawString(t.getHours(), x, 0);
cg.drawString(hours, x, 0);
x+=2;
cg.fillRect(x, 10, x+2, 10+2).fillRect(x, 20, x+2, 20+2);
x+=6;
@ -46,6 +53,7 @@ function draw() {
cg.setFont("7x11Numeric7Seg",1);
cg.drawString(("0"+t.getSeconds()).substr(-2), x, 20);
cg.setFont("6x8",1);
cg.drawString(meridian, x+2, 0);
cg.setFontAlign(0,-1);
cg.drawString(locale.date(t),IW/2,IH-8);
// draw to screen
@ -65,7 +73,7 @@ Bangle.on('lcdPower',on=>{
if (secondInterval) clearInterval(secondInterval);
secondInterval = undefined;
if (on) {
setInterval(draw,1000);
secondInterval = setInterval(draw,1000);
draw();
}
});

View File

@ -1 +1,2 @@
0.01: Init
0.02: fix 3/4 moon orientation

View File

@ -55,7 +55,7 @@ function drawMoon(d) {
},
// 3/4 ascending
4: () => {
moon[7]();
moon[3]();
g.setColor(MOON).fillEllipse(
moonX - moonR / 2,
moonY - moonR,
@ -70,7 +70,7 @@ function drawMoon(d) {
},
// 3/4 descending
6: () => {
moon[3]();
moon[7]();
g.setColor(MOON).fillEllipse(
moonX - moonR / 2,
moonY - moonR,

View File

@ -9,6 +9,7 @@ const distanceUnits = { // how many meters per X?
const speedUnits = { // how many kph per X?
"kmh": 1,
"kph": 1,
"km/h": 1,
"mph": 1.60934
};
@ -129,9 +130,9 @@ var locales = {
thousands_sep: ",",
currency_symbol: "$",
int_curr_symbol: "CAD",
speed: "mph",
distance: { 0: "mi", 1: "kmi" },
temperature: F",
speed: "km/h",
distance: { 0: "m", 1: "km" },
temperature: C",
ampm: { 0: "am", 1: "pm" },
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%A, %B %d, %Y", "1": "%Y-%m-%d" }, // Sunday, March 1, 2020 // 2012-12-20

View File

@ -1,3 +1,4 @@
0.01: New App!
0.02: Watch vibrates with every beat
0.03: Uses mean of three time intervalls to calculate bmp
0.04: App shows instructions, Widgets remain visible, color changed

View File

@ -10,7 +10,7 @@ function changecolor() {
const maxColors = 2;
const colors = {
0: { value: 0xFFFF, name: "White" },
1: { value: 0x000F, name: "Navy" },
// 1: { value: 0x000F, name: "Navy" },
// 2: { value: 0x03E0, name: "DarkGreen" },
// 3: { value: 0x03EF, name: "DarkCyan" },
// 4: { value: 0x7800, name: "Maroon" },
@ -21,7 +21,7 @@ function changecolor() {
// 9: { value: 0x001F, name: "Blue" },
// 10: { value: 0x07E0, name: "Green" },
// 11: { value: 0x07FF, name: "Cyan" },
// 12: { value: 0xF800, name: "Red" },
1: { value: 0xF800, name: "Red" },
// 13: { value: 0xF81F, name: "Magenta" },
// 14: { value: 0xFFE0, name: "Yellow" },
// 15: { value: 0xFFFF, name: "White" },
@ -40,11 +40,11 @@ function changecolor() {
}
function updateScreen() {
g.clear();
g.clearRect(0, 50, 250, 150);
changecolor();
Bangle.buzz(50, 0.75);
g.setFont("Vector",48);
g.drawString(Math.floor(bpm)+"bpm", -1, 70);
g.drawString(Math.floor(bpm)+"bpm", 5, 60);
}
Bangle.on('touch', function(button) {
@ -66,10 +66,8 @@ Bangle.on('touch', function(button) {
tStart = Date.now();
clearInterval(time_diff);
g.clear();
g.setFont("Vector",48);
bpm = (60 * 1000/(time_diff));
g.drawString(Math.floor(bpm)+"bpm", -1, 70);
updateScreen();
clearInterval(interval);
interval = setInterval(updateScreen, 60000 / bpm);
return bpm;
@ -91,3 +89,9 @@ setWatch(() => {
}, BTN3, {repeat:true});
interval = setInterval(updateScreen, 60000 / bpm);
g.clear();
g.drawString('Touch the screen to set tempo.\nUse BTN1 to increase, and\nBTN3 to decrease bpm value by 1.', 15, 150);
Bangle.loadWidgets();
Bangle.drawWidgets();

2
apps/qrcode/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Add posibillity to generate Wifi code.

View File

@ -3,8 +3,28 @@
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<p>Enter a URL: <input type="text" id="url" class="form-input" value="http://espruino.com"></p>
<input type="radio" id="useURL" name="mode" checked/>
<label for="useURL">Use URL:</label>
<input type="text" id="url" class="form-input" value="http://espruino.com">
<hr>
<input type="radio" id="useWIFI" name="mode"/>
<label for="useWIFI">Use Wifi Credentials:</label>
<input type="text" id="ssid" class="form-input" value="">
<p>Wifi password: <input type="password" id="password" class="form-input" value=""></p>
<div class="form-group">
<label for="encryption" class="control-label">Encryption</label>
<div class="input-group">
<select name="encryption" id="encryption" class="form-control">
<option value="WPA">WPA/WPA2</option>
<option value="WEP">WEP</option>
<option value="nopass">None</option>
</select>
</div>
</div>
<div>
<input type="checkbox" id="hidden" name="hidden"/>
<label for="hidden">Wifi is hidden</label>
</div>
<p>Try your QR Code: <div id="qrcode"></div></p>
<p>Click <button id="upload" class="btn btn-primary">Upload</button></p>
@ -14,31 +34,72 @@
<script src="../../lib/imageconverter.js"></script>
<script>
var qrcode = new QRCode("qrcode", {
text: document.getElementById("url").value,
width: 200,
height: 200,
colorDark : "#000000",
colorLight : "#ffffff",
});
document.getElementById("url").addEventListener("change", function() {
//https://github.com/evgeni/qifi/blob/gh-pages/index.html#L168
function escapeString (string) {
var to_escape = ['\\', ';', ',', ':', '"'];
var hex_only = /^[0-9a-f]+$/i;
var output = "";
for (var i=0; i<string.length; i++) {
if(string[i].includes(to_escape)) {
output += '\\'+string[i];
}
else {
output += string[i];
}
}
return output;
}
function generateWifiString(ssid, password, hidden,encryption){
//https://github.com/evgeni/qifi/blob/gh-pages/index.html#L198
var qrstring = 'WIFI:S:'+escapeString(ssid)+';T:'+encryption+';P:'+escapeString(password)+';';
if (hidden) {
qrstring += 'H:true';
}
return qrstring;
}
function refreshQRCode(){
qrcode.clear(); // clear the code.
qrcode.makeCode(document.getElementById("url").value); // make another code.
if(document.getElementById("useWIFI").checked){
const ssid = document.getElementById("ssid").value;
const password = document.getElementById("password").value;
const encryption = document.getElementById("encryption").value;
const hidden = document.getElementById("hidden").checked;
const wifiString = generateWifiString(ssid, password, hidden, encryption);
qrcode.makeCode(wifiString);
}else{
qrcode.makeCode(document.getElementById("url").value);
}
}
var qrcode = new QRCode("qrcode", {
text: document.getElementById("url").value,
width: 200,
height: 200,
colorDark : "#000000",
colorLight : "#ffffff",
});
document.getElementById("url").addEventListener("change", refreshQRCode);
document.getElementById("ssid").addEventListener("change",refreshQRCode);
document.getElementById("password").addEventListener("change",refreshQRCode);
document.getElementById("encryption").addEventListener("change",refreshQRCode);
document.getElementById("hidden").addEventListener("change",refreshQRCode);
document.getElementById("useURL").addEventListener("change",refreshQRCode);
document.getElementById("useWIFI").addEventListener("change",refreshQRCode);
document.getElementById("upload").addEventListener("click", function() {
var url = document.getElementById("url").value;
var content = document.getElementById("url").value;
if(document.getElementById("useWIFI").checked){
content = document.getElementById("ssid").value
}
var img = imageconverter.canvastoString(document.getElementsByTagName("canvas")[0],{mode:"1bit",output:"string",compression:true});
var app = `var img = ${img};
var url = ${JSON.stringify(url)};
var content = ${JSON.stringify(content)};
g.setColor(1,1,1);
g.fillRect(0,0,239,239);
g.drawImage(img,20,20);
g.setFontAlign(0,0);
g.setFont("6x8");
g.setColor(0,0,0);
g.drawString(url,120,230);
g.drawString(content,120,230);
g.setColor(1,1,1);
`;
sendCustomizedApp({

View File

@ -218,6 +218,12 @@ function refreshLibrary() {
if (versionInfo) versionInfo = " <small>("+versionInfo+")</small>";
var readme = `<a class="c-hand" onclick="showReadme('${app.id}')">Read more...</a>`;
var favourite = favourites.find(e => e == app.id);
var username = "espruino";
var githubMatch = window.location.href.match(/\/(\w+)\.github\.io/);
if(githubMatch) username = githubMatch[1];
var url = `https://github.com/${username}/BangleApps/tree/master/apps/${app.id}`;
return `<div class="tile column col-6 col-sm-12 col-xs-12">
<div class="tile-icon">
<figure class="avatar"><img src="apps/${app.icon?`${app.id}/${app.icon}`:"unknown.png"}" alt="${escapeHtml(app.name)}"></figure><br/>
@ -225,7 +231,7 @@ function refreshLibrary() {
<div class="tile-content">
<p class="tile-title text-bold">${escapeHtml(app.name)} ${versionInfo}</p>
<p class="tile-subtitle">${escapeHtml(app.description)}${app.readme?`<br/>${readme}`:""}</p>
<a href="https://github.com/espruino/BangleApps/tree/master/apps/${app.id}" target="_blank" class="link-github"><img src="img/github-icon-sml.png" alt="See the code on GitHub"/></a>
<a href="${url}" target="_blank" class="link-github"><img src="img/github-icon-sml.png" alt="See the code on GitHub"/></a>
</div>
<div class="tile-action">
<button class="btn btn-link btn-action btn-lg ${!app.custom?"text-error":"d-hide"}" appid="${app.id}" title="Favorite"><i class="icon"></i>${favourite?"&#x2665;":"&#x2661;"}</button>