msdeibel 2020-04-15 17:12:11 +02:00
commit fdf6ee24c1
32 changed files with 324 additions and 121 deletions

View File

@ -9,3 +9,4 @@ Changed for individual apps are listed in `apps/appname/ChangeLog`
* Fix issue removing an app that was just installed (fix #253)
* Add `Favourite` functionality
* Version number now clickable even when you're at the latest version (fix #291)
* Rewrite 'getInstalledApps' to minimize RAM usage

View File

@ -78,7 +78,7 @@
{ "id": "welcome",
"name": "Welcome",
"icon": "app.png",
"version":"0.06",
"version":"0.07",
"description": "Appears at first boot and explains how to use Bangle.js",
"tags": "start,welcome",
"allow_emulator":true,
@ -86,13 +86,14 @@
{"name":"welcome.boot.js","url":"boot.js"},
{"name":"welcome.app.js","url":"app.js"},
{"name":"welcome.settings.js","url":"settings.js"},
{"name":"welcome.settings.json","url":"settings-default.json","evaluate":true},
{"name":"welcome.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "gbridge",
"name": "Gadgetbridge",
"icon": "app.png",
"version":"0.08",
"version":"0.09",
"description": "The default notification handler for Gadgetbridge notifications from Android",
"tags": "tool,system,android,widget",
"type":"widget",
@ -119,7 +120,7 @@
{ "id": "setting",
"name": "Settings",
"icon": "settings.png",
"version":"0.14",
"version":"0.15",
"description": "A menu for setting up Bangle.js",
"tags": "tool,system",
"storage": [
@ -279,7 +280,7 @@
{ "id": "gpsrec",
"name": "GPS Recorder",
"icon": "app.png",
"version":"0.06",
"version":"0.07",
"interface": "interface.html",
"description": "Application that allows you to record a GPS track. Can run in background",
"tags": "tool,outdoors,gps,widget",
@ -329,7 +330,7 @@
{ "id": "widbat",
"name": "Battery Level Widget",
"icon": "widget.png",
"version":"0.04",
"version":"0.05",
"description": "Show the current battery level and charging status in the top right of the clock",
"tags": "widget,battery",
"type":"widget",
@ -341,7 +342,7 @@
"name": "Battery Level Widget (with percentage)",
"shortName": "Battery Widget",
"icon": "widget.png",
"version":"0.08",
"version":"0.09",
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
"tags": "widget,battery",
"type":"widget",
@ -354,7 +355,7 @@
{ "id": "widbt",
"name": "Bluetooth Widget",
"icon": "widget.png",
"version":"0.03",
"version":"0.04",
"description": "Show the current Bluetooth connection status in the top right of the clock",
"tags": "widget,bluetooth",
"type":"widget",
@ -362,6 +363,18 @@
{"name":"widbt.wid.js","url":"widget.js"}
]
},
{ "id": "widram",
"name": "RAM Widget",
"shortName":"RAM Widget",
"icon": "widget.png",
"version":"0.01",
"description": "Display your Bangle's available RAM percentage in a widget",
"tags": "widget",
"type": "widget",
"storage": [
{"name":"widram.wid.js","url":"widget.js"}
]
},
{ "id": "hrm",
"name": "Heart Rate Monitor",
"icon": "heartrate.png",
@ -504,13 +517,14 @@
"id": "ncstart",
"name": "NCEU Startup",
"icon": "start.png",
"version":"0.03",
"version":"0.04",
"description": "NodeConfEU 2019 'First Start' Sequence",
"tags": "start,welcome",
"storage": [
{"name":"ncstart.app.js","url":"start.js"},
{"name":"ncstart.boot.js","url":"boot.js"},
{"name":"ncstart.settings.js","url":"settings.js"},
{"name":"ncstart.settings.json","url":"settings-default.json","evaluate":true},
{"name":"ncstart.img","url":"start-icon.js","evaluate":true},
{"name":"nc-bangle.img","url":"start-bangle.js","evaluate":true},
{"name":"nc-nceu.img","url":"start-nceu.js","evaluate":true},
@ -775,7 +789,7 @@
{ "id": "widclk",
"name": "Digital clock widget",
"icon": "widget.png",
"version":"0.03",
"version":"0.04",
"description": "A simple digital clock widget",
"tags": "widget,clock",
"type":"widget",
@ -1219,8 +1233,8 @@
"name": "Snake",
"shortName":"Snake",
"icon": "snake.png",
"version":"0.01",
"description": "The classic snake game. Eat apples and don't bite your tail:",
"version":"0.02",
"description": "The classic snake game. Eat apples and don't bite your tail.",
"tags": "game,fun",
"readme": "README.md",
"storage": [

View File

@ -7,3 +7,4 @@
0.06: Gadgetbridge App 'Connected' state is no longer toggleable
0.07: Move configuration to settings menu
0.08: Don't turn on LCD at start of every song
0.09: Update Bluetooth connection state automatically

View File

@ -189,8 +189,8 @@
g.flip(); // turns screen on
}
NRF.on("connected", changedConnectionState);
NRF.on("disconnected", changedConnectionState);
NRF.on("connect", changedConnectionState);
NRF.on("disconnect", changedConnectionState);
WIDGETS["gbridgew"] = { area: "tl", width: 24, draw: draw };

View File

@ -4,3 +4,4 @@
0.04: Properly Fix GPS time display in gpsrec app
0.05: Tweaks for variable size widget system
0.06: Ensure widget update itself (fix #118) and change to using icons
0.07: Added @jeffmer's awesome track viewer

View File

@ -70,27 +70,65 @@ function viewTracks() {
return E.showMenu(menu);
}
function getTrackInfo(fn) {
var filename = getFN(fn);
var minLat = 90;
var maxLat = -90;
var minLong = 180;
var maxLong = -180;
var starttime, duration=0;
var f = require("Storage").open(filename,"r");
if (f===undefined) return;
var l = f.readLine(f);
var nl = 0, c, n;
if (l!==undefined) {
c = l.split(",");
starttime = parseInt(c[0]);
}
// pushed this loop together to try and bump loading speed a little
while(l!==undefined) {
++nl;c=l.split(",");
n = parseFloat(c[1]);if(n>maxLat)maxLat=n;if(n<minLat)minLat=n;
n = parseFloat(c[2]);if(n>maxLong)maxLong=n;if(n<minLong)minLong=n;
l = f.readLine(f);
}
if (c) duration = parseInt(c[0]) - starttime;
var lfactor = Math.cos(minLat*Math.PI/180);
var ylen = (maxLat-minLat);
var xlen = (maxLong-minLong)* lfactor;
var scale = xlen>ylen ? 200/xlen : 200/ylen;
return {
fn : fn,
filename : filename,
time : new Date(starttime),
records : nl,
minLat : minLat, maxLat : maxLat,
minLong : minLong, maxLong : maxLong,
lfactor : lfactor,
scale : scale,
duration : Math.round(duration/1000)
};
}
function asTime(v){
var mins = Math.floor(v/60);
var secs = v-mins*60;
return ""+mins.toString()+"m "+secs.toString()+"s";
}
function viewTrack(n) {
E.showMessage("Loading...","GPS Track "+n);
var info = getTrackInfo(n);
const menu = {
'': { 'title': 'GPS Track '+n }
};
var trackCount = 0;
var trackTime;
var f = require("Storage").open(getFN(n),"r");
var l = f.readLine();
if (l!==undefined) {
var c = l.split(",");
trackTime = new Date(parseInt(c[0]));
}
while (l!==undefined) {
trackCount++;
// TODO: min/max/length of track?
l = f.readLine();
}
if (trackTime)
menu[" "+trackTime.toISOString().substr(0,16).replace("T"," ")] = function(){};
menu[trackCount+" records"] = function(){};
// TODO: option to draw it? Just scan through, project using min/max
if (info.time)
menu[info.time.toISOString().substr(0,16).replace("T"," ")] = function(){};
menu["Duration"] = { value : asTime(info.duration)};
menu["Records"] = { value : ""+info.records };
menu['Plot'] = function() {
plotTrack(info);
};
menu['Erase'] = function() {
E.showPrompt("Delete Track?").then(function(v) {
if (v) {
@ -107,4 +145,80 @@ function viewTrack(n) {
return E.showMenu(menu);
}
function plotTrack(info) {
function xcoord(long){
return 30 + Math.round((long-info.minLong)*info.lfactor*info.scale);
}
function ycoord(lat){
return 210 - Math.round((lat - info.minLat)*info.scale);
}
function radians(a) {
return a*Math.PI/180;
}
function distance(lat1,long1,lat2,long2){
var x = radians(long1-long2) * Math.cos(radians((lat1+lat2)/2));
var y = radians(lat2-lat1);
return Math.sqrt(x*x + y*y) * 6371000;
}
E.showMenu(); // remove menu
g.setColor(1,0.5,0.5);
g.setFont("Vector",16);
g.fillRect(9,80,11,120);
g.fillPoly([9,60,19,80,0,80]);
g.setColor(1,1,1);
g.drawString("N",2,40);
g.drawString("Track"+info.fn.toString()+" - Loading",10,220);
g.setColor(0,0,0);
g.fillRect(0,220,239,239);
g.setColor(1,1,1);
g.drawString(asTime(info.duration),10,220);
var f = require("Storage").open(info.filename,"r");
if (f===undefined) return;
var l = f.readLine(f);
var ox=0;
var oy=0;
var olat,olong,dist=0;
var first = true;
var i=0;
while(l!==undefined) {
var c = l.split(",");
var lat = parseFloat(c[1]);
var long = parseFloat(c[2]);
var x = xcoord(long);
var y = ycoord(lat);
if (first) {
g.moveTo(x,y);
g.setColor(0,1,0);
g.fillCircle(x,y,5);
g.setColor(1,1,1);
first = false;
} else if (x!=ox || y!=oy) {
g.lineTo(x,y);
}
if (!first) {
var d = distance(olat,olong,lat,long);
if (!isNaN(d)) dist+=d;
}
olat = lat;
olong = long;
ox = x;
oy = y;
l = f.readLine(f);
}
g.setColor(1,0,0);
g.fillCircle(ox,oy,5);
g.setColor(1,1,1);
g.drawString(require("locale").distance(dist),120,220);
g.setFont("6x8",2);
g.setFontAlign(0,0,3);
g.drawString("Back",230,200);
setWatch(function() {
viewTrack(info.fn);
}, BTN3);
}
showMainMenu();

View File

@ -2,3 +2,6 @@
Renamed as nodeconf-specific
0.03: Move configuration into App/widget settings
Move loader into welcome.boot.js
0.04: Run again when updated
Don't run again when settings app is updated (or absent)
Add "Run Now" option to settings

View File

@ -1,9 +1,11 @@
(function() {
let s = require('Storage').readJSON('setting.json', 1) || {}
let s = require('Storage').readJSON('ncstart.settings.json', 1)
|| require('Storage').readJSON('setting.json', 1)
|| {welcomed: true} // do NOT run if global settings are also absent
if (!s.welcomed && require('Storage').read('ncstart.app.js')) {
setTimeout(() => {
s.welcomed = true
require('Storage').write('setting.json', s)
require('Storage').write('ncstart.settings.json', s)
load('ncstart.app.js')
})
}

View File

@ -0,0 +1,3 @@
{
"welcomed": false
}

View File

@ -1,16 +1,15 @@
// The welcome app is special, and gets to use global settings
(function(back) {
let settings = require('Storage').readJSON('setting.json', 1) || {}
let settings = require('Storage').readJSON('ncstart.settings.json', 1)
|| require('Storage').readJSON('setting.json', 1) || {}
E.showMenu({
'': { 'title': 'NCEU Startup' },
'Run again': {
'Run on Next Boot': {
value: !settings.welcomed,
format: v => v ? 'Yes' : 'No',
onchange: v => {
settings.welcomed = v ? undefined : true
require('Storage').write('setting.json', settings)
},
format: v => v ? 'OK' : 'No',
onchange: v => require('Storage').write('ncstart.settings.json', {welcomed: !v}),
},
'Run Now': () => load('ncstart.app.js'),
'< Back': back,
})
})

View File

@ -16,3 +16,4 @@
Make capitalization more consistent
Move LCD Brightness menu into more general LCD menu
0.14: Reduce memory usage when running app settings page
0.15: Reduce memory usage when running default clock chooser (#294)

View File

@ -296,10 +296,10 @@ function makeConnectable() {
});
}
function showClockMenu() {
var clockApps = require("Storage").list(/\.info$/).map(app => {
try { return require("Storage").readJSON(app); }
catch (e) { }
}).filter(app => app.type == "clock").sort((a, b) => a.sortorder - b.sortorder);
var clockApps = require("Storage").list(/\.info$/)
.map(app => {var a=storage.readJSON(app, 1);return (a&&a.type == "clock")?a:undefined})
.filter(app => app) // filter out any undefined apps
.sort((a, b) => a.sortorder - b.sortorder);
const clockMenu = {
'': {
'title': 'Select Clock',

View File

@ -1 +1,2 @@
0.01: New App!
0.01: New App!
0.02: Performance and graphic improvements, game pause, beep and buzz

View File

@ -1,6 +1,6 @@
# Snake
![Screenshot](https://i.ibb.co/XzWrvPL/screenshot.png)
![Screenshot](https://i.imgur.com/bXQjxhB.png)
The legentary classic game is now available on Bangle.js!
Eat apples and don't bite your tail.
@ -11,3 +11,4 @@ Eat apples and don't bite your tail.
- DOWN: BTN3
- LEFT: BTN4
- RIGHT: BTN5
- PAUSE: BTN2

View File

@ -1,37 +1,61 @@
Bangle.setLCDMode("120x120");
const H = g.getWidth();
const W = g.getHeight();
let running = true;
let score = 0;
let d;
// game world
const gridSize = 40;
const gridSize = 20;
const tileSize = 6;
let nextX = 0;
let nextY = 0;
// snake
const defaultTailSize = 3;
let tailSize = defaultTailSize;
const snakeTrail = [];
let snakeX = 10;
let snakeY = 10;
const snake = { x: 10, y: 10 };
const apple = { x: Math.floor(Math.random() * gridSize), y: Math.floor(Math.random() * gridSize) };
// apple
let appleX = Math.floor(Math.random() * gridSize);
let appleY = Math.floor(Math.random() * gridSize);
function drawBackground(){
g.setColor("#000000");
g.fillRect(0, 0, H, W);
}
function drawApple(){
g.setColor("#FF0000");
g.fillCircle((apple.x * tileSize) + tileSize/2, (apple.y * tileSize) + tileSize/2, tileSize/2);
}
function drawSnake(){
g.setColor("#008000");
for (let i = 0; i < snakeTrail.length; i++) {
g.fillRect(snakeTrail[i].x * tileSize, snakeTrail[i].y * tileSize, snakeTrail[i].x * tileSize + tileSize, snakeTrail[i].y * tileSize + tileSize);
//snake bites it's tail
if (snakeTrail[i].x === snake.x && snakeTrail[i].y === snake.y && tailSize > defaultTailSize) {
Bangle.buzz(1000);
gameOver();
}
}
}
function drawScore(){
g.setColor("#FFFFFF");
g.setFont("6x8");
g.setFontAlign(0, 0);
g.drawString("Score:" + score, W / 2, 10);
}
function gameStart() {
running = true;
score = 0;
}
function gameStop() {
function gameOver() {
g.clear();
g.setColor("#FFFFFF");
g.setFont("6x8", 2);
g.drawString("GAME OVER!", W / 2, H / 2 - 20);
g.drawString("Tap to Restart", W / 2, H / 2 + 20);
g.setFont("6x8");
g.drawString("GAME OVER!", W / 2, H / 2 - 10);
g.drawString("Tap to Restart", W / 2, H / 2 + 10);
running = false;
tailSize = defaultTailSize;
}
@ -41,66 +65,50 @@ function draw() {
return;
}
g.clear();
// move snake in next pos
snakeX += nextX;
snakeY += nextY;
snake.x += nextX;
snake.y += nextY;
// snake over game world?
if (snakeX < 0) {
snakeX = gridSize - 1;
// snake over game world
if (snake.x < 0) {
snake.x = gridSize - 1;
}
if (snake.x > gridSize - 1) {
snake.x = 0;
}
if (snakeX > gridSize - 1) {
snakeX = 0;
if (snake.y < 0) {
snake.y = gridSize - 1;
}
if (snake.y > gridSize - 1) {
snake.y = 0;
}
if (snakeY < 0) {
snakeY = gridSize - 1;
}
if (snakeY > gridSize - 1) {
snakeY = 0;
}
//snake bite apple?
if (snakeX === appleX && snakeY === appleY) {
//snake bite apple
if (snake.x === apple.x && snake.y === apple.y) {
Bangle.beep(20);
tailSize++;
score++;
appleX = Math.floor(Math.random() * gridSize);
appleY = Math.floor(Math.random() * gridSize);
apple.x = Math.floor(Math.random() * gridSize);
apple.y = Math.floor(Math.random() * gridSize);
drawApple();
}
//paint background
g.setColor("#000000");
g.fillRect(0, 0, H, W);
// paint snake
g.setColor("#008000");
for (let i = 0; i < snakeTrail.length; i++) {
g.fillRect(snakeTrail[i].x * tileSize, snakeTrail[i].y * tileSize, snakeTrail[i].x * tileSize + tileSize, snakeTrail[i].y * tileSize + tileSize);
//snake bites it's tail?
if (snakeTrail[i].x === snakeX && snakeTrail[i].y === snakeY && tailSize > defaultTailSize) {
gameStop();
}
}
// paint apple
g.setColor("#FF0000");
g.fillRect(appleX * tileSize, appleY * tileSize, appleX * tileSize + tileSize, appleY * tileSize + tileSize);
// paint score
g.setColor("#FFFFFF");
g.setFont("6x8");
g.setFontAlign(0, 0);
g.drawString("Score:" + score, W / 2, 10);
drawBackground();
drawApple();
drawSnake();
drawScore();
//set snake trail
snakeTrail.push({ x: snakeX, y: snakeY });
snakeTrail.push({ x: snake.x, y: snake.y });
while (snakeTrail.length > tailSize) {
snakeTrail.shift();
}
g.flip();
}
// input
@ -132,6 +140,9 @@ setWatch(() => {// Right
d = 'r';
}
}, BTN5, { repeat: true });
setWatch(() => {// Pause
running = !running;
}, BTN2, { repeat: true });
Bangle.on('touch', button => {
if (!running) {
@ -140,5 +151,5 @@ Bangle.on('touch', button => {
});
// render X times per second
var x = 5;
const x = 5;
setInterval(draw, 1000 / x);

View File

@ -4,3 +4,6 @@
0.04: Fix regression after tweaks to Storage.readJSON
0.05: Move configuration into App/widget settings
0.06: Move loader into welcome.boot.js
0.07: Run again when updated
Don't run again when settings app is updated (or absent)
Add "Run Now" option to settings

View File

@ -288,6 +288,13 @@ setWatch(()=>{
}, BTN2, {repeat:true,edge:"rising"});
setWatch(()=>move(-1), BTN1, {repeat:true});
(function migrateSettings(){
let global_settings = require('Storage').readJSON('setting.json', 1)
if (global_settings) {
delete global_settings.welcomed
require('Storage').write('setting.json', global_settings)
}
})()
Bangle.setLCDTimeout(0);
Bangle.setLCDPower(1);

View File

@ -1,9 +1,11 @@
(function() {
let s = require('Storage').readJSON('setting.json', 1) || {}
let s = require('Storage').readJSON('welcome.settings.json', 1)
|| require('Storage').readJSON('setting.json', 1)
|| {welcomed: true} // do NOT run if global settings are also absent
if (!s.welcomed && require('Storage').read('welcome.app.js')) {
setTimeout(() => {
s.welcomed = true
require('Storage').write('setting.json', s)
require('Storage').write('welcome.settings.json', {welcomed: "yes"})
load('welcome.app.js')
})
}

View File

@ -0,0 +1,3 @@
{
"welcomed": false
}

View File

@ -1,16 +1,15 @@
// The welcome app is special, and gets to use global settings
(function(back) {
let settings = require('Storage').readJSON('setting.json', 1) || {}
let settings = require('Storage').readJSON('welcome.settings.json', 1)
|| require('Storage').readJSON('setting.json', 1) || {}
E.showMenu({
'': { 'title': 'Welcome App' },
'Run again': {
'Run on Next Boot': {
value: !settings.welcomed,
format: v => v ? 'Yes' : 'No',
onchange: v => {
settings.welcomed = v ? undefined : true
require('Storage').write('setting.json', settings)
},
format: v => v ? 'OK' : 'No',
onchange: v => require('Storage').write('welcome.settings.json', {welcomed: !v}),
},
'Run Now': () => load('welcome.app.js'),
'< Back': back,
})
})

View File

@ -1,3 +1,4 @@
0.02: Now refresh battery monitor every minute if LCD on
0.03: Tweaks for variable size widget system
0.04: Ensure redrawing works with variable size widget system
0.05: Fix regression stopping correct widget updates

View File

@ -30,7 +30,7 @@ Bangle.on('lcdPower', function(on) {
WIDGETS["bat"].draw();
// refresh once a minute if LCD on
if (!batteryInterval)
batteryInterval = setInterval(draw, 60000);
batteryInterval = setInterval(()=>WIDGETS["bat"].draw(), 60000);
} else {
if (batteryInterval) {
clearInterval(batteryInterval);

View File

@ -5,3 +5,4 @@
0.06: Show battery percentage as text
0.07: Add settings: percentage/color/charger icon
0.08: Draw percentage as inverted on monochrome battery
0.09: Fix regression stopping correct widget updates

View File

@ -110,7 +110,7 @@ Bangle.on('lcdPower', function(on) {
WIDGETS["batpc"].draw();
// refresh once a minute if LCD on
if (!batteryInterval)
batteryInterval = setInterval(draw, 60000);
batteryInterval = setInterval(()=>WIDGETS["batpc"].draw(), 60000);
} else {
if (batteryInterval) {
clearInterval(batteryInterval);

View File

@ -1,2 +1,3 @@
0.02: Tweaks for variable size widget system
0.03: Ensure redrawing works with variable size widget system
0.04: Fix automatic update of Bluetooth connection status

View File

@ -13,7 +13,7 @@ function changed() {
WIDGETS["bluetooth"].draw();
g.flip();// turns screen on
}
NRF.on('connected',changed);
NRF.on('disconnected',changed);
NRF.on('connect',changed);
NRF.on('disconnect',changed);
WIDGETS["bluetooth"]={area:"tr",width:24,draw:draw};
})()

View File

@ -1,2 +1,3 @@
0.02: Now refresh battery monitor every minute if LCD on
0.03: Ensure redrawing works with variable size widget system
0.04: Fix regression stopping correct widget updates

View File

@ -14,7 +14,7 @@
}
}
function startTimers(){
intervalRef = setInterval(draw, 60*1000);
intervalRef = setInterval(()=>WIDGETS["wdclk"].draw(), 60*1000);
WIDGETS["wdclk"].draw();
}
Bangle.on('lcdPower', (on) => {
@ -23,5 +23,5 @@
});
WIDGETS["wdclk"]={area:"tr",width:width,draw:draw};
if (Bangle.isLCDOn) intervalRef = setInterval(draw, 60*1000);
if (Bangle.isLCDOn) intervalRef = setInterval(()=>WIDGETS["wdclk"].draw(), 60*1000);
})()

1
apps/widram/ChangeLog Normal file
View File

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

23
apps/widram/widget.js Normal file
View File

@ -0,0 +1,23 @@
(() => {
function draw() {
g.reset();
var m = process.memory();
var pc = Math.round(m.usage*100/m.total);
g.drawImage(atob("BwgBqgP////AVQ=="), this.x+(24-7)/2, this.y+4);
g.setColor(pc>70 ? "#ff0000" : (pc>50 ? "#ffff00" : "#ffffff"));
g.setFont("6x8").setFontAlign(0,0).drawString(pc+"%", this.x+12, this.y+20, true/*solid*/);
}
var ramInterval;
Bangle.on('lcdPower', function(on) {
if (on) {
WIDGETS["ram"].draw();
if (!ramInterval) ramInterval = setInterval(()=>WIDGETS["ram"].draw(), 10000);
} else {
if (ramInterval) {
clearInterval(ramInterval);
ramInterval = undefined;
}
}
});
WIDGETS["ram"]={area:"tl",width: 24,draw:draw};
})()

BIN
apps/widram/widget.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

View File

@ -75,12 +75,21 @@ getInstalledApps : () => {
Progress.hide({sticky:true});
return reject("");
}
Puck.eval('require("Storage").list(/\.info$/).map(f=>{var j=require("Storage").readJSON(f,1)||{};j.id=f.slice(0,-5);return j})', (appList,err) => {
Puck.write('\x10Bluetooth.print("[");require("Storage").list(/\.info$/).forEach(f=>{var j=require("Storage").readJSON(f,1)||{};j.id=f.slice(0,-5);Bluetooth.print(JSON.stringify(j)+",")});Bluetooth.println("0]")\n', (appList,err) => {
Progress.hide({sticky:true});
try {
appList = JSON.parse(appList);
// remove last element since we added a final '0'
// to make things easy on the Bangle.js side
appList = appList.slice(0,-1);
} catch (e) {
appList = null;
err = e.toString();
}
if (appList===null) return reject(err || "");
console.log("getInstalledApps", appList);
resolve(appList);
});
}, true /* callback on newline */);
});
});
},