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

pull/3051/head
Erik Andresen 2023-10-15 10:23:47 +02:00
commit 5b20af90da
463 changed files with 18457 additions and 1771 deletions

View File

@ -135,15 +135,17 @@
<h3>Utilities</h3>
<p>
<button class="btn tooltip" id="settime" data-tooltip="Set the Bangle's time to your Browser's time">Set Bangle.js Time</button>
<button class="btn tooltip" id="screenshot" data-tooltip="Create screenshot">Screenshot</button>
<button class="btn tooltip" id="downloadallapps" data-tooltip="Download all Bangle.js files to a ZIP file">Backup</button>
<button class="btn tooltip" id="uploadallapps" data-tooltip="Restore Bangle.js from a ZIP file">Restore</button>
</p><p>
<button class="btn tooltip" id="removeall" data-tooltip="Delete everything, leave it blank">Remove all Apps</button>
<button class="btn tooltip" id="reinstallall" data-tooltip="Re-install every app, leave all data">Reinstall apps</button>
<button class="btn tooltip" id="installdefault" data-tooltip="Delete everything, install default apps">Install default apps</button>
<button class="btn tooltip" id="installfavourite" data-tooltip="Delete everything, install your favourites">Install favourite apps</button>
<button class="btn tooltip" id="defaultbanglesettings" data-tooltip="Reset your Bangle's settings to the defaults">Reset Settings</button>
</p><p>
<button class="btn tooltip" id="newGithubIssue" data-tooltip="Create a new issue on GitHub">New issue on GitHub</button>
<button class="btn tooltip" id="downloadallapps" data-tooltip="Download all Bangle.js files to a ZIP file">Backup</button>
<button class="btn tooltip" id="uploadallapps" data-tooltip="Restore Bangle.js from a ZIP file">Restore</button>
<button class="btn tooltip" id="defaultbanglesettings" data-tooltip="Reset your Bangle's settings to the defaults">Reset Settings</button>
<button class="btn tooltip" id="webideremote" data-tooltip="Enable the Web IDE remote server">Web IDE Remote</button>
</p>
<h3>Settings</h3>
@ -172,6 +174,10 @@
<input type="checkbox" id="settings-minify">
<i class="form-icon"></i> Minify apps before upload (⚠DANGER⚠: Not recommended. Uploads smaller, faster apps but this <b>will</b> break many apps)
</label>
<label class="form-switch">
<input type="checkbox" id="settings-alwaysAllowUpdate">
<i class="form-icon"></i> Always allow to reinstall apps in place regardless of the version
</label>
<button class="btn" id="defaultsettings">Reset to default App Loader settings</button>
</details>
</div>

View File

@ -44,3 +44,6 @@
0.39: Dated event repeat option
0.40: Use substring of message when it's longer than fits the designated menu entry.
0.41: Fix a menu bug affecting alarms with empty messages.
0.42: Fix date not getting saved in event edit menu when tapping Confirm
0.43: New settings: Show confirm, Show Overflow, Show Type.
0.44: Add "delete timer after expiration" setting to events.

View File

@ -13,7 +13,7 @@ It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master
- `Repeat` &rarr; Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely)
- `New Timer` &rarr; Configure a new timer (triggered based on amount of time elapsed in hours/minutes/seconds)
- `New Event` &rarr; Configure a new event (triggered based on time and date)
- `Repeat` &rarr; Alarm can be be fired only once or repeated (every X number of _days_, _weeks_, _months_ or _years_)
- `Repeat` &rarr; Alarm can be fired only once or repeated (every X number of _days_, _weeks_, _months_ or _years_)
- `Advanced`
- `Scheduler settings` &rarr; Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details
- `Enable All` &rarr; Enable _all_ disabled alarms & timers

View File

@ -1,6 +1,11 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
const settings = Object.assign({
showConfirm : true,
showAutoSnooze : true,
showHidden : true
}, require('Storage').readJSON('alarm.json',1)||{});
// 0 = Sunday (default), 1 = Monday
const firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0;
const WORKDAYS = 62;
@ -51,12 +56,14 @@ function getLabel(e) {
}
function trimLabel(label, maxLength) {
if(settings.showOverflow) return label;
return (label.length > maxLength
? label.substring(0,maxLength-3) + "..."
: label.substring(0,maxLength));
}
function formatAlarmMessage(msg) {
function formatAlarmProperty(msg) {
if(settings.showOverflow) return msg;
if (msg == null) {
return msg;
} else if (msg.length > 7) {
@ -99,6 +106,9 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
var isNew = alarmIndex === undefined;
var alarm = require("sched").newDefaultAlarm();
if (withDate || selectedAlarm.date) {
alarm.del = require("sched").getSettings().defaultDeleteExpiredTimers;
}
alarm.dow = handleFirstDayOfWeek(alarm.dow);
if (selectedAlarm) {
@ -155,7 +165,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
},
/*LANG*/"Message": {
value: alarm.msg,
format: formatAlarmMessage,
format: formatAlarmProperty,
onchange: () => {
setTimeout(() => {
keyboard.input({text:alarm.msg}).then(result => {
@ -166,6 +176,19 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
}, 100);
}
},
/*LANG*/"Group": {
value: alarm.group,
format: formatAlarmProperty,
onchange: () => {
setTimeout(() => {
keyboard.input({text:alarm.group}).then(result => {
alarm.group = result;
prepareAlarmForSave(alarm, alarmIndex, time, date, true);
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
});
}, 100);
}
},
/*LANG*/"Enabled": {
value: alarm.on,
onchange: v => alarm.on = v
@ -173,6 +196,9 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
/*LANG*/"Repeat": {
value: decodeRepeat(alarm),
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, date || alarm.dow, (repeat, dow) => {
if (repeat) {
alarm.del = false; // do not auto delete a repeated alarm
}
alarm.rp = repeat;
alarm.dow = dow;
prepareAlarmForSave(alarm, alarmIndex, time, date, true);
@ -184,23 +210,32 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
value: alarm.as,
onchange: v => alarm.as = v
},
/*LANG*/"Delete After Expiration": {
value: alarm.del,
onchange: v => alarm.del = v
},
/*LANG*/"Hidden": {
value: alarm.hidden || false,
onchange: v => alarm.hidden = v
},
/*LANG*/"Cancel": () => showMainMenu(),
/*LANG*/"Confirm": () => {
prepareAlarmForSave(alarm, alarmIndex, time);
prepareAlarmForSave(alarm, alarmIndex, time, date);
saveAndReload();
showMainMenu();
}
};
if (!keyboard) delete menu[/*LANG*/"Message"];
if (!keyboard || !settings.showGroup) delete menu[/*LANG*/"Group"];
if (!settings.showConfirm) delete menu[/*LANG*/"Confirm"];
if (!settings.showAutoSnooze) delete menu[/*LANG*/"Auto Snooze"];
if (!settings.showHidden) delete menu[/*LANG*/"Hidden"];
if (!alarm.date) {
delete menu[/*LANG*/"Day"];
delete menu[/*LANG*/"Month"];
delete menu[/*LANG*/"Year"];
delete menu[/*LANG*/"Delete After Expiration"];
}
if (!isNew) {
@ -259,7 +294,6 @@ function decodeRepeat(alarm) {
}
function showEditRepeatMenu(repeat, day, dowChangeCallback) {
var originalRepeat = repeat;
var dow;
const menu = {
@ -292,26 +326,32 @@ function showEditRepeatMenu(repeat, day, dowChangeCallback) {
},
/*LANG*/"Custom": {
value: isCustom ? decodeRepeat({ rp: true, dow: dow }) : false,
onchange: () => setTimeout(showCustomDaysMenu, 10, dow, dowChangeCallback, originalRepeat, originalDow)
onchange: () => setTimeout(showCustomDaysMenu, 10, dow, dowChangeCallback, repeat, originalDow)
}
};
} else {
// var date = day; // eventually: detect day of date and configure a repeat e.g. 3rd Monday of Month
dow = EVERY_DAY;
repeat = repeat || {interval: "month", num: 1};
const repeatObj = repeat || {interval: "month", num: 1};
restOfMenu = {
/*LANG*/"Every": {
value: repeat.num,
value: repeatObj.num,
min: 1,
onchange: v => repeat.num = v
onchange: v => {
repeat = repeatObj;
repeat.num = v;
}
},
/*LANG*/"Interval": {
value: INTERVALS.indexOf(repeat.interval),
value: INTERVALS.indexOf(repeatObj.interval),
format: v => INTERVAL_LABELS[v],
min: 0,
max: INTERVALS.length - 1,
onchange: v => repeat.interval = INTERVALS[v]
onchange: v => {
repeat = repeatObj;
repeat.interval = INTERVALS[v];
}
}
};
}
@ -387,7 +427,7 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
},
/*LANG*/"Message": {
value: timer.msg,
format: formatAlarmMessage,
format: formatAlarmProperty,
onchange: () => {
setTimeout(() => {
keyboard.input({text:timer.msg}).then(result => {
@ -420,6 +460,8 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
};
if (!keyboard) delete menu[/*LANG*/"Message"];
if (!settings.showConfirm) delete menu[/*LANG*/"Confirm"];
if (!settings.showHidden) delete menu[/*LANG*/"Hidden"];
if (!isNew) {
menu[/*LANG*/"Delete"] = () => {
E.showPrompt(getLabel(timer) + "\n" + /*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {

View File

@ -2,7 +2,7 @@
"id": "alarm",
"name": "Alarms & Timers",
"shortName": "Alarms",
"version": "0.41",
"version": "0.44",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm",
@ -11,7 +11,8 @@
"dependencies": { "scheduler":"type", "alarm":"widget" },
"storage": [
{ "name": "alarm.app.js", "url": "app.js" },
{ "name": "alarm.img", "url": "app-icon.js", "evaluate": true }
{ "name": "alarm.img", "url": "app-icon.js", "evaluate": true },
{ "name": "alarm.settings.js", "url":"settings.js" }
],
"screenshots": [
{ "url": "screenshot-1.png" },
@ -25,5 +26,6 @@
{ "url": "screenshot-9.png" },
{ "url": "screenshot-10.png" },
{ "url": "screenshot-11.png" }
]
],
"data":[ {"name":"alarm.settings.json"} ]
}

51
apps/alarm/settings.js Normal file
View File

@ -0,0 +1,51 @@
(function(back) {
let settings = Object.assign({
showConfirm : true,
showAutoSnooze : true,
showHidden : true
}, require('Storage').readJSON('alarm.json',1)||{});
const save = () => require('Storage').write('alarm.json', settings);
const DATE_FORMATS = ['default', 'mmdd'];
const DATE_FORMATS_LABELS = [/*LANG*/'Default', /*LANG*/'MMDD'];
const appMenu = {
'': {title: 'alarm'}, '< Back': back,
/*LANG*/'Menu Date Format': {
value: DATE_FORMATS.indexOf(settings.menuDateFormat || 'default'),
format: v => DATE_FORMATS_LABELS[v],
min: 0,
max: DATE_FORMATS.length - 1,
onchange : v => {
if(v > 0) {
settings.menuDateFormat=DATE_FORMATS[v];
} else {
delete settings.menuDateFormat;
}
save();
}
},
/*LANG*/'Show Menu Auto Snooze': {
value : !!settings.showAutoSnooze,
onchange : v => { settings.showAutoSnooze=v; save();}
},
/*LANG*/'Show Menu Confirm': {
value : !!settings.showConfirm,
onchange : v => { settings.showConfirm=v; save();}
},
/*LANG*/'Show Menu Hidden': {
value : !!settings.showHidden,
onchange : v => { settings.showHidden=v; save();}
},
/*LANG*/'Show Menu Group': {
value : !!settings.showGroup,
onchange : v => { settings.showGroup=v; save();}
},
/*LANG*/'Show Text Overflow': {
value : !!settings.showOverflow,
onchange : v => { settings.showOverflow=v; save();}
},
};
E.showMenu(appMenu);
});

View File

@ -27,4 +27,8 @@
0.26: Change handling of GPS status to depend on GPS events instead of connection events
0.27: Issue newline before GB commands (solves issue with console.log and ignored commands)
0.28: Navigation messages no longer launch the Maps view unless they're new
0.29: Support for http request xpath return format
0.29: Support for http request xpath return format
0.30: Send firmware and hardware versions on connection
Allow alarm enable/disable
0.31: Implement API for activity fetching
0.32: Added support for loyalty cards from gadgetbridge

View File

@ -86,7 +86,7 @@
var a = require("sched").newDefaultAlarm();
a.id = "gb"+j;
a.appid = "gbalarms";
a.on = true;
a.on = event.d[j].on !== undefined ? event.d[j].on : true;
a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
a.last = last;
@ -193,10 +193,37 @@
Bangle.on('HRM',actHRMHandler);
actInterval = setInterval(function() {
var steps = Bangle.getStepCount();
gbSend({ t: "act", stp: steps-lastSteps, hrm: lastBPM });
gbSend({ t: "act", stp: steps-lastSteps, hrm: lastBPM, rt:1 });
lastSteps = steps;
}, event.int*1000);
},
// {t:"actfetch", ts:long}
"actfetch": function() {
gbSend({t: "actfetch", state: "start"});
var actCount = 0;
var actCb = function(r) {
// The health lib saves the samples at the start of the 10-minute block
// However, GB expects them at the end of the block, so let's offset them
// here to keep a consistent API in the health lib
var sampleTs = r.date.getTime() + 600000;
if (sampleTs >= event.ts) {
gbSend({
t: "act",
ts: sampleTs,
stp: r.steps,
hrm: r.bpm,
mov: r.movement
});
actCount++;
}
}
if (event.ts != 0) {
require("health").readAllRecordsSince(new Date(event.ts - 600000), actCb);
} else {
require("health").readFullDatabase(actCb);
}
gbSend({t: "actfetch", state: "end", count: actCount});
},
"nav": function() {
event.id="nav";
if (event.instr) {
@ -209,6 +236,11 @@
event.t="remove";
}
require("messages").pushMessage(event);
},
"cards" : function() {
// we receive all, just override what we have
if (Array.isArray(event.d))
require("Storage").writeJSON("android.cards.json", event.d);
}
};
var h = HANDLERS[event.t];
@ -253,6 +285,7 @@
Bangle.on("charging", sendBattery);
NRF.on("connect", () => setTimeout(function() {
sendBattery();
gbSend({t: "ver", fw: process.env.VERSION, hw: process.env.HWVERSION});
GB({t:"force_calendar_sync_start"}); // send a list of our calendar entries to start off the sync process
}, 2000));
NRF.on("disconnect", () => {
@ -264,10 +297,9 @@
require("messages").clearAll();
});
setInterval(sendBattery, 10*60*1000);
// Health tracking
Bangle.on('health', health=>{
if (actInterval===undefined) // if 'realtime' we do it differently
gbSend({ t: "act", stp: health.steps, hrm: health.bpm });
// Health tracking - if 'realtime' data is sent with 'rt:1', but let's still send our activity log every 10 mins
Bangle.on('health', h=>{
gbSend({ t: "act", stp: h.steps, hrm: h.bpm, mov: h.movement });
});
// Music control
Bangle.musicControl = cmd => {

View File

@ -2,7 +2,7 @@
"id": "android",
"name": "Android Integration",
"shortName": "Android",
"version": "0.29",
"version": "0.32",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge",
@ -15,6 +15,6 @@
{"name":"android.img","url":"app-icon.js","evaluate":true},
{"name":"android.boot.js","url":"boot.js"}
],
"data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}],
"data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}, {"name":"android.cards.json"}],
"sortorder": -8
}

View File

@ -31,7 +31,7 @@
<div class="form-group">
<label class="form-label">Select which GNSS system you want.</label>
<label class="form-radio">
<input type="radio" name="gnss_select" value="1" checked><i class="form-icon"></i> GPS
<input type="radio" name="gnss_select" value="1" checked><i class="form-icon"></i> GPS (fastest to get a fix)
</label>
<label class="form-radio">
<input type="radio" name="gnss_select" value="2"><i class="form-icon"></i> BDS

View File

@ -2,3 +2,5 @@
0.02: Store last GPS lock, can be used instead of waiting for new GPS on start
0.03: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
0.04: Compatibility with Bangle.js 2, get location from My Location
0.05: Enable widgets
0.06: Fix azimuth (bug #2651), only show degrees

View File

@ -11,7 +11,6 @@
const SunCalc = require("suncalc"); // from modules folder
const storage = require("Storage");
const BANGLEJS2 = process.env.HWVERSION == 2; // check for bangle 2
function drawMoon(phase, x, y) {
const moonImgFiles = [
@ -110,7 +109,7 @@ function drawPoints() {
}
function drawData(title, obj, startX, startY) {
g.clear();
g.clearRect(Bangle.appRect);
drawTitle(title);
let xPos, yPos;
@ -141,22 +140,21 @@ function drawData(title, obj, startX, startY) {
function drawMoonPositionPage(gps, title) {
const pos = SunCalc.getMoonPosition(new Date(), gps.lat, gps.lon);
const moonColor = g.theme.dark ? {r: 1, g: 1, b: 1} : {r: 0, g: 0, b: 0};
const azimuth = pos.azimuth + Math.PI; // 0 is south, we want 0 to be north
const pageData = {
Azimuth: pos.azimuth.toFixed(2),
Altitude: pos.altitude.toFixed(2),
Azimuth: parseInt(azimuth * 180 / Math.PI + 0.5) + '°',
Altitude: parseInt(pos.altitude * 180 / Math.PI + 0.5) + '°',
Distance: `${pos.distance.toFixed(0)} km`,
"Parallactic Ang": pos.parallacticAngle.toFixed(2),
"Parallactic Ang": parseInt(pos.parallacticAngle * 180 / Math.PI + 0.5) + '°',
};
const azimuthDegrees = parseInt(pos.azimuth * 180 / Math.PI);
const azimuthDegrees = parseInt(azimuth * 180 / Math.PI + 0.5);
drawData(title, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20);
drawPoints();
drawPoint(azimuthDegrees, 8, moonColor);
let m = setWatch(() => {
let m = moonIndexPageMenu(gps);
}, BANGLEJS2 ? BTN : BTN3, {repeat: false, edge: "falling"});
Bangle.setUI({mode: "custom", back: () => moonIndexPageMenu(gps)});
}
function drawMoonIlluminationPage(gps, title) {
@ -174,9 +172,7 @@ function drawMoonIlluminationPage(gps, title) {
drawData(title, pageData, null, 35);
drawMoon(phaseIdx, g.getWidth() / 2, g.getHeight() / 2);
let m = setWatch(() => {
let m = moonIndexPageMenu(gps);
}, BANGLEJS2 ? BTN : BTN3, {repease: false, edge: "falling"});
Bangle.setUI({mode: "custom", back: () => moonIndexPageMenu(gps)});
}
@ -194,17 +190,17 @@ function drawMoonTimesPage(gps, title) {
// Draw the moon rise position
const risePos = SunCalc.getMoonPosition(times.rise, gps.lat, gps.lon);
const riseAzimuthDegrees = parseInt(risePos.azimuth * 180 / Math.PI);
const riseAzimuth = risePos.azimuth + Math.PI; // 0 is south, we want 0 to be north
const riseAzimuthDegrees = parseInt(riseAzimuth * 180 / Math.PI);
drawPoint(riseAzimuthDegrees, 8, moonColor);
// Draw the moon set position
const setPos = SunCalc.getMoonPosition(times.set, gps.lat, gps.lon);
const setAzimuthDegrees = parseInt(setPos.azimuth * 180 / Math.PI);
const setAzimuth = setPos.azimuth + Math.PI; // 0 is south, we want 0 to be north
const setAzimuthDegrees = parseInt(setAzimuth * 180 / Math.PI);
drawPoint(setAzimuthDegrees, 8, moonColor);
let m = setWatch(() => {
let m = moonIndexPageMenu(gps);
}, BANGLEJS2 ? BTN : BTN3, {repease: false, edge: "falling"});
Bangle.setUI({mode: "custom", back: () => moonIndexPageMenu(gps)});
}
function drawSunShowPage(gps, key, date) {
@ -214,16 +210,15 @@ function drawSunShowPage(gps, key, date) {
const mins = ("0" + date.getMinutes()).substr(-2);
const secs = ("0" + date.getMinutes()).substr(-2);
const time = `${hrs}:${mins}:${secs}`;
const azimuth = pos.azimuth + Math.PI; // 0 is south, we want 0 to be north
const azimuth = Number(pos.azimuth.toFixed(2));
const azimuthDegrees = parseInt(pos.azimuth * 180 / Math.PI);
const altitude = Number(pos.altitude.toFixed(2));
const azimuthDegrees = parseInt(azimuth * 180 / Math.PI + 0.5) + '°';
const altitude = parseInt(pos.altitude * 180 / Math.PI + 0.5) + '°';
const pageData = {
Time: time,
Altitude: altitude,
Azimumth: azimuth,
Degrees: azimuthDegrees
Azimuth: azimuthDegrees,
};
drawData(key, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20 + 5);
@ -233,9 +228,7 @@ function drawSunShowPage(gps, key, date) {
// Draw the suns position
drawPoint(azimuthDegrees, 8, {r: 1, g: 1, b: 0});
m = setWatch(() => {
m = sunIndexPageMenu(gps);
}, BANGLEJS2 ? BTN : BTN3, {repeat: false, edge: "falling"});
Bangle.setUI({mode: "custom", back: () => sunIndexPageMenu(gps)});
return null;
}
@ -314,7 +307,9 @@ function getCenterStringX(str) {
function init() {
let location = require("Storage").readJSON("mylocation.json",1)||{"lat":51.5072,"lon":0.1276,"location":"London"};
Bangle.loadWidgets();
indexPageMenu(location);
Bangle.drawWidgets();
}
let m;

View File

@ -1,10 +1,10 @@
{
"id": "astrocalc",
"name": "Astrocalc",
"version": "0.04",
"version": "0.06",
"description": "Calculates interesting information on the sun like sunset and sunrise and moon cycles for the current day based on your location from MyLocation app",
"icon": "astrocalc.png",
"tags": "app,sun,moon,cycles,tool",
"tags": "app,sun,moon,cycles,tool,outdoors",
"supports": ["BANGLEJS", "BANGLEJS2"],
"allow_emulator": true,
"dependencies": {"mylocation":"app"},

View File

@ -1,2 +1,5 @@
0.02: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast
0.03: Bangle 2 support
0.04: Increase size of ship, asteroids and fonts for better readability
0.05: improve collision detect for larger ship v astroid
0.06: added, 7 point asteroid ploygon, made ship solid, rather than outline

View File

@ -18,6 +18,22 @@ if (process.env.HWVERSION==2) {
}
var W = g.getWidth();
var H = g.getHeight();
var SS = W/11; // ship back length
var SL = W/15; // ship side length
var AS = W/18; // asteroid radius
// radius of ship, assumed a circle inside equilateral traingle of side SS
// r = a / root 3 where a is length of equilateral triangle
var SR = SS / Math.sqrt(3);
var AST = [ // asteroid polygon as X/Y pairs
0 ,-1.5,
1 , 0,
0.5, 0,
0.5, 0.5,
0 , 1,
-1 , 0,
-1 , -1
];
g.clear().setFontAlign(0,-1);
function newAst(x,y) {
@ -25,7 +41,7 @@ function newAst(x,y) {
x:x,y:y,
vx:Math.random()-0.5,
vy:Math.random()-0.5,
rad:3+Math.random()*5
rad:3+Math.random()*AS
};
return a;
}
@ -41,8 +57,9 @@ var lastFrame;
function gameStop() {
running = false;
g.clear();
g.drawString("Game Over!",120,(H-6)/2);
g.setFont('Vector', W/7);
g.setFontAlign(0,0);
g.drawString("Game Over", W/2, H/2);
g.flip();
}
@ -104,12 +121,13 @@ function onFrame() {
}
g.clear();
g.drawString(score,W-20,0);
g.setFont('Vector', 16);
g.drawString(score,W-20,16);
var rs = Math.PI*0.8;
g.drawPoly([
ship.x+Math.cos(ship.r)*4, ship.y+Math.sin(ship.r)*4,
ship.x+Math.cos(ship.r+rs)*3, ship.y+Math.sin(ship.r+rs)*3,
ship.x+Math.cos(ship.r-rs)*3, ship.y+Math.sin(ship.r-rs)*3,
g.fillPoly([
ship.x+Math.cos(ship.r)*SS, ship.y+Math.sin(ship.r)*SS,
ship.x+Math.cos(ship.r+rs)*SL, ship.y+Math.sin(ship.r+rs)*SL,
ship.x+Math.cos(ship.r-rs)*SL, ship.y+Math.sin(ship.r-rs)*SL,
],true);
var na = [];
ammo.forEach(function(a) {
@ -137,7 +155,10 @@ function onFrame() {
ast.forEach(function(a) {
a.x += a.vx*d;
a.y += a.vy*d;
g.drawCircle(a.x, a.y, a.rad);
//g.drawCircle(a.x, a.y, a.rad);
// a 7 point asteroid with rough circle radius of scale 2
g.drawPoly(g.transformVertices(AST,{x:a.x,y:a.y,scale:a.rad,rotate:t}),true);
if (a.x<0) a.x+=W;
if (a.y<0) a.y+=H;
if (a.x>=W) a.x-=W;
@ -165,7 +186,7 @@ function onFrame() {
var dx = a.x-ship.x;
var dy = a.y-ship.y;
var d = Math.sqrt(dx*dx+dy*dy);
if (d < a.rad) crashed = true;
if (d < a.rad + SR) crashed = true;
});
ast=na;
if (!ast.length) {

View File

@ -1,10 +1,10 @@
{
"id": "astroid",
"name": "Asteroids!",
"version": "0.03",
"version": "0.06",
"description": "Retro asteroids game",
"icon": "asteroids.png",
"screenshots": [{"url":"screenshot_asteroids.png"}],
"screenshots": [{"url":"screenshot.png"}],
"tags": "game",
"supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true,

BIN
apps/astroid/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Don't fire if the app uses swipes already.
0.03: Only count defined handlers in the handler array.

View File

@ -26,7 +26,7 @@
if (Bangle["#on"+eventType] === undefined) {
return 0;
} else if (Bangle["#on"+eventType] instanceof Array) {
return Bangle["#on"+eventType].length;
return Bangle["#on"+eventType].filter(x=>x).length;
} else if (Bangle["#on"+eventType] !== undefined) {
return 1;
}

View File

@ -1,7 +1,7 @@
{ "id": "backswipe",
"name": "Back Swipe",
"shortName":"BackSwipe",
"version":"0.02",
"version":"0.03",
"description": "Service that allows you to use an app's back button using left to right swipe gesture",
"icon": "app.png",
"tags": "back,gesture,swipe",

1
apps/bblobface/ChangeLog Normal file
View File

@ -0,0 +1 @@
1.00: Initial release of Bangle Blobs Clock!

35
apps/bblobface/README.md Normal file
View File

@ -0,0 +1,35 @@
# Bangle Blobs Clock
What if every time you checked the time, you could play a turn of a turn-based puzzle game?
You check the time dozens, maybe hundreds of times per day, and Bangle Blobs Clock wants to add a splash of fun to each of these moments!
Bangle Blobs Clock is a fully featured watch face with a turn-based puzzle game right next to the clock.
![](screenshot1.png)
![](screenshot2.png)
## Clock Features
- Hour and minute
- Seconds (only while the screen is unlocked to save power)
- Month, day, and day of week
- Battery percentage. Blue while charging, red when low, green otherwise.
- Respects your 24-hour/12-hour time setting in Locale
- Press the pause button to access your Widgets
- Supports Fast Loading
## The Game
This is a turn-based puzzle game based on Puyo Puyo, an addictive puzzle game franchise by SEGA.
Blobs arrive in pairs that you can move, rotate, and place. When at least four Blobs of the same color touch, they pop, causing Blobs above them to fall.
If this causes another pop, it's called a chain! Build a massive chain reaction of popping Blobs!
- Drag left and right to move the pair
- Tap the left or right half of the screen to rotate the pair
- Swipe down to place the pair
## More Info
If you're confused about the functionality of the clock or want a better explanation of how to play the game, I wrote up a user manual here: https://docs.google.com/document/d/1watPzChawBu4iM0lXypreejs3wvf2_8C-x5V2MWJQBc/edit?usp=sharing
## Special Thanks
I'm Pasta Rhythm, computer scientist and aspiring game developer. I would like to say thank you to the people who inspired me while I was making this app:
- [nxdefiant, who made a Tetris game.](https://github.com/espruino/BangleApps/tree/master/apps/tetris) Bangle Blobs is my first Bangle app and my first time using JavaScript, so this was a daunting project. This Tetris game served as a great example that helped me get started.
- [gfwilliams for Anton Clock](https://github.com/espruino/BangleApps/tree/master/apps/antonclk) and [Targor for Kanagawa Clock.](https://github.com/espruino/BangleApps/tree/master/apps/kanagsec) These were good examples for how to make a watch face for the Bangle.js 2.
- Thanks to Gordon Williams and to everyone who contributes to Espruino and the Bangle.js 2 projects!
- SEGA, owners of the Puyo Puyo franchise that Bangle Blobs is based on. Please check out official Puyo Puyo games!
- Compile, the original creators of Puyo Puyo. The company went bankrupt long ago, but the people who worked for them continue to make games.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+HGm56+5BQ4JBAJItXAAoMMCJQAPJ5pfhJApPQL65HHKIbTU2nXAAu0I5xQNBo4tC2gAFGIxHIL5oNGEoItGGIgwDL6oMGFxgwFL6oVFFxwwEL7YuPGARfVBYwvUL6YLGL84THL84KHL7YHCL6AeBFx+0JggAGLx4wQFwa3DAIwvHNJQwMFwhgIEQ7ILGAYxHBAQWJADUeFAIAEjwtnjwAFGMglBFowxEGA/XgrgICJouMGA4aBAIgvMB4ouOGAouGMZgNGFx4wCPQ5hMN44vTK44wLNo5fUcRwuHL67iOHAxfhFxYJBBooeBFx8ecRY4KBowwOFxDgHM5BtHGBguZfhIkBGI4ICFyILFAIxBHAAoOGXIgLHBowBGFo0FAAoxHFxhfPAoQAJCIguNGxRtGABYpDQB72LFxwwEcCJfJFx4wCL7gvTADYv/F/4APYoQuOaoYwpFz4wOF0IwDGI4ICF0IxFAAgtFA="))

768
apps/bblobface/app.js Normal file
View File

@ -0,0 +1,768 @@
{
// ~~ Variables for clock ~~
let clockDrawTimeout;
let twelveHourTime = require('Storage').readJSON('setting.json', 1)['12hour'];
let updateSeconds = !Bangle.isLocked();
let batteryLevel = E.getBattery();
// ~~ Variables for game logic ~~
const NUM_COLORS = 6;
const NUISANCE_COLOR = 7;
let grid = [
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0])
];
let hiddenRow = new Uint8Array([0, 0, 0, 0, 0, 0]);
let nextQueue = [{pivot: 1, leaf: 1}, {pivot: 1, leaf: 1}];
let currentPair = {pivot: 0, leaf: 0};
let dropCoordinates = {pivotX: 2, pivotY: 11, leafX: 2, leafY: 10};
let pairX = 2;
let pairOrientation = 0; //0 is up, 1 is right, 2 is down, 3 is left
let slotsToCheck = [];
let selectedColors;
let lastChain = 0;
let gameLost = false;
let gamePaused = false;
let midChain = false;
/*
Sets up a new game.
Must be called once before the first round.
*/
let restartGame = function() {
grid = [
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0])
];
hiddenRow = new Uint8Array([0, 0, 0, 0, 0, 0]);
currentPair = {pivot: 0, leaf: 0};
pairX = 2;
pairOrientation = 0; //0 is up, 1 is right, 2 is down, 3 is left
slotsToCheck = [];
gameLost = false;
lastChain = 0;
//Set up random colors
selectedColors = new Uint8Array([1, 2, 3, 4, 5, 6]);
for (let i = NUM_COLORS - 1; i > 0; i--) {
let swap = selectedColors[i];
let swapIndex = Math.floor(Math.random() * (i + 1));
selectedColors[i] = selectedColors[swapIndex];
selectedColors[swapIndex] = swap;
}
//Create the first two pairs (Always in the first three colors)
nextQueue[0].pivot = selectedColors[Math.floor(Math.random() * 3)];
nextQueue[0].leaf = selectedColors[Math.floor(Math.random() * 3)];
nextQueue[1].pivot = selectedColors[Math.floor(Math.random() * 3)];
nextQueue[1].leaf = selectedColors[Math.floor(Math.random() * 3)];
};
/*
Readies the next pair and generates a new one for the queue.
*/
let newPair = function() {
currentPair.pivot = nextQueue[0].pivot;
currentPair.leaf = nextQueue[0].leaf;
nextQueue[0].pivot = nextQueue[1].pivot;
nextQueue[0].leaf = nextQueue[1].leaf;
nextQueue[1].pivot = selectedColors[Math.floor(Math.random() * 4)];
nextQueue[1].leaf = selectedColors[Math.floor(Math.random() * 4)];
pairX = 2;
pairOrientation = 0;
calcDropCoordinates();
};
/*
Calculates the coordinates at which the current pair will be placed when quick dropped.
*/
let calcDropCoordinates = function() {
dropCoordinates.pivotX = pairX;
//Find Y coordinate of pivot
dropCoordinates.pivotY = -2;
for (let i = 11; i >= 0; i--) {
if (grid[i][pairX] == 0) {
dropCoordinates.pivotY = i;
break;
}
}
if (dropCoordinates.pivotY == -2 && hiddenRow[pairX] == 0)
dropCoordinates.pivotY = -1;
//Find coordinates of leaf
if (pairOrientation == 1) {
dropCoordinates.leafX = pairX + 1;
dropCoordinates.leafY = -2;
for (let i = 11; i >= 0; i--) {
if (grid[i][pairX + 1] == 0) {
dropCoordinates.leafY = i;
break;
}
}
if (dropCoordinates.leafY == -2 && hiddenRow[pairX + 1] == 0)
dropCoordinates.leafY = -1;
} else if (pairOrientation == 3) {
dropCoordinates.leafX = pairX - 1;
dropCoordinates.leafY = -2;
for (let i = 11; i >= 0; i--) {
if (grid[i][pairX - 1] == 0) {
dropCoordinates.leafY = i;
break;
}
}
if (dropCoordinates.leafY == -2 && hiddenRow[pairX - 1] == 0)
dropCoordinates.leafY = -1;
} else if (pairOrientation == 2) {
dropCoordinates.leafX = pairX;
dropCoordinates.leafY = dropCoordinates.pivotY;
dropCoordinates.pivotY--;
} else {
dropCoordinates.leafX = pairX;
dropCoordinates.leafY = dropCoordinates.pivotY - 1;
}
};
/*
Moves the current pair a certain number of slots.
*/
let movePair = function(dx) {
pairX += dx;
if (dx < 0) {
if (pairX < (pairOrientation == 3 ? 1 : 0))
pairX = (pairOrientation == 3 ? 1 : 0);
}
if (dx > 0) {
if (pairX > (pairOrientation == 1 ? 4 : 5))
pairX = (pairOrientation == 1 ? 4 : 5);
}
calcDropCoordinates();
};
/*
Rotates the pair in the given direction around the pivot.
*/
let rotatePair = function(clockwise) {
pairOrientation += (clockwise ? 1 : -1);
if (pairOrientation > 3)
pairOrientation = 0;
if (pairOrientation < 0)
pairOrientation = 3;
if (pairOrientation == 1 && pairX == 5)
pairX = 4;
if (pairOrientation == 3 && pairX == 0)
pairX = 1;
calcDropCoordinates();
};
/*
Places the current pair at the drop coordinates.
*/
let quickDrop = function() {
if (dropCoordinates.pivotY == -1) {
hiddenRow[dropCoordinates.pivotX] = currentPair.pivot;
} else if (dropCoordinates.pivotY > -1) {
grid[dropCoordinates.pivotY][dropCoordinates.pivotX] = currentPair.pivot;
}
if (dropCoordinates.leafY == -1) {
hiddenRow[dropCoordinates.leafX] = currentPair.leaf;
} else if (dropCoordinates.leafY > -1) {
grid[dropCoordinates.leafY][dropCoordinates.leafX] = currentPair.leaf;
}
currentPair.pivot = 0;
currentPair.leaf = 0;
};
/*
Makes all blobs fall to the lowest available slot.
All blobs that fall will be added to slotsToCheck.
*/
let settleBlobs = function() {
for (let x = 0; x < 6; x++) {
let lowestOpen = 11;
for (let y = 11; y >= 0; y--) {
if (grid[y][x] != 0) {
if (y != lowestOpen) {
grid[lowestOpen][x] = grid[y][x];
grid[y][x] = 0;
addSlotToCheck(x, lowestOpen);
}
lowestOpen--;
}
}
if (lowestOpen >= 0 && hiddenRow[x] != 0) {
grid[lowestOpen][x] = hiddenRow[x];
hiddenRow[x] = 0;
addSlotToCheck(x, lowestOpen);
}
}
};
/*
Adds a slot to slotsToCheck. This slot will be checked for a pop
next time popAll is called.
*/
let addSlotToCheck = function(x, y) {
slotsToCheck.push({x: x, y: y});
};
/*
Checks for a pop at every slot in slotsToCheck.
Pops at all locations.
*/
let popAll = function() {
let result = {pops: 0};
while(slotsToCheck.length > 0) {
let coord = slotsToCheck.pop();
if (grid[coord.y][coord.x] != 0 && grid[coord.y][coord.x] != NUISANCE_COLOR) {
if (checkSlotForPop(coord.x, coord.y))
result.pops += 1;
}
}
return result;
};
/*
Checks a specific slot for a pop.
If there are four or more adjacent blobs of the same color, they are removed.
*/
let checkSlotForPop = function(x, y) {
let toDelete = [
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0]),
new Uint8Array([0, 0, 0, 0, 0, 0])
];
let blobsInClump = 0;
let color = grid[y][x];
let toCheck = [{x: x, y: y}];
//Count every blob in this clump
while (toCheck.length > 0) {
let coord = toCheck.pop();
if (grid[coord.y][coord.x] == color && toDelete[coord.y][coord.x] == 0) {
blobsInClump++;
toDelete[coord.y][coord.x] = 1;
if (coord.x > 0) toCheck.push({x: coord.x - 1, y: coord.y});
if (coord.x < 5) toCheck.push({x: coord.x + 1, y: coord.y});
if (coord.y > 0) toCheck.push({x: coord.x, y: coord.y - 1});
if (coord.y < 11) toCheck.push({x: coord.x, y: coord.y + 1});
}
if (grid[coord.y][coord.x] == NUISANCE_COLOR && toDelete[coord.y][coord.x] == 0)
toDelete[coord.y][coord.x] = 1; //For erasing garbage
}
//If there are at least four blobs in this clump, remove them from the grid and draw a pop.
if (blobsInClump >= 4) {
for (let y = 0; y < 12; y++) {
for (let x = 0; x < 6; x++) {
if (toDelete[y][x] == 1) {
grid[y][x] = 0;
//Clear the blob out of the slot
g.setBgColor(0, 0, 0);
g.clearRect((x*18)+34, (y*14)+7, (x*18)+52, (y*14)+21);
//Draw the pop
let colorInfo = getColor(color);
g.setColor(colorInfo.r, colorInfo.g, colorInfo.b);
if (color < NUISANCE_COLOR) {
//A fancy pop for popped colors!
g.drawEllipse((x*18)+36, (y*14)+7, (x*18)+50, (y*14)+21);
g.drawEllipse((x*18)+27, (y*14)-2, (x*18)+59, (y*14)+30);
} else if (color == NUISANCE_COLOR) {
//Nuisance Blobs are simply crossed out.
//TODO: Nuisance Blobs are currently unusued, but also untested. Test before use.
g.drawLine((x*18)+34, (y*14)+7, (x*18)+52, (y*14)+21);
}
}
}
}
return true;
}
return false;
};
// Variables for graphics
let oldGhost = {pivotX: 0, pivotY: 0, leafX: 0, leafY: 0};
/*
Draws the time on the side.
*/
let drawTime = function(scheduleNext) {
//Change this to alter the y-coordinate of the top edge.
let dy = 25;
g.setBgColor(0, 0, 0);
g.clearRect(2, dy, 30, dy + 121);
//Draw the time
let d = new Date();
let h = d.getHours(), m = d.getMinutes();
if (twelveHourTime) {
let mer = 'A';
if (h >= 12) mer = 'P';
if (h >= 13) h -= 12;
if (h == 0) h = 12;
g.setColor(1, 1, 1);
g.setFont("Vector", 12);
g.drawString(mer, 23, dy + 63);
}
let hs = h.toString().padStart(2, 0);
let ms = m.toString().padStart(2, 0);
g.setFont("Vector", 24);
g.setColor(1, 0.2, 1);
g.drawString(hs, 3, dy + 21);
g.setColor(0.5, 0.5, 1);
g.drawString(ms, 3, dy + 42);
//Draw seconds
let s = d.getSeconds();
if (updateSeconds) {
let ss = s.toString().padStart(2, 0);
g.setFont("Vector", 12);
g.setColor(0.2, 1, 0.2);
g.drawString(ss, 3, dy + 63);
}
//Draw the date
let dayString = d.getDate().toString();
let dayNames = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
let dayName = dayNames[d.getDay()];
let monthNames = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JLY", "AUG", "SEP", "OCT", "NOV", "DEC"];
let monthName = monthNames[d.getMonth()];
g.setColor(1, 1, 1);
g.setFont("Vector", 12);
g.drawString(monthName, 3, dy + 84);
g.drawString(dayString, 3, dy + 97);
g.setColor(0.5, 0.5, 0.5);
g.drawString(dayName, 3, dy + 110);
//Draw battery
if (s == 0) batteryLevel = E.getBattery();
if (Bangle.isCharging()) {
g.setColor(0, 0, 1);
} else if (batteryLevel <= 15) {
g.setColor(1, 0, 0);
} else {
g.setColor(0, 1, 0);
}
g.drawString(batteryLevel + "%", 3, dy + 1);
//Schedule the next draw if requested.
if (!scheduleNext) return;
if (clockDrawTimeout) clearTimeout(clockDrawTimeout);
let interval = updateSeconds ? 1000 : 60000;
clockDrawTimeout = setTimeout(function() {
clockDrawTimeout = undefined;
drawTime(true);
}, interval - (Date.now() % interval));
};
/*
Returns a tuple in the format {r, g, b} with the color
of the blob with the given ID.
This saves memory compared to having the colors stored in an array.
*/
let getColor = function(color) {
if (color == 1)
return {r: 1, g: 0, b: 0};
if (color == 2)
return {r: 0, g: 1, b: 0};
if (color == 3)
return {r: 0, g: 0, b: 1};
if (color == 4)
return {r: 1, g: 1, b: 0};
if (color == 5)
return {r: 1, g: 0, b: 1};
if (color == 6)
return {r: 0, g: 1, b: 1};
if (color == 7)
return {r: 0.5, g: 0.5, b: 0.5};
return {r: 1, g: 1, b: 1};
};
/*
Clears the screen and draws the background.
*/
let drawBackground = function() {
//Background
g.setBgColor(0.5, 0.2, 0.1);
g.clear();
g.setBgColor(0, 0, 0);
g.clearRect(33, 0, 142, 176);
g.setBgColor(0.5, 0.5, 0.5);
g.clearRect(33, 4, 142, 6);
//Reset button
g.setBgColor(0.5, 0.5, 0.5);
g.setColor(0, 0, 0);
g.clearRect(143, 150, 175, 175);
g.setFont("Vector", 30);
g.drawString("R", 152, 150);
//Pause button
g.clearRect(0, 150, 32, 175);
g.fillRect(9, 154, 13, 171);
g.fillRect(18, 154, 22, 171);
};
/*
Draws a box under the next queue that displays
the current value of lastChain.
*/
let drawChainCount = function() {
g.setBgColor(0, 0, 0);
g.setColor(1, 0.2, 0.2);
g.setFont("Vector", 23);
g.clearRect(145, 42, 173, 64);
if (lastChain > 0) {
if (lastChain < 10) g.drawString(lastChain, 154, 44);
if (lastChain >= 10) g.drawString(lastChain, 147, 44);
}
};
/*
Draws the blob at the given slot.
*/
let drawBlobAtSlot = function(x, y) {
//If this blob is in the hidden row, clear it out and stop.
if (y < 0) {
g.setBgColor(0, 0, 0);
g.clearRect((x*18)+34, 0, (x*18)+52, 3);
return;
}
//First, clear what was in that slot.
g.setBgColor(0, 0, 0);
g.clearRect((x*18)+34, (y*14)+7, (x*18)+52, (y*14)+21);
let color = grid[y][x];
if (color != 0) {
let myColor = getColor(color);
g.setColor(myColor.r, myColor.g, myColor.b);
g.fillEllipse((x*18)+34, (y*14)+7, (x*18)+52, (y*14)+21);
g.setColor(1, 1, 1);
g.drawEllipse((x*18)+34, (y*14)+7, (x*18)+52, (y*14)+21);
}
};
/*
Draws the ghost piece.
clearOld: if the previous location of the ghost piece should be cleared.
*/
let drawGhostPiece = function(clearOld) {
if (clearOld) {
g.setColor(0, 0, 0);
g.fillRect((oldGhost.pivotX*18)+38, (oldGhost.pivotY*14)+8, (oldGhost.pivotX*18)+47, (oldGhost.pivotY*14)+17);
g.fillRect((oldGhost.leafX*18)+38, (oldGhost.leafY*14)+8, (oldGhost.leafX*18)+47, (oldGhost.leafY*14)+17);
}
let pivotX = dropCoordinates.pivotX;
let pivotY = dropCoordinates.pivotY;
let leafX = dropCoordinates.leafX;
let leafY = dropCoordinates.leafY;
let pivotColor = getColor(currentPair.pivot);
let leafColor = getColor(currentPair.leaf);
g.setColor(pivotColor.r, pivotColor.g, pivotColor.b);
g.fillRect((pivotX*18)+40, (pivotY*14)+10, (pivotX*18)+45, (pivotY*14)+15);
g.setColor(1, 1, 1);
g.drawRect((pivotX*18)+38, (pivotY*14)+8, (pivotX*18)+47, (pivotY*14)+17);
g.setColor(leafColor.r, leafColor.g, leafColor.b);
g.fillRect((leafX*18)+40, (leafY*14)+10, (leafX*18)+45, (leafY*14)+15);
oldGhost = {pivotX: pivotX, pivotY: pivotY, leafX: leafX, leafY: leafY};
};
/*
Draws the next queue.
*/
let drawNextQueue = function() {
g.setBgColor(0, 0, 0);
g.clearRect(145, 4, 173, 28);
let p1 = nextQueue[0].pivot;
let l1 = nextQueue[0].leaf;
let p2 = nextQueue[1].pivot;
let l2 = nextQueue[1].leaf;
let p1C = getColor(p1);
let l1C = getColor(l1);
let p2C = getColor(p2);
let l2C = getColor(l2);
g.setColor(p1C.r, p1C.g, p1C.b);
g.fillEllipse(146, 17, 157, 28);
g.setColor(l1C.r, l1C.g, l1C.b);
g.fillEllipse(146, 5, 157, 16);
g.setColor(p2C.r, p2C.g, p2C.b);
g.fillEllipse(162, 17, 173, 28);
g.setColor(l2C.r, l2C.g, l2C.b);
g.fillEllipse(162, 5, 173, 16);
g.setColor(1, 1, 1);
g.drawLine(159, 4, 159, 28);
g.drawEllipse(146, 17, 157, 28);
g.drawEllipse(146, 5, 157, 16);
g.drawEllipse(162, 17, 173, 28);
g.drawEllipse(162, 5, 173, 16);
};
/*
Redraws the screen, except for the ghost piece.
*/
let redrawBoard = function() {
drawBackground();
drawNextQueue();
drawChainCount();
drawTime(false);
for (let y = 0; y < 12; y++) {
for (let x = 0; x < 6; x++) {
drawBlobAtSlot(x, y);
}
}
};
/*
Toggles the pause screen.
*/
let togglePause = function() {
gamePaused = !gamePaused;
if (gamePaused) {
g.setBgColor(0.5, 0.2, 0.1);
g.clear();
drawTime(false);
g.setBgColor(0, 0, 0);
g.setColor(1, 1, 1);
g.clearRect(48, 66, 157, 110);
g.setFont("Vector", 20);
g.drawString("Tap here\nto unpause", 50, 68);
require("widget_utils").show();
Bangle.drawWidgets();
} else {
require("widget_utils").hide();
redrawBoard();
drawGhostPiece(false);
//Display the loss text if the game is lost.
if (gameLost) {
g.setBgColor(0, 0, 0);
g.setColor(1, 1, 1);
g.clearRect(33, 73, 142, 103);
g.setFont("Vector", 20);
g.drawString("You Lose", 43, 80);
}
}
};
// ~~ Events ~~
let dragAmnt = 0;
let onTouch = (z, e) => {
if (midChain) return;
if (gamePaused) {
if (e.x >= 40 && e.y >= 58 && e.x <= 165 && e.y <= 118) {
g.setBgColor(1, 1, 1);
g.clearRect(48, 66, 157, 110);
g.flip();
togglePause();
}
} else {
//Tap reset button
if (e.x >= 143 && e.y >= 150) {
restartGame();
newPair();
redrawBoard();
drawGhostPiece(false);
g.flip();
return;
}
//Tap pause button
if (e.x <= 32 && e.y >= 150) {
togglePause();
return;
}
//While playing, rotate pieces.
if (!gameLost && !gamePaused) {
if (e.x < 88) {
rotatePair(false);
drawGhostPiece(true);
} else {
rotatePair(true);
drawGhostPiece(true);
}
}
}
};
Bangle.on("touch", onTouch);
let onDrag = (e) => {
if (gameLost || gamePaused || midChain) return;
//Do nothing if the user is dragging down so that they don't accidentally move while dropping
if (e.dy >= 5) {
return;
}
dragAmnt += e.dx;
if (e.b == 0) {
dragAmnt = 0;
}
if (dragAmnt >= 20) {
movePair(Math.floor(dragAmnt / 20));
drawGhostPiece(true);
dragAmnt = dragAmnt % 20;
}
if (dragAmnt <= -20) {
movePair(Math.ceil(dragAmnt / 20));
drawGhostPiece(true);
dragAmnt = dragAmnt % 20;
}
};
Bangle.on("drag", onDrag);
let onSwipe = (x, y) => {
if (gameLost || gamePaused || midChain) return;
if (y > 0) {
let pivotX = dropCoordinates.pivotX;
let pivotY = dropCoordinates.pivotY;
let leafX = dropCoordinates.leafX;
let leafY = dropCoordinates.leafY;
if (pivotY < -1 && leafY < -1) return;
quickDrop();
drawBlobAtSlot(pivotX, pivotY);
drawBlobAtSlot(leafX, leafY);
g.flip();
//Check for pops
if (pivotY >= 0) addSlotToCheck(pivotX, pivotY);
if (leafY >= 0) addSlotToCheck(leafX, leafY);
midChain = true;
let currentChain = 0;
while (popAll().pops > 0) {
currentChain++;
lastChain = currentChain;
drawChainCount();
g.flip();
settleBlobs();
redrawBoard();
g.flip();
}
newPair();
drawNextQueue();
drawGhostPiece(false);
//If the top slot of the third column is taken, lose the game.
if (grid[0][2] != 0) {
gameLost = true;
g.setBgColor(0, 0, 0);
g.setColor(1, 1, 1);
g.clearRect(33, 73, 142, 103);
g.setFont("Vector", 20);
g.drawString("You Lose", 43, 80);
}
midChain = false;
}
};
Bangle.on("swipe", onSwipe);
let onLock = on => {
updateSeconds = !on;
drawTime(true);
};
Bangle.on('lock', onLock);
let onCharging = charging => {
drawTime(false);
};
Bangle.on('charging', onCharging);
Bangle.setUI({mode:"clock", remove:function() {
//Remove listeners
Bangle.removeListener("touch", onTouch);
Bangle.removeListener("drag", onDrag);
Bangle.removeListener("swipe", onSwipe);
Bangle.removeListener('lock', onLock);
Bangle.removeListener('charging', onCharging);
if (clockDrawTimeout) clearTimeout(clockDrawTimeout);
require("widget_utils").show();
}});
g.reset();
Bangle.loadWidgets();
require("widget_utils").hide();
drawBackground();
drawTime(true);
restartGame();
newPair();
drawGhostPiece(false);
drawNextQueue();
drawChainCount();
}

BIN
apps/bblobface/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

View File

@ -0,0 +1,15 @@
{ "id": "bblobface",
"name": "Bangle Blobs Clock",
"shortName":"BBClock",
"icon": "app.png",
"version": "1.00",
"description": "A fully featured watch face with a playable game on the side.",
"readme":"README.md",
"type": "clock",
"tags": "clock, game",
"supports" : ["BANGLEJS2"],
"storage": [
{"name":"bblobface.app.js","url":"app.js"},
{"name":"bblobface.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -3,3 +3,4 @@
0.03: Use default Bangle formatter for booleans
0.04: Add options for units in locale and recording GPS
0.05: Allow toggling of "max" values (screen tap) and recording (button press)
0.06: Fix local unit setting

View File

@ -420,7 +420,7 @@ function updateClock() {
// Read settings.
let cfg = require('Storage').readJSON('bikespeedo.json',1)||{};
cfg.spd = !cfg.localeUnits; // Multiplier for speed unit conversions. 0 = use the locale values for speed
cfg.spd = cfg.localeUnits ? 0 : 1; // Multiplier for speed unit conversions. 0 = use the locale values for speed
cfg.spd_unit = 'km/h'; // Displayed speed unit
cfg.alt = 1; // Multiplier for altitude unit conversions. (feet:'0.3048')
cfg.alt_unit = 'm'; // Displayed altitude units ('feet')

View File

@ -2,7 +2,7 @@
"id": "bikespeedo",
"name": "Bike Speedometer (beta)",
"shortName": "Bike Speedometer",
"version": "0.05",
"version": "0.06",
"description": "Shows GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude from internal sources",
"icon": "app.png",
"screenshots": [{"url":"Screenshot.png"}],

View File

@ -67,3 +67,4 @@
0.56: Settings.log = 0,1,2,3 for off,display, log, both
0.57: Handle the whitelist being disabled
0.58: "Make Connectable" temporarily bypasses the whitelist
0.59: Whitelist: Try to resolve peer addresses using NRF.resolveAddress() - for 2v19 or 2v18 cutting edge builds

View File

@ -79,7 +79,7 @@ if (global.save) boot += `global.save = function() { throw new Error("You can't
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`;
if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!NRF.ignoreWhitelist && !(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!NRF.ignoreWhitelist) { let whitelist = (require('Storage').readJSON('setting.json',1)||{}).whitelist; if (NRF.resolveAddress !== undefined) { let resolvedAddr = NRF.resolveAddress(addr); if (resolvedAddr !== undefined) addr = resolvedAddr + " (resolved)"; } if (!whitelist.includes(addr)) NRF.disconnect(); }});\n`;
if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation
// ================================================== FIXING OLDER FIRMWARES
if (FWVERSION<215.068) // 2v15.68 and before had compass heading inverted.

View File

@ -1,7 +1,7 @@
{
"id": "boot",
"name": "Bootloader",
"version": "0.58",
"version": "0.59",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png",
"type": "bootloader",

View File

@ -1 +1,2 @@
0.01: Initial release.
0.02: Handle the case where other apps have set bleAdvert to an array

View File

@ -1,6 +1,22 @@
(() => {
function advertiseBattery() {
Bangle.bleAdvert[0x180F] = [E.getBattery()];
if(Array.isArray(Bangle.bleAdvert)){
// ensure we're in the cycle
var found = false;
for(var ad in Bangle.bleAdvert){
if(ad[0x180F]){
ad[0x180F] = [E.getBattery()];
found = true;
break;
}
}
if(!found)
Bangle.bleAdvert.push({ 0x180F: [E.getBattery()] });
}else{
// simple object
Bangle.bleAdvert[0x180F] = [E.getBattery()];
}
NRF.setAdvertising(Bangle.bleAdvert);
}

View File

@ -2,7 +2,7 @@
"id": "bootgattbat",
"name": "BLE GATT Battery Service",
"shortName": "BLE Battery Service",
"version": "0.01",
"version": "0.02",
"description": "Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth.\n",
"icon": "bluetooth.png",
"type": "bootloader",

View File

@ -1 +1,2 @@
0.01: New app!
0.02: Advertise accelerometer data and sensor location

View File

@ -1,10 +1,16 @@
var _a;
{
var __assign = Object.assign;
var Layout_1 = require("Layout");
Bangle.loadWidgets();
Bangle.drawWidgets();
var HRM_MIN_CONFIDENCE_1 = 75;
var services_1 = ["0x180d", "0x181a", "0x1819"];
var services_1 = [
"0x180d",
"0x181a",
"0x1819",
"E95D0753251D470AA062FA1922DFA9A8",
];
var acc_1;
var bar_1;
var gps_1;
@ -21,7 +27,6 @@
mag: false,
};
var idToName = {
acc: "Acceleration",
bar: "Barometer",
gps: "GPS",
hrm: "HRM",
@ -69,7 +74,6 @@
{
type: "h",
c: [
__assign(__assign({ type: "btn", label: idToName.acc, id: "acc", cb: function () { } }, btnStyle), { col: colour_1.on, btnBorder: colour_1.on }),
__assign({ type: "btn", label: "Back", cb: function () {
setBtnsShown_1(false);
} }, btnStyle),
@ -222,6 +226,13 @@
return [x[0], x[1], y[0], y[1], z[0], z[1]];
};
encodeMag_1.maxLen = 6;
var encodeAcc_1 = function (data) {
var x = toByteArray_1(data.x * 1000, 2, true);
var y = toByteArray_1(data.y * 1000, 2, true);
var z = toByteArray_1(data.z * 1000, 2, true);
return [x[0], x[1], y[0], y[1], z[0], z[1]];
};
encodeAcc_1.maxLen = 6;
var toByteArray_1 = function (value, numberOfBytes, isSigned) {
var byteArray = new Array(numberOfBytes);
if (isSigned && (value < 0)) {
@ -251,6 +262,7 @@
case "0x180d": return !!hrm_1;
case "0x181a": return !!(bar_1 || mag_1);
case "0x1819": return !!(gps_1 && gps_1.lat && gps_1.lon || mag_1);
case "E95D0753251D470AA062FA1922DFA9A8": return !!acc_1;
}
};
var serviceToAdvert_1 = function (serv, initial) {
@ -264,11 +276,20 @@
readable: true,
notify: true,
};
var os = {
maxLen: 1,
readable: true,
notify: true,
};
if (hrm_1) {
o.value = encodeHrm_1(hrm_1);
os.value = [2];
hrm_1 = undefined;
}
return _a = {}, _a["0x2a37"] = o, _a;
return _a = {},
_a["0x2a37"] = o,
_a["0x2a38"] = os,
_a;
}
return {};
case "0x1819":
@ -331,6 +352,21 @@
}
return o;
}
case "E95D0753251D470AA062FA1922DFA9A8": {
var o = {};
if (acc_1 || initial) {
o["E95DCA4B251D470AA062FA1922DFA9A8"] = {
maxLen: encodeAcc_1.maxLen,
readable: true,
notify: true,
};
if (acc_1) {
o["E95DCA4B251D470AA062FA1922DFA9A8"].value = encodeAcc_1(acc_1);
acc_1 = undefined;
}
}
return o;
}
}
};
var getBleAdvert_1 = function (map, all) {
@ -402,12 +438,23 @@
enableSensors_1();
{
var ad = getBleAdvert_1(function (serv) { return serviceToAdvert_1(serv, true); }, true);
var adServices = Object
.keys(ad)
.map(function (k) { return k.replace("0x", ""); });
NRF.setServices(ad, {
advertise: adServices,
uart: false,
});
var bangle2 = Bangle;
var cycle = Array.isArray(bangle2.bleAdvert) ? bangle2.bleAdvert : [];
for (var id in ad) {
var serv = ad[id];
var value = void 0;
for (var ch in serv) {
value = serv[ch].value;
break;
}
cycle.push((_a = {}, _a[id] = value || [], _a));
}
bangle2.bleAdvert = cycle;
NRF.setAdvertising(cycle, {
interval: 100,
});
}
}

View File

@ -33,16 +33,27 @@ const enum BleServ {
// contains: LocationAndSpeed
LocationAndNavigation = "0x1819",
// Acc // none known for this
// org.microbit.service.accelerometer
// contains: Acc
Acc = "E95D0753251D470AA062FA1922DFA9A8",
}
const services = [BleServ.HRM, BleServ.EnvSensing, BleServ.LocationAndNavigation];
const services = [
BleServ.HRM,
BleServ.EnvSensing,
BleServ.LocationAndNavigation,
BleServ.Acc,
];
const enum BleChar {
// org.bluetooth.characteristic.heart_rate_measurement
// <see encode function>
HRM = "0x2a37",
// org.bluetooth.characteristic.body_sensor_location
// u8
SensorLocation = "0x2a38",
// org.bluetooth.characteristic.elevation
// s24, meters 0.01
Elevation = "0x2a6c",
@ -65,6 +76,11 @@ const enum BleChar {
// org.bluetooth.characteristic.magnetic_flux_density_3d
// s16: x, y, z, tesla (10^-7)
MagneticFlux3D = "0x2aa1",
// org.microbit.characteristic.accelerometer_data
// s16 x3, -1024 .. 1024
// docs: https://lancaster-university.github.io/microbit-docs/ble/accelerometer-service/
Acc = "E95DCA4B251D470AA062FA1922DFA9A8",
}
type BleCharAdvert = {
@ -84,6 +100,16 @@ type LenFunc<T> = {
maxLen: number,
}
const enum SensorLocations {
Other = 0,
Chest = 1,
Wrist = 2,
Finger = 3,
Hand = 4,
EarLobe = 5,
Foot = 6,
}
let acc: undefined | AccelData;
let bar: undefined | PressureData;
let gps: undefined | GPSFix;
@ -104,8 +130,7 @@ const settings: BtAdvMap<boolean> = {
mag: false,
};
const idToName: BtAdvMap<string, true> = {
acc: "Acceleration",
const idToName: BtAdvMap<string> = {
bar: "Barometer",
gps: "GPS",
hrm: "HRM",
@ -197,15 +222,6 @@ const btnLayout = new Layout(
{
type: "h",
c: [
{
type: "btn",
label: idToName.acc,
id: "acc",
cb: () => {},
...btnStyle,
col: colour.on,
btnBorder: colour.on,
},
{
type: "btn",
label: "Back",
@ -464,6 +480,15 @@ const encodeMag: LenFunc<CompassData> = (data: CompassData) => {
};
encodeMag.maxLen = 6;
const encodeAcc: LenFunc<AccelData> = (data: AccelData) => {
const x = toByteArray(data.x * 1000, 2, true);
const y = toByteArray(data.y * 1000, 2, true);
const z = toByteArray(data.z * 1000, 2, true);
return [ x[0]!, x[1]!, y[0]!, y[1]!, z[0]!, z[1]! ];
};
encodeAcc.maxLen = 6;
const toByteArray = (value: number, numberOfBytes: number, isSigned: boolean) => {
const byteArray: Array<number> = new Array(numberOfBytes);
@ -503,6 +528,7 @@ const haveServiceData = (serv: BleServ): boolean => {
case BleServ.HRM: return !!hrm;
case BleServ.EnvSensing: return !!(bar || mag);
case BleServ.LocationAndNavigation: return !!(gps && gps.lat && gps.lon || mag);
case BleServ.Acc: return !!acc;
}
};
@ -515,12 +541,22 @@ const serviceToAdvert = (serv: BleServ, initial = false): BleServAdvert => {
readable: true,
notify: true,
};
const os: BleCharAdvert = {
maxLen: 1,
readable: true,
notify: true,
};
if (hrm) {
o.value = encodeHrm(hrm);
os.value = [SensorLocations.Wrist];
hrm = undefined;
}
return { [BleChar.HRM]: o };
return {
[BleChar.HRM]: o,
[BleChar.SensorLocation]: os,
};
}
return {};
@ -591,6 +627,25 @@ const serviceToAdvert = (serv: BleServ, initial = false): BleServAdvert => {
return o;
}
case BleServ.Acc: {
const o: BleServAdvert = {};
if (acc || initial) {
o[BleChar.Acc] = {
maxLen: encodeAcc.maxLen,
readable: true,
notify: true,
};
if (acc) {
o[BleChar.Acc]!.value = encodeAcc(acc);
acc = undefined;
}
}
return o;
}
}
};
@ -702,16 +757,39 @@ enableSensors();
// must have fixed services from the start:
const ad = getBleAdvert(serv => serviceToAdvert(serv, true), /*all*/true);
const adServices = Object
.keys(ad)
.map((k: string) => k.replace("0x", ""));
NRF.setServices(
ad,
{
advertise: adServices,
uart: false,
},
);
type BleAdvert = { [key: string]: number[] };
const bangle2 = Bangle as {
bleAdvert?: BleAdvert | BleAdvert[];
};
const cycle = Array.isArray(bangle2.bleAdvert) ? bangle2.bleAdvert : [];
for(const id in ad){
const serv = ad[id as BleServ];
let value;
// pick the first characteristic to advertise
for(const ch in serv){
value = serv[ch as BleChar]!.value;
break;
}
cycle.push({ [id]: value || [] });
}
bangle2.bleAdvert = cycle;
NRF.setAdvertising(
cycle,
{
interval: 100,
}
);
}
}

View File

@ -2,7 +2,7 @@
"id": "btadv",
"name": "btadv",
"shortName": "btadv",
"version": "0.01",
"version": "0.02",
"description": "Advertise & export live heart rate, accel, pressure, GPS & mag data over bluetooth",
"icon": "icon.png",
"tags": "health,tool,sensors,bluetooth",

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Handle the case where other apps have set bleAdvert to an array

View File

@ -23,7 +23,7 @@ function onTemperature(p) {
var temp100 = Math.round(avrTemp*100);
var pressure100 = Math.round(avrPressure*100);
Bangle.bleAdvert[0xFCD2] = [ 0x40, /* BTHome Device Information
var advert = [ 0x40, /* BTHome Device Information
bit 0: "Encryption flag"
bit 1-4: "Reserved for future use"
bit 5-7: "BTHome Version" */
@ -37,6 +37,21 @@ function onTemperature(p) {
0x04, // Pressure, 16 bit
pressure100&255,(pressure100>>8)&255,pressure100>>16
];
if(Array.isArray(Bangle.bleAdvert)){
var found = false;
for(var ad in Bangle.bleAdvert){
if(ad[0xFCD2]){
ad[0xFCD2] = advert;
found = true;
break;
}
}
if(!found)
Bangle.bleAdvert.push({ 0xFCD2: advert });
}else{
Bangle.bleAdvert[0xFCD2] = advert;
}
NRF.setAdvertising(Bangle.bleAdvert);
}

View File

@ -1,7 +1,7 @@
{ "id": "bthometemp",
"name": "BTHome Temperature and Pressure",
"shortName":"BTHome T",
"version":"0.01",
"version":"0.02",
"description": "Displays temperature and pressure, and advertises them over bluetooth using BTHome.io standard",
"icon": "app.png",
"tags": "bthome,bluetooth,temperature",

View File

@ -14,3 +14,4 @@
0.13: Switch to swipe left/right for month and up/down for year selection
Display events for current month on touch
0.14: Add support for holidays
0.15: Edit holidays on device in settings

View File

@ -76,11 +76,32 @@ getDowLbls = function(locale) {
};
sameDay = function(d1, d2) {
"jit";
return d1.getFullYear() === d2.getFullYear() &&
d1.getMonth() === d2.getMonth() &&
d1.getDate() === d2.getDate();
};
drawEvent = function(ev, curDay, x1, y1, x2, y2) {
"ram";
switch(ev.type) {
case "e": // alarm/event
const hour = 0|ev.date.getHours() + 0|ev.date.getMinutes()/60.0;
const slice = hour/24*(eventsPerDay-1); // slice 0 for 0:00 up to eventsPerDay for 23:59
const height = (y2-2) - (y1+2); // height of a cell
const sliceHeight = height/eventsPerDay;
const ystart = (y1+2) + slice*sliceHeight;
g.setColor(bgEvent).fillRect(x1+1, ystart, x2-2, ystart+sliceHeight);
break;
case "h": // holiday
g.setColor(bgColorWeekend).fillRect(x1+1, y1+1, x2-1, y2-1);
break;
case "o": // other
g.setColor(bgOtherEvent).fillRect(x1+1, y1+1, x2-1, y2-1);
break;
}
}
drawCalendar = function(date) {
g.setBgColor(bgColor);
g.clearRect(0, 0, maxX, maxY);
@ -119,7 +140,6 @@ drawCalendar = function(date) {
true
);
g.setFont("6x8", fontSize);
let dowLbls = getDowLbls(require('locale').name);
dowLbls.forEach((lbl, i) => {
g.drawString(lbl, i * colW + colW / 2, headerH + rowH / 2);
@ -173,6 +193,7 @@ drawCalendar = function(date) {
const eventsThisMonth = events.filter(ev => ev.date > weekBeforeMonth && ev.date < week2AfterMonth);
eventsThisMonth.sort((a,b) => a.date - b.date);
let i = 0;
g.setFont("8x12", fontSize);
for (y = 0; y < rowN - 1; y++) {
for (x = 0; x < colN; x++) {
i++;
@ -189,22 +210,7 @@ drawCalendar = function(date) {
// Display events for this day
eventsThisMonth.forEach((ev, idx) => {
if (sameDay(ev.date, curDay)) {
switch(ev.type) {
case "e": // alarm/event
const hour = ev.date.getHours() + ev.date.getMinutes()/60.0;
const slice = hour/24*(eventsPerDay-1); // slice 0 for 0:00 up to eventsPerDay for 23:59
const height = (y2-2) - (y1+2); // height of a cell
const sliceHeight = height/eventsPerDay;
const ystart = (y1+2) + slice*sliceHeight;
g.setColor(bgEvent).fillRect(x1+1, ystart, x2-2, ystart+sliceHeight);
break;
case "h": // holiday
g.setColor(bgColorWeekend).fillRect(x1+1, y1+1, x2-1, y2-1);
break;
case "o": // other
g.setColor(bgOtherEvent).fillRect(x1+1, y1+1, x2-1, y2-1);
break;
}
drawEvent(ev, curDay, x1, y1, x2, y2);
eventsThisMonth.splice(idx, 1); // this event is no longer needed
}
@ -222,17 +228,15 @@ drawCalendar = function(date) {
);
}
require("Font8x12").add(Graphics);
g.setFont("8x12", fontSize);
g.setColor(day < 50 ? fgOtherMonth : fgSameMonth);
g.drawString(
(day > 50 ? day - 50 : day).toString(),
x * colW + colW / 2,
headerH + rowH + y * rowH + rowH / 2
);
}
}
};
} // end for (x = 0; x < colN; x++)
} // end for (y = 0; y < rowN - 1; y++)
}; // end function drawCalendar
showMenu = function() {
const menu = {
@ -319,6 +323,7 @@ setUI = function() {
});
};
require("Font8x12").add(Graphics);
drawCalendar(date);
setUI();
Bangle.loadWidgets();

View File

@ -28,11 +28,16 @@ function readFile(input) {
for(let i=0; i<input.files.length; i++) {
const reader = new FileReader();
reader.addEventListener("load", () => {
const jCalData = ICAL.parse(reader.result);
const icalText = reader.result.substring(reader.result.indexOf("BEGIN:VCALENDAR")); // remove html before data
const jCalData = ICAL.parse(icalText);
const comp = new ICAL.Component(jCalData);
const vtz = comp.getFirstSubcomponent('vtimezone');
const tz = new ICAL.Timezone(vtz);
// Fetch the VEVENT part
comp.getAllSubcomponents('vevent').forEach(vevent => {
event = new ICAL.Event(vevent);
const event = new ICAL.Event(vevent);
event.startDate.zone = tz;
holidays = holidays.filter(holiday => !sameDay(new Date(holiday.date), event.startDate.toJSDate())); // remove if already exists
const holiday = eventToHoliday(event);

View File

@ -1,7 +1,7 @@
{
"id": "calendar",
"name": "Calendar",
"version": "0.14",
"version": "0.15",
"description": "Simple calendar",
"icon": "calendar.png",
"screenshots": [{"url":"screenshot_calendar.png"}],

View File

@ -1,27 +1,154 @@
(function (back) {
const FILE = "calendar.json";
const settings = require('Storage').readJSON(FILE, true) || {};
var FILE = "calendar.json";
const HOLIDAY_FILE = "calendar.days.json";
var settings = require('Storage').readJSON(FILE, true) || {};
if (settings.ndColors === undefined)
if (process.env.HWVERSION == 2) {
settings.ndColors = true;
} else {
settings.ndColors = false;
}
const holidays = require("Storage").readJSON(HOLIDAY_FILE,1).sort((a,b) => new Date(a.date) - new Date(b.date)) || [];
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
E.showMenu({
"": { "title": "Calendar" },
"< Back": () => back(),
'B2 Colors': {
value: settings.ndColors,
onchange: v => {
settings.ndColors = v;
writeSettings();
}
},
});
})
function writeHolidays() {
holidays.sort((a,b) => new Date(a.date) - new Date(b.date));
require('Storage').writeJSON(HOLIDAY_FILE, holidays);
}
function formatDate(d) {
return d.getFullYear() + "-" + (d.getMonth() + 1).toString().padStart(2, '0') + "-" + d.getDate().toString().padStart(2, '0');
}
const editdate = (i) => {
const holiday = holidays[i];
const date = new Date(holiday.date);
const dateStr = require("locale").date(date, 1);
const menu = {
"": { "title" : holiday.name},
"< Back": () => {
writeHolidays();
editdates();
},
/*LANG*/"Day": {
value: date ? date.getDate() : null,
min: 1,
max: 31,
wrap: true,
onchange: v => {
date.setDate(v);
holiday.date = formatDate(date);
}
},
/*LANG*/"Month": {
value: date ? date.getMonth() + 1 : null,
format: v => require("date_utils").month(v),
onchange: v => {
date.setMonth((v+11)%12);
holiday.date = formatDate(date);
}
},
/*LANG*/"Year": {
value: date ? date.getFullYear() : null,
min: 1900,
max: 2100,
onchange: v => {
date.setFullYear(v);
holiday.date = formatDate(date);
}
},
/*LANG*/"Name": () => {
require("textinput").input({text:holiday.name}).then(result => {
holiday.name = result;
editdate(i);
});
},
/*LANG*/"Type": {
value: function() {
switch(holiday.type) {
case 'h': return 0;
case 'o': return 1;
}
return 0;
}(),
min: 0, max: 1,
format: v => [/*LANG*/"Holiday", /*LANG*/"Other"][v],
onchange: v => {
holiday.type = function() {
switch(v) {
case 0: return 'h';
case 1: return 'o';
}
}();
}
},
/*LANG*/"Repeat": {
value: !!holiday.repeat,
format: v => v ? /*LANG*/"Yearly" : /*LANG*/"Never",
onchange: v => {
holiday.repeat = v ? 'y' : undefined;
}
},
/*LANG*/"Delete": () => E.showPrompt(/*LANG*/"Delete" + " " + menu[""].title + "?").then(function(v) {
if (v) {
holidays.splice(i, 1);
writeHolidays();
editdates();
} else {
editday(i);
}
}
),
};
try {
require("textinput");
} catch(e) {
// textinput not installed
delete menu[/*LANG*/"Name"];
}
E.showMenu(menu);
};
const editdates = () => {
const menu = holidays.map((holiday,i) => {
const date = new Date(holiday.date);
const dateStr = require("locale").date(date, 1);
return {
title: dateStr + ' ' + holiday.name,
onchange: v => setTimeout(() => editdate(i), 10),
};
});
menu[''] = { 'title': 'Holidays' };
menu['< Back'] = ()=>settingsmenu();
E.showMenu(menu);
};
const settingsmenu = () => {
E.showMenu({
"": { "title": "Calendar" },
"< Back": () => back(),
'B2 Colors': {
value: settings.ndColors,
onchange: v => {
settings.ndColors = v;
writeSettings();
}
},
/*LANG*/"Edit Holidays": () => editdates(),
/*LANG*/"Add Holiday": () => {
holidays.push({
"date":formatDate(new Date()),
"name":/*LANG*/"New",
"type":'h',
});
editdate(holidays.length-1);
},
});
};
settingsmenu();
})

35
apps/cards/Barcode.js Normal file
View File

@ -0,0 +1,35 @@
/*
* JS source adapted from https://github.com/lindell/JsBarcode
*
* The MIT License (MIT)
*
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
class Barcode{
constructor(data, options){
this.data = data;
this.text = options.text || data;
this.options = options;
}
}
module.exports = Barcode;

1
apps/cards/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Simple app to display loyalty cards

32
apps/cards/README.md Normal file
View File

@ -0,0 +1,32 @@
# Cards
Simple app to display loyalty cards synced from Catima through GadgetBridge.
The app can display the cards' info (balance, expiration, note, etc.) and tapping on the appropriate field will display the code, if the type is supported.
To come back to the visualization of the card's details from the code view, simply press the button.
Beware that the small screen of the Banglejs 2 cannot render properly complex barcodes (in fact the resolution is very limited to render most barcodes).
### Supported codes types
* `CODE_39`
* `CODABAR`
* `QR_CODE`
### Disclaimer
This app is a proof of concept, many codes are too complex to be rendered by the bangle's screen or hardware (at least with the current logic), keep that in mind.
### How to sync
We can synchronize cards with GadgetBridge and Catima, refer to those projects for further information.
The feature is currently available on nightly builds only.
It should be released from version 0.77 (not yet out at the time of writing).
GadgetBridge syncronizes all cards at once, if you have too many cards you may want to explicitly select which ones to syncronize, keep in mind the limitations of the Banglejs.
### Credits
Barcode generation adapted from [lindell/JsBarcode](https://github.com/lindell/JsBarcode)
QR code generation adapted from [ricmoo/QRCode](https://github.com/ricmoo/QRCode)

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4AYoIAjF/4v/F/4v/F/4v/FAdNAAsoADgv/F/4v/F/4vqu4AjF/4v/F/4v6poAjF/4AfFAYAGF/4v/F/4v/F/4v/F94A/AH4A/AH4A/ABo"))

210
apps/cards/app.js Normal file
View File

@ -0,0 +1,210 @@
/* CARDS is a list of:
{id:int,
name,
value,
type,
expiration,
color,
balance,
note,
...
}
*/
Bangle.loadWidgets();
Bangle.drawWidgets();
//may make it configurable in the future
const WHITE=-1
const BLACK=0
var FILE = "android.cards.json";
var Locale = require("locale");
var fontSmall = "6x8";
var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2";
var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2";
var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4";
var CARDS = require("Storage").readJSON("android.cards.json",true)||[];
var settings = require("Storage").readJSON("cards.settings.json",true)||{};
function getDate(timestamp) {
return new Date(timestamp*1000);
}
function formatDay(date) {
let formattedDate = Locale.dow(date,1) + " " + Locale.date(date).replace(/\d\d\d\d/,"");
if (!settings.useToday) {
return formattedDate;
}
const today = new Date(Date.now());
if (date.getDay() == today.getDay() && date.getMonth() == today.getMonth())
return /*LANG*/"Today ";
else {
const tomorrow = new Date(Date.now() + 86400 * 1000);
if (date.getDay() == tomorrow.getDay() && date.getMonth() == tomorrow.getMonth()) {
return /*LANG*/"Tomorrow ";
}
return formattedDate;
}
}
function getColor(intColor) {
return "#"+(0x1000000+Number(intColor)).toString(16).padStart(6,"0");
}
function isLight(color) {
var r = +("0x"+color.slice(1,3));
var g = +("0x"+color.slice(3,5));
var b = +("0x"+color.slice(5,7));
var threshold = 0x88 * 3;
return (r+g+b) > threshold;
}
function printSquareCode(binary, size) {
var padding = 5;
var ratio = (g.getWidth()-(2*padding))/size;
for (var y = 0; y < size; y++) {
for (var x = 0; x < size; x++) {
if (binary[x + y * size]) {
g.setColor(BLACK).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio});
} else {
g.setColor(WHITE).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio});
}
}
}
}
function printLinearCode(binary) {
var yFrom = 15;
var yTo = 28;
var width = g.getWidth()/binary.length;
for(var b = 0; b < binary.length; b++){
var x = b * width;
if(binary[b] === "1"){
g.setColor(BLACK).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
}
else if(binary[b]){
g.setColor(WHITE).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
}
}
}
function showCode(card) {
E.showScroller();
// keeping it on rising edge would come back twice..
setWatch(()=>showCard(card), BTN, {edge:"falling"});
// theme independent
g.setColor(WHITE).fillRect(0, 0, g.getWidth(), g.getHeight());
switch (card.type) {
case "QR_CODE": {
const getBinaryQR = require("cards.qrcode.js");
let code = getBinaryQR(card.value);
printSquareCode(code.data, code.size);
break;
}
case "CODE_39": {
g.setFont("Vector:20");
g.setFontAlign(0,1).setColor(BLACK);
g.drawString(card.value, g.getWidth()/2, g.getHeight());
const CODE39 = require("cards.code39.js");
let code = new CODE39(card.value, {});
printLinearCode(code.encode().data);
break;
}
case "CODABAR": {
g.setFont("Vector:20");
g.setFontAlign(0,1).setColor(BLACK);
g.drawString(card.value, g.getWidth()/2, g.getHeight());
const codabar = require("cards.codabar.js");
let code = new codabar(card.value, {});
printLinearCode(code.encode().data);
break;
}
default:
g.clear(true);
g.setFont("Vector:30");
g.setFontAlign(0,0);
g.drawString(card.value, g.getWidth()/2, g.getHeight()/2);
}
}
function showCard(card) {
var lines = [];
var bodyFont = fontBig;
if(!card) return;
g.setFont(bodyFont);
//var lines = [];
if (card.name) lines = g.wrapString(card.name, g.getWidth()-10);
var titleCnt = lines.length;
var start = getDate(card.expiration);
var includeDay = true;
lines = lines.concat("", /*LANG*/"View code");
var valueLine = lines.length - 1;
if (card.expiration)
lines = lines.concat("",/*LANG*/"Expires"+": ", g.wrapString(formatDay(getDate(card.expiration)), g.getWidth()-10));
if(card.balance)
lines = lines.concat("",/*LANG*/"Balance"+": ", g.wrapString(card.balance, g.getWidth()-10));
if(card.note && card.note.trim())
lines = lines.concat("",g.wrapString(card.note, g.getWidth()-10));
lines = lines.concat("",/*LANG*/"< Back");
var titleBgColor = card.color ? getColor(card.color) : g.theme.bg2;
var titleColor = g.theme.fg2;
if (card.color)
titleColor = isLight(titleBgColor) ? BLACK : WHITE;
E.showScroller({
h : g.getFontHeight(), // height of each menu item in pixels
c : lines.length, // number of menu items
// a function to draw a menu item
draw : function(idx, r) {
// FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12
g.setBgColor(idx<titleCnt ? titleBgColor : g.theme.bg).
setColor(idx<titleCnt ? titleColor : g.theme.fg).
clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
g.setFont(bodyFont).drawString(lines[idx], r.x, r.y);
}, select : function(idx) {
if (idx>=lines.length-2)
showList();
else if (idx==valueLine)
showCode(card);
},
back : () => showList()
});
}
// https://github.com/metafloor/bwip-js
// https://github.com/lindell/JsBarcode
function showList() {
if(CARDS.length == 0) {
E.showMessage(/*LANG*/"No cards");
return;
}
E.showScroller({
h : 52,
c : Math.max(CARDS.length,3), // workaround for 2v10.219 firmware (min 3 not needed for 2v11)
draw : function(idx, r) {"ram"
var card = CARDS[idx];
g.setColor(g.theme.fg);
g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
if (!card) return;
var isPast = false;
var x = r.x+2, name = card.name;
var body = card.expiration ? formatDay(getDate(card.expiration)) : "";
if (card.balance) body += "\n" + card.balance;
if (name) g.setFontAlign(-1,-1).setFont(fontBig)
.setColor(isPast ? "#888" : g.theme.fg).drawString(name, x+4,r.y+2);
if (body) {
g.setFontAlign(-1,-1).setFont(fontMedium).setColor(isPast ? "#888" : g.theme.fg);
g.drawString(body, x+10,r.y+20);
}
g.setColor("#888").fillRect(r.x,r.y+r.h-1,r.x+r.w-1,r.y+r.h-1); // dividing line between items
if(card.color) {
g.setColor(getColor(card.color));
g.fillRect(r.x,r.y+4,r.x+3, r.y+r.h-4);
}
},
select : idx => showCard(CARDS[idx]),
back : () => load()
});
}
showList();

BIN
apps/cards/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

88
apps/cards/codabar.js Normal file
View File

@ -0,0 +1,88 @@
// Encoding specification:
// http://www.barcodeisland.com/codabar.phtml
/*
* JS source adapted from https://github.com/lindell/JsBarcode
*
* The MIT License (MIT)
*
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
const Barcode = require("cards.Barcode.js");
class codabar extends Barcode{
constructor(data, options){
if (/^[0-9\-\$\:\.\+\/]+$/.test(data)) {
data = "A" + data + "A";
}
super(data.toUpperCase(), options);
this.text = this.options.text || this.text.replace(/[A-D]/g, '');
}
valid(){
return /^[A-D][0-9\-\$\:\.\+\/]+[A-D]$/.test(this.data)
}
encode(){
var result = [];
var encodings = this.getEncodings();
for(var i = 0; i < this.data.length; i++){
result.push(encodings[this.data.charAt(i)]);
// for all characters except the last, append a narrow-space ("0")
if (i !== this.data.length - 1) {
result.push("0");
}
}
return {
text: this.text,
data: result.join('')
};
}
getEncodings(){
return {
"0": "101010011",
"1": "101011001",
"2": "101001011",
"3": "110010101",
"4": "101101001",
"5": "110101001",
"6": "100101011",
"7": "100101101",
"8": "100110101",
"9": "110100101",
"-": "101001101",
"$": "101100101",
":": "1101011011",
"/": "1101101011",
".": "1101101101",
"+": "1011011011",
"A": "1011001001",
"B": "1001001011",
"C": "1010010011",
"D": "1010011001"
};
}
}
module.exports = codabar

130
apps/cards/code39.js Normal file
View File

@ -0,0 +1,130 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/Code_39#Encoding
/*
* JS source adapted from https://github.com/lindell/JsBarcode
*
* The MIT License (MIT)
*
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
const Barcode = require("cards.Barcode.js");
class CODE39 extends Barcode {
constructor(data, options){
data = data.toUpperCase();
// Calculate mod43 checksum if enabled
if(options.mod43){
data += getCharacter(mod43checksum(data));
}
super(data, options);
}
encode(){
// First character is always a *
var result = getEncoding("*");
// Take every character and add the binary representation to the result
for(let i = 0; i < this.data.length; i++){
result += getEncoding(this.data[i]) + "0";
}
// Last character is always a *
result += getEncoding("*");
return {
data: result,
text: this.text
};
}
valid(){
return /^[0-9A-Z\-\.\ \$\/\+\%]+$/.test(this.data);
}
}
// All characters. The position in the array is the (checksum) value
var characters = [
"0", "1", "2", "3",
"4", "5", "6", "7",
"8", "9", "A", "B",
"C", "D", "E", "F",
"G", "H", "I", "J",
"K", "L", "M", "N",
"O", "P", "Q", "R",
"S", "T", "U", "V",
"W", "X", "Y", "Z",
"-", ".", " ", "$",
"/", "+", "%", "*"
];
// The decimal representation of the characters, is converted to the
// corresponding binary with the getEncoding function
var encodings = [
20957, 29783, 23639, 30485,
20951, 29813, 23669, 20855,
29789, 23645, 29975, 23831,
30533, 22295, 30149, 24005,
21623, 29981, 23837, 22301,
30023, 23879, 30545, 22343,
30161, 24017, 21959, 30065,
23921, 22385, 29015, 18263,
29141, 17879, 29045, 18293,
17783, 29021, 18269, 17477,
17489, 17681, 20753, 35770
];
// Get the binary representation of a character by converting the encodings
// from decimal to binary
function getEncoding(character){
return getBinary(characterValue(character));
}
function getBinary(characterValue){
return encodings[characterValue].toString(2);
}
function getCharacter(characterValue){
return characters[characterValue];
}
function characterValue(character){
return characters.indexOf(character);
}
function mod43checksum(data){
var checksum = 0;
for(let i = 0; i < data.length; i++){
checksum += characterValue(data[i]);
}
checksum = checksum % 43;
return checksum;
}
module.exports = CODE39;

21
apps/cards/metadata.json Normal file
View File

@ -0,0 +1,21 @@
{
"id": "cards",
"name": "Cards",
"version": "0.01",
"description": "Display loyalty cards",
"icon": "app.png",
"screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_card2.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}],
"tags": "cards",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"cards.app.js","url":"app.js"},
{"name":"cards.settings.js","url":"settings.js"},
{"name":"cards.Barcode.js","url":"Barcode.js"},
{"name":"cards.qrcode.js","url":"qrcode.js"},
{"name":"cards.codabar.js","url":"codabar.js"},
{"name":"cards.code39.js","url":"code39.js"},
{"name":"cards.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"cards.settings.json"}]
}

705
apps/cards/qrcode.js Normal file
View File

@ -0,0 +1,705 @@
/*
* C source adapted from https://github.com/ricmoo/QRCode
*
* The MIT License (MIT)
*
* This library is written and maintained by Richard Moore.
* Major parts were derived from Project Nayuki's library.
*
* Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode)
* Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
var c = E.compiledC(`
// int get_qr(int, int)
typedef signed char __int8_t;
typedef unsigned char __uint8_t;
typedef signed short int __int16_t;
typedef unsigned short int __uint16_t;
typedef signed int __int32_t;
typedef unsigned int __uint32_t;
typedef __int8_t int8_t;
typedef __int16_t int16_t;
typedef __int32_t int32_t;
typedef __uint8_t uint8_t;
typedef __uint16_t uint16_t;
typedef __uint32_t uint32_t;
typedef struct QRCode {
uint8_t version;
uint8_t size;
uint8_t ecc;
uint8_t mode;
uint8_t mask;
uint8_t *modules;
} QRCode;
uint16_t qrcode_getBufferSize(uint8_t version);
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data);
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length);
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y);
static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = {
{ 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372},
{ 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750},
{ 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430},
{ 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040},
};
static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = {
{ 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49},
{ 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25},
{ 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81},
{ 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68},
};
static const uint16_t NUM_RAW_DATA_MODULES[40] = {
208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523,
7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587,
19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648
};
static int max(int a, int b) {
if (a > b) { return a; }
return b;
}
static int abs(int value) {
if (value < 0) { return -value; }
return value;
}
static void *memset(void *s, int c, int n) {
char *arr = (char *)s;
for (int i = 0; i<n; i++) {
arr[i] = c;
}
return arr;
}
static void *memcpy(void *dest, const void *src, int n) {
char *arr1 = (char *)dest;
const char *arr2 = (char *)src;
for (int i = 0; i<n; i++) {
arr1[i] = arr2[i];
}
return arr1;
}
int strlen(const char *s) {
int i = 0;
while (s[i]) ++i;
return i;
}
static int8_t getAlphanumeric(char c) {
if (c >= '0' && c <= '9') { return (c - '0'); }
if (c >= 'A' && c <= 'Z') { return (c - 'A' + 10); }
switch (c) {
case ' ': return 36;
case '$': return 37;
case '%': return 38;
case '*': return 39;
case '+': return 40;
case '-': return 41;
case '.': return 42;
case '/': return 43;
case ':': return 44;
}
return -1;
}
static bool isAlphanumeric(const char *text, uint16_t length) {
while (length != 0) {
if (getAlphanumeric(text[--length]) == -1) { return false; }
}
return true;
}
static bool isNumeric(const char *text, uint16_t length) {
while (length != 0) {
char c = text[--length];
if (c < '0' || c > '9') { return false; }
}
return true;
}
static char getModeBits(uint8_t version, uint8_t mode) {
unsigned int modeInfo = 0x7bbb80a;
if (version > 9) { modeInfo >>= 9; }
if (version > 26) { modeInfo >>= 9; }
char result = 8 + ((modeInfo >> (3 * mode)) & 0x07);
if (result == 15) { result = 16; }
return result;
}
typedef struct BitBucket {
uint32_t bitOffsetOrWidth;
uint16_t capacityBytes;
uint8_t *data;
} BitBucket;
static uint16_t bb_getGridSizeBytes(uint8_t size) {
return (((size * size) + 7) / 8);
}
static uint16_t bb_getBufferSizeBytes(uint32_t bits) {
return ((bits + 7) / 8);
}
static void bb_initBuffer(BitBucket *bitBuffer, uint8_t *data, int32_t capacityBytes) {
bitBuffer->bitOffsetOrWidth = 0;
bitBuffer->capacityBytes = capacityBytes;
bitBuffer->data = data;
memset(data, 0, bitBuffer->capacityBytes);
}
static void bb_initGrid(BitBucket *bitGrid, uint8_t *data, uint8_t size) {
bitGrid->bitOffsetOrWidth = size;
bitGrid->capacityBytes = bb_getGridSizeBytes(size);
bitGrid->data = data;
memset(data, 0, bitGrid->capacityBytes);
}
static void bb_appendBits(BitBucket *bitBuffer, uint32_t val, uint8_t length) {
uint32_t offset = bitBuffer->bitOffsetOrWidth;
for (int8_t i = length - 1; i >= 0; i--, offset++) {
bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7));
}
bitBuffer->bitOffsetOrWidth = offset;
}
static void bb_setBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool on) {
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
uint8_t mask = 1 << (7 - (offset & 0x07));
if (on) {
bitGrid->data[offset >> 3] |= mask;
} else {
bitGrid->data[offset >> 3] &= ~mask;
}
}
static void bb_invertBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool invert) {
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
uint8_t mask = 1 << (7 - (offset & 0x07));
bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0);
if (on ^ invert) {
bitGrid->data[offset >> 3] |= mask;
} else {
bitGrid->data[offset >> 3] &= ~mask;
}
}
static bool bb_getBit(BitBucket *bitGrid, uint8_t x, uint8_t y) {
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
}
static void applyMask(BitBucket *modules, BitBucket *isFunction, uint8_t mask) {
uint8_t size = modules->bitOffsetOrWidth;
for (uint8_t y = 0; y < size; y++) {
for (uint8_t x = 0; x < size; x++) {
if (bb_getBit(isFunction, x, y)) { continue; }
bool invert = 0;
switch (mask) {
case 0: invert = (x + y) % 2 == 0; break;
case 1: invert = y % 2 == 0; break;
case 2: invert = x % 3 == 0; break;
case 3: invert = (x + y) % 3 == 0; break;
case 4: invert = (x / 3 + y / 2) % 2 == 0; break;
case 5: invert = x * y % 2 + x * y % 3 == 0; break;
case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break;
case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break;
}
bb_invertBit(modules, x, y, invert);
}
}
}
static void setFunctionModule(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y, bool on) {
bb_setBit(modules, x, y, on);
bb_setBit(isFunction, x, y, true);
}
static void drawFinderPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
uint8_t size = modules->bitOffsetOrWidth;
for (int8_t i = -4; i <= 4; i++) {
for (int8_t j = -4; j <= 4; j++) {
uint8_t dist = max(abs(i), abs(j));
int16_t xx = x + j, yy = y + i;
if (0 <= xx && xx < size && 0 <= yy && yy < size) {
setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4);
}
}
}
}
static void drawAlignmentPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
for (int8_t i = -2; i <= 2; i++) {
for (int8_t j = -2; j <= 2; j++) {
setFunctionModule(modules, isFunction, x + j, y + i, max(abs(i), abs(j)) != 1);
}
}
}
static void drawFormatBits(BitBucket *modules, BitBucket *isFunction, uint8_t ecc, uint8_t mask) {
uint8_t size = modules->bitOffsetOrWidth;
uint32_t data = ecc << 3 | mask;
uint32_t rem = data;
for (int i = 0; i < 10; i++) {
rem = (rem << 1) ^ ((rem >> 9) * 0x537);
}
data = data << 10 | rem;
data ^= 0x5412;
for (uint8_t i = 0; i <= 5; i++) {
setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0);
}
setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0);
setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0);
setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0);
for (int8_t i = 9; i < 15; i++) {
setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0);
}
for (int8_t i = 0; i <= 7; i++) {
setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0);
}
for (int8_t i = 8; i < 15; i++) {
setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0);
}
setFunctionModule(modules, isFunction, 8, size - 8, true);
}
static void drawVersion(BitBucket *modules, BitBucket *isFunction, uint8_t version) {
int8_t size = modules->bitOffsetOrWidth;
if (version < 7) { return; }
uint32_t rem = version;
for (uint8_t i = 0; i < 12; i++) {
rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
}
uint32_t data = version << 12 | rem;
for (uint8_t i = 0; i < 18; i++) {
bool bit = ((data >> i) & 1) != 0;
uint8_t a = size - 11 + i % 3, b = i / 3;
setFunctionModule(modules, isFunction, a, b, bit);
setFunctionModule(modules, isFunction, b, a, bit);
}
}
static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint8_t version, uint8_t ecc) {
uint8_t size = modules->bitOffsetOrWidth;
for (uint8_t i = 0; i < size; i++) {
setFunctionModule(modules, isFunction, 6, i, i % 2 == 0);
setFunctionModule(modules, isFunction, i, 6, i % 2 == 0);
}
drawFinderPattern(modules, isFunction, 3, 3);
drawFinderPattern(modules, isFunction, size - 4, 3);
drawFinderPattern(modules, isFunction, 3, size - 4);
if (version > 1) {
uint8_t alignCount = version / 7 + 2;
uint8_t step;
if (version != 32) {
step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2;
} else {
step = 26;
}
uint8_t alignPositionIndex = alignCount - 1;
uint8_t alignPosition[alignCount];
alignPosition[0] = 6;
uint8_t l_size = version * 4 + 17;
for (uint8_t i = 0, pos = l_size - 7; i < alignCount - 1; i++, pos -= step) {
alignPosition[alignPositionIndex--] = pos;
}
for (uint8_t i = 0; i < alignCount; i++) {
for (uint8_t j = 0; j < alignCount; j++) {
if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) {
continue;
} else {
drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]);
}
}
}
}
drawFormatBits(modules, isFunction, ecc, 0);
drawVersion(modules, isFunction, version);
}
static void drawCodewords(BitBucket *modules, BitBucket *isFunction, BitBucket *codewords) {
uint32_t bitLength = codewords->bitOffsetOrWidth;
uint8_t *data = codewords->data;
uint8_t size = modules->bitOffsetOrWidth;
uint32_t i = 0;
for (int16_t right = size - 1; right >= 1; right -= 2) {
if (right == 6) { right = 5; }
for (uint8_t vert = 0; vert < size; vert++) {
for (int j = 0; j < 2; j++) {
uint8_t x = right - j;
bool upwards = ((right & 2) == 0) ^ (x < 6);
uint8_t y = upwards ? size - 1 - vert : vert;
if (!bb_getBit(isFunction, x, y) && i < bitLength) {
bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0);
i++;
}
}
}
}
}
static uint32_t getPenaltyScore(BitBucket *modules) {
uint32_t result = 0;
uint8_t size = modules->bitOffsetOrWidth;
for (uint8_t y = 0; y < size; y++) {
bool colorX = bb_getBit(modules, 0, y);
for (uint8_t x = 1, runX = 1; x < size; x++) {
bool cx = bb_getBit(modules, x, y);
if (cx != colorX) {
colorX = cx;
runX = 1;
} else {
runX++;
if (runX == 5) {
result += 3;
} else if (runX > 5) {
result++;
}
}
}
}
for (uint8_t x = 0; x < size; x++) {
bool colorY = bb_getBit(modules, x, 0);
for (uint8_t y = 1, runY = 1; y < size; y++) {
bool cy = bb_getBit(modules, x, y);
if (cy != colorY) {
colorY = cy;
runY = 1;
} else {
runY++;
if (runY == 5) {
result += 3;
} else if (runY > 5) {
result++;
}
}
}
}
uint16_t black = 0;
for (uint8_t y = 0; y < size; y++) {
uint16_t bitsRow = 0, bitsCol = 0;
for (uint8_t x = 0; x < size; x++) {
bool color = bb_getBit(modules, x, y);
if (x > 0 && y > 0) {
bool colorUL = bb_getBit(modules, x - 1, y - 1);
bool colorUR = bb_getBit(modules, x, y - 1);
bool colorL = bb_getBit(modules, x - 1, y);
if (color == colorUL && color == colorUR && color == colorL) {
result += 3;
}
}
bitsRow = ((bitsRow << 1) & 0x7FF) | color;
bitsCol = ((bitsCol << 1) & 0x7FF) | bb_getBit(modules, y, x);
if (x >= 10) {
if (bitsRow == 0x05D || bitsRow == 0x5D0) {
result += 40;
}
if (bitsCol == 0x05D || bitsCol == 0x5D0) {
result += 40;
}
}
if (color) { black++; }
}
}
uint16_t total = size * size;
for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) {
result += 10;
}
return result;
}
static uint8_t rs_multiply(uint8_t x, uint8_t y) {
uint16_t z = 0;
for (int8_t i = 7; i >= 0; i--) {
z = (z << 1) ^ ((z >> 7) * 0x11D);
z ^= ((y >> i) & 1) * x;
}
return z;
}
static void rs_init(uint8_t degree, uint8_t *coeff) {
memset(coeff, 0, degree);
coeff[degree - 1] = 1;
uint16_t root = 1;
for (uint8_t i = 0; i < degree; i++) {
for (uint8_t j = 0; j < degree; j++) {
coeff[j] = rs_multiply(coeff[j], root);
if (j + 1 < degree) {
coeff[j] ^= coeff[j + 1];
}
}
root = (root << 1) ^ ((root >> 7) * 0x11D);
}
}
static void rs_getRemainder(uint8_t degree, uint8_t *coeff, const uint8_t *data, uint8_t length, uint8_t *result, uint8_t stride) {
for (uint8_t i = 0; i < length; i++) {
uint8_t factor = data[i] ^ result[0];
for (uint8_t j = 1; j < degree; j++) {
result[(j - 1) * stride] = result[j * stride];
}
result[(degree - 1) * stride] = 0;
for (uint8_t j = 0; j < degree; j++) {
result[j * stride] ^= rs_multiply(coeff[j], factor);
}
}
}
static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, uint16_t length, uint8_t version) {
int8_t mode = 2;
if (isNumeric((char*)text, length)) {
mode = 0;
bb_appendBits(dataCodewords, 1 << 0, 4);
bb_appendBits(dataCodewords, length, getModeBits(version, 0));
uint16_t accumData = 0;
uint8_t accumCount = 0;
for (uint16_t i = 0; i < length; i++) {
accumData = accumData * 10 + ((char)(text[i]) - '0');
accumCount++;
if (accumCount == 3) {
bb_appendBits(dataCodewords, accumData, 10);
accumData = 0;
accumCount = 0;
}
}
if (accumCount > 0) {
bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1);
}
} else if (isAlphanumeric((char*)text, length)) {
mode = 1;
bb_appendBits(dataCodewords, 1 << 1, 4);
bb_appendBits(dataCodewords, length, getModeBits(version, 1));
uint16_t accumData = 0;
uint8_t accumCount = 0;
for (uint16_t i = 0; i < length; i++) {
accumData = accumData * 45 + getAlphanumeric((char)(text[i]));
accumCount++;
if (accumCount == 2) {
bb_appendBits(dataCodewords, accumData, 11);
accumData = 0;
accumCount = 0;
}
}
if (accumCount > 0) {
bb_appendBits(dataCodewords, accumData, 6);
}
} else {
bb_appendBits(dataCodewords, 1 << 2, 4);
bb_appendBits(dataCodewords, length, getModeBits(version, 2));
for (uint16_t i = 0; i < length; i++) {
bb_appendBits(dataCodewords, (char)(text[i]), 8);
}
}
return mode;
}
static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data) {
uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1];
uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1];
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
uint8_t blockEccLen = totalEcc / numBlocks;
uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks;
uint8_t shortBlockLen = moduleCount / 8 / numBlocks;
uint8_t shortDataBlockLen = shortBlockLen - blockEccLen;
uint8_t result[data->capacityBytes];
memset(result, 0, sizeof(result));
uint8_t coeff[blockEccLen];
rs_init(blockEccLen, coeff);
uint16_t offset = 0;
uint8_t *dataBytes = data->data;
for (uint8_t i = 0; i < shortDataBlockLen; i++) {
uint16_t index = i;
uint8_t stride = shortDataBlockLen;
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
result[offset++] = dataBytes[index];
if (blockNum == numShortBlocks) { stride++; }
index += stride;
}
}
{
uint16_t index = shortDataBlockLen * (numShortBlocks + 1);
uint8_t stride = shortDataBlockLen;
for (uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) {
result[offset++] = dataBytes[index];
if (blockNum == 0) { stride++; }
index += stride;
}
}
uint8_t blockSize = shortDataBlockLen;
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
if (blockNum == numShortBlocks) { blockSize++; }
rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks);
dataBytes += blockSize;
}
memcpy(data->data, result, data->capacityBytes);
data->bitOffsetOrWidth = moduleCount;
}
static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0);
uint16_t qrcode_getBufferSize(uint8_t version) {
return bb_getGridSizeBytes(4 * version + 17);
}
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) {
uint8_t size = version * 4 + 17;
qrcode->version = version;
qrcode->size = size;
qrcode->ecc = ecc;
qrcode->modules = modules;
uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03;
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1];
struct BitBucket codewords;
uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)];
bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes));
int8_t mode = encodeDataCodewords(&codewords, data, length, version);
if (mode < 0) { return -1; }
qrcode->mode = mode;
uint32_t padding = (dataCapacity * 8) - codewords.bitOffsetOrWidth;
if (padding > 4) { padding = 4; }
bb_appendBits(&codewords, 0, padding);
bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8);
for (uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) {
bb_appendBits(&codewords, padByte, 8);
}
BitBucket modulesGrid;
bb_initGrid(&modulesGrid, modules, size);
BitBucket isFunctionGrid;
uint8_t isFunctionGridBytes[bb_getGridSizeBytes(size)];
bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size);
drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits);
performErrorCorrection(version, eccFormatBits, &codewords);
drawCodewords(&modulesGrid, &isFunctionGrid, &codewords);
uint8_t mask = 0;
int32_t minPenalty = (2147483647);
for (uint8_t i = 0; i < 8; i++) {
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i);
applyMask(&modulesGrid, &isFunctionGrid, i);
int penalty = getPenaltyScore(&modulesGrid);
if (penalty < minPenalty) {
mask = i;
minPenalty = penalty;
}
applyMask(&modulesGrid, &isFunctionGrid, i);
}
qrcode->mask = mask;
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask);
applyMask(&modulesGrid, &isFunctionGrid, mask);
return 0;
}
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data) {
return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data));
}
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) {
if (x >= qrcode->size || y >= qrcode->size) {
return false;
}
uint32_t offset = y * qrcode->size + x;
return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
}
int get_qr (char *string, uint8_t *qrcodeBitmap) {
// The structure to manage the QR code
QRCode qrcode;
// Allocate a chunk of memory to store the QR code
uint8_t qrcodeBytes[qrcode_getBufferSize(3)];
qrcode_initText(&qrcode, qrcodeBytes, 3, 0, string);
for (uint8_t y = 0; y < qrcode.size; y++) {
for (uint8_t x = 0; x < qrcode.size; x++) {
qrcodeBitmap[x + y * qrcode.size] = qrcode_getModule(&qrcode, x, y);
}
}
return qrcode.size;
}
`);
function getBinaryQR (value) {
var qrcodeBitmap = new Uint8Array(850);
var flatValue = Uint8Array(E.toArrayBuffer(E.toFlatString(value ,0)));
var valueAddr = E.getAddressOf(flatValue, true);
var qrAddr = E.getAddressOf(qrcodeBitmap, true);
if (valueAddr == 0 || qrAddr == 0) {
console.log ("Failed to get flat arrays..");
//return;
}
var qrsize = c.get_qr(valueAddr, qrAddr);
return { data: qrcodeBitmap, size: qrsize };
}
module.exports = getBinaryQR;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

19
apps/cards/settings.js Normal file
View File

@ -0,0 +1,19 @@
(function(back) {
var settings = require("Storage").readJSON("cards.settings.json",1)||{};
function updateSettings() {
require("Storage").writeJSON("cards.settings.json", settings);
}
var mainmenu = {
"" : { "title" : "Cards" },
"< Back" : back,
/*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?/*LANG*/"Yes":/*LANG*/"No" },
/*LANG*/"Use 'Today',..." : {
value : !!settings.useToday,
onchange: v => {
settings.useToday = v;
updateSettings();
}
},
};
E.showMenu(mainmenu);
})

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Handle missing settings (e.g. first-install)

View File

@ -1,5 +1,5 @@
(() => {
const chargingRotation = 0 | require('Storage').readJSON("chargerot.settings.json").rotate;
const chargingRotation = 0 | (require('Storage').readJSON("chargerot.settings.json",1)||{}).rotate;
const defaultRotation = 0 | require('Storage').readJSON("setting.json").rotate;
if (Bangle.isCharging()) g.setRotation(chargingRotation&3,chargingRotation>>2).clear();
Bangle.on('charging', (charging) => {

View File

@ -1,7 +1,7 @@
{
"id": "chargerot",
"name": "Charge LCD rotation",
"version": "0.01",
"version": "0.02",
"description": "When charging, this app can rotate your screen and revert it when unplugged. Made for all sort of cradles.",
"icon": "icon.png",
"tags": "battery",

View File

@ -4,4 +4,5 @@
0.04: On 2v18+ firmware, we can now stop swipe events from being handled by other apps
eg. when a clockinfo is selected, swipes won't affect swipe-down widgets
0.05: Reported image for battery is now transparent (2v18+)
0.06: When >1 clockinfo, swiping one back tries to ensure they don't display the same thing
0.06: When >1 clockinfo, swiping one back tries to ensure they don't display the same thing
0.07: Developer tweak: clkinfo load errors are emitted

View File

@ -141,7 +141,7 @@ exports.load = function() {
if(b) b.items = b.items.concat(a.items);
else menu = menu.concat(a);
} catch(e){
console.log("Could not load clock info "+E.toJS(fn));
console.log("Could not load clock info "+E.toJS(fn)+": "+e);
}
});

View File

@ -1,7 +1,7 @@
{ "id": "clock_info",
"name": "Clock Info Module",
"shortName": "Clock Info",
"version":"0.06",
"version":"0.07",
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
"icon": "app.png",
"type": "module",

View File

@ -0,0 +1,14 @@
# Clock Switcher
This switches the default clock.
The idea is that you can use this app in combination with e.g. the
[Pattern Launcher](?q=ptlaunch) as a quick toggle, instead of navigating through
the settings menu.
## Usage
Load the app to switch to your next installed clock.
## Creator
Richard de Boer (rigrig)

23
apps/clockswitch/app.js Normal file
View File

@ -0,0 +1,23 @@
const storage = require('Storage');
const clocks = storage.list(/\.info$/)
.map(app => {
const 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)
.map(app => app.src);
if (clocks.length<1) {
E.showAlert(/*LANG*/"No clocks found!", "Clock Switcher")
.then(load);
} else if (clocks.length<2) {
E.showAlert(/*LANG*/"Nothing to do:\nOnly one clock installed!", "Clock Switcher")
.then(load);
} else {
let settings = storage.readJSON('setting.json',true)||{clock:null};
const old = clocks.indexOf(settings.clock),
next = (old+1)%clocks.length;
settings.clock = clocks[next];
storage.writeJSON('setting.json', settings);
setTimeout(load, 100); // storage.writeJSON needs some time to complete
}

1
apps/clockswitch/icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AClhCyoAYsIwuF4IwtF4Qxqw2GF4mG1YsqAAeF1eyAAIteFhAvHGLeGwouLR4IuEGDJcGwooBAweH6/X6wwGGKtbKownB640C1gGCAAQwZLgotDF4WG6wuFMZAuVw2yEgqLCABIuD1eGF6eGExYwLw4bCF1BuCDgWFdaGFRgwAJlb0HJogvPdQoAKq0AlYJG1YwDRr+sgEAL4wABwxgNF4ZeSqwLIMAYvNwpebAAOFSBgMCw7sQLxSQORwZLKLw4OLSBlbBgWyLznX2RfPLqBeM6/WcQYvZldbrYvN64jDF7rRNF7qPDGBqPLd6YxDGBTvQPpowQ1YvLGAeHF54wDlYMIwwvPwovQGAIuJ6+FdxSQF1YwRABKONF4mGF7aONAANbMDpeDRxRgFsOyFy+yP4gvLMAiRX6yNDwouMGDYuELxyRGwySS2QuUMAr0SdQguSGA+G1gtMLgguUGAQxFwuH1aWE2QsBwoQEFyzEHAB+FFzAwCMQoALFrRiRwwtefI5mCQwIslAH4A/AFw"))

BIN
apps/clockswitch/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,14 @@
{ "id": "clockswitch",
"name": "Clock Switcher",
"shortName":"Switch Clock",
"version":"0.01",
"description": "Switch to the next installed clock",
"icon": "icon.png",
"tags": "tool",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"clockswitch.app.js","url":"app.js"},
{"name":"clockswitch.img","url":"icon.js","evaluate":true}
]
}

View File

@ -10,3 +10,4 @@
0.28: More config options for cleaner look, enabled fast loading
0.29: Fixed a bug that would leave old font files in storage.
0.30: Added options to show widgets and date on twist and tap. New fonts.
0.31: Bugfix, no more freeze.

View File

@ -34,9 +34,12 @@
extrasTimeout = undefined;
hideExtras();
}, 5000);
extrasShown = false;
};
let drawExtras = function() { //draw date, day of the week and widgets
let date = new Date();
g.reset();
g.clearRect(0, 138, g.getWidth() - 1, 176);
g.setFont("Teletext10x18Ascii").setFontAlign(0, 1);
if (settings.weekday) g.drawString(require("locale").dow(date).toUpperCase(), g.getWidth() / 2, g.getHeight() - 18);
if (settings.date) g.drawString(require('locale').date(date, 1), g.getWidth() / 2, g.getHeight());
@ -45,21 +48,23 @@
};
let hideExtras = function() {
if (extrasTimeout) clearTimeout(extrasTimeout);
extrasTimeout = undefined; //NEW
g.reset();
g.clearRect(0, 138, g.getWidth() - 1, 176);
require("widget_utils").hide();
extrasShown = false;
extrasShown = false; ///NEW
};
let draw = function() {
if (drawTimeout) clearTimeout(drawTimeout); //NEW
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
let date = new Date();
g.reset();
if (extrasShown) drawExtras();
else hideExtras();
require('contourclock').drawClock(settings.fontIndex);
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
};
if (settings.hideWhenLocked) {
onLock = locked => {
@ -83,6 +88,8 @@
Bangle.removeListener('twist', showExtras);
if (drawTimeout) clearTimeout(drawTimeout);
if (extrasTimeout) clearTimeout(extrasTimeout);
drawTimeout = undefined;
extrasTimeout = undefined;
if (settings.hideWhenLocked) require("widget_utils").show();
g.reset();
g.clear();
@ -91,7 +98,7 @@
g.clear();
if (settings.widgets) {
Bangle.loadWidgets();
Bangle.drawWidgets();
setTimeout(Bangle.drawWidgets,0); //NEW
}
draw();
}

View File

@ -1,7 +1,7 @@
{ "id": "contourclock",
"name": "Contour Clock",
"shortName" : "Contour Clock",
"version":"0.30",
"version":"0.31",
"icon": "app.png",
"readme": "README.md",
"description": "A Minimalist clockface with large Digits.",

View File

@ -5,3 +5,6 @@
0.05: Now scrolls text when string gets longer than screen width.
0.06: The code is now more reliable and the input snappier. Widgets will be drawn if present.
0.07: Settings for display colors
0.08: Catch and discard swipe events on fw2v19 and up (as well as some cutting
edge 2v18 ones), allowing compatability with the Back Swipe app.
0.09: Fix colors settings, where color was stored as string instead of the expected int.

View File

@ -103,6 +103,133 @@ exports.input = function(options) {
initDraw();
//setTimeout(initDraw, 0); // So Bangle.appRect reads the correct environment. It would draw off to the side sometimes otherwise.
let dragHandlerDB = function(event) {
"ram";
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
// Choose character by draging along red rectangle at bottom of screen
if (event.y >= ( (R.y+R.h) - 12 )) {
// Translate x-position to character
if (event.x < ABCPADDING) { abcHL = 0; }
else if (event.x >= 176-ABCPADDING) { abcHL = 25; }
else { abcHL = Math.floor((event.x-ABCPADDING)/6); }
// Datastream for development purposes
//print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev));
// Unmark previous character and mark the current one...
// Handling switching between letters and numbers/punctuation
if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
if (abcHL != abcHLPrev) {
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2);
}
// Print string at top of screen
if (event.b == 0) {
text = text + ABC.charAt(abcHL);
updateTopString();
// Autoswitching letter case
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL);
}
// Update previous character to current one
abcHLPrev = abcHL;
typePrev = 'abc';
}
// 12345678901234567890
// Choose number or puctuation by draging on green rectangle
else if ((event.y < ( (R.y+R.h) - 12 )) && (event.y > ( (R.y+R.h) - 52 ))) {
// Translate x-position to character
if (event.x < NUMPADDING) { numHL = 0; }
else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; }
else { numHL = Math.floor((event.x-NUMPADDING)/6); }
// Datastream for development purposes
//print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev));
// Unmark previous character and mark the current one...
// Handling switching between letters and numbers/punctuation
if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
if (numHL != numHLPrev) {
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4);
}
// Print string at top of screen
if (event.b == 0) {
g.setColor(HLCOLOR);
// Backspace if releasing before list of numbers/punctuation
if (event.x < NUMPADDING) {
// show delete sign
showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4);
delSpaceLast = 1;
text = text.slice(0, -1);
updateTopString();
//print(text);
}
// Append space if releasing after list of numbers/punctuation
else if (event.x > (R.x+R.w)-NUMPADDING) {
//show space sign
showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4);
delSpaceLast = 1;
text = text + ' ';
updateTopString();
//print(text);
}
// Append selected number/punctuation
else {
text = text + NUMHIDDEN.charAt(numHL);
updateTopString();
// Autoswitching letter case
if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase();
}
}
// Update previous character to current one
numHLPrev = numHL;
typePrev = 'num';
}
// Make a space or backspace by swiping right or left on screen above green rectangle
else if (event.y > 20+4) {
if (event.b == 0) {
g.setColor(HLCOLOR);
if (event.x < (R.x+R.w)/2) {
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
// show delete sign
showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4);
delSpaceLast = 1;
// Backspace and draw string upper right corner
text = text.slice(0, -1);
updateTopString();
if (text.length==0) changeCase(abcHL);
//print(text, 'undid');
}
else {
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
//show space sign
showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4);
delSpaceLast = 1;
// Append space and draw string upper right corner
text = text + NUMHIDDEN.charAt(0);
updateTopString();
//print(text, 'made space');
}
}
}
};
let catchSwipe = ()=>{
E.stopEventPropagation&&E.stopEventPropagation();
};
function changeCase(abcHL) {
if (settings.uppercase) return;
g.setColor(BGCOLOR);
@ -119,131 +246,12 @@ exports.input = function(options) {
mode: 'custom',
back: ()=>{
Bangle.setUI();
Bangle.prependListener&&Bangle.removeListener('swipe', catchSwipe); // Remove swipe lister if it was added with `Bangle.prependListener()` (fw2v19 and up).
g.clearRect(Bangle.appRect);
resolve(text);
},
drag: function(event) {
"ram";
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
// Choose character by draging along red rectangle at bottom of screen
if (event.y >= ( (R.y+R.h) - 12 )) {
// Translate x-position to character
if (event.x < ABCPADDING) { abcHL = 0; }
else if (event.x >= 176-ABCPADDING) { abcHL = 25; }
else { abcHL = Math.floor((event.x-ABCPADDING)/6); }
// Datastream for development purposes
//print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev));
// Unmark previous character and mark the current one...
// Handling switching between letters and numbers/punctuation
if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
if (abcHL != abcHLPrev) {
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2);
}
// Print string at top of screen
if (event.b == 0) {
text = text + ABC.charAt(abcHL);
updateTopString();
// Autoswitching letter case
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL);
}
// Update previous character to current one
abcHLPrev = abcHL;
typePrev = 'abc';
}
// 12345678901234567890
// Choose number or puctuation by draging on green rectangle
else if ((event.y < ( (R.y+R.h) - 12 )) && (event.y > ( (R.y+R.h) - 52 ))) {
// Translate x-position to character
if (event.x < NUMPADDING) { numHL = 0; }
else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; }
else { numHL = Math.floor((event.x-NUMPADDING)/6); }
// Datastream for development purposes
//print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev));
// Unmark previous character and mark the current one...
// Handling switching between letters and numbers/punctuation
if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
if (numHL != numHLPrev) {
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4);
}
// Print string at top of screen
if (event.b == 0) {
g.setColor(HLCOLOR);
// Backspace if releasing before list of numbers/punctuation
if (event.x < NUMPADDING) {
// show delete sign
showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4);
delSpaceLast = 1;
text = text.slice(0, -1);
updateTopString();
//print(text);
}
// Append space if releasing after list of numbers/punctuation
else if (event.x > (R.x+R.w)-NUMPADDING) {
//show space sign
showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4);
delSpaceLast = 1;
text = text + ' ';
updateTopString();
//print(text);
}
// Append selected number/punctuation
else {
text = text + NUMHIDDEN.charAt(numHL);
updateTopString();
// Autoswitching letter case
if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase();
}
}
// Update previous character to current one
numHLPrev = numHL;
typePrev = 'num';
}
// Make a space or backspace by swiping right or left on screen above green rectangle
else if (event.y > 20+4) {
if (event.b == 0) {
g.setColor(HLCOLOR);
if (event.x < (R.x+R.w)/2) {
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
// show delete sign
showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4);
delSpaceLast = 1;
// Backspace and draw string upper right corner
text = text.slice(0, -1);
updateTopString();
if (text.length==0) changeCase(abcHL);
//print(text, 'undid');
}
else {
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
//show space sign
showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4);
delSpaceLast = 1;
// Append space and draw string upper right corner
text = text + NUMHIDDEN.charAt(0);
updateTopString();
//print(text, 'made space');
}
}
}
}
drag: dragHandlerDB,
});
Bangle.prependListener&&Bangle.prependListener('swipe', catchSwipe); // Intercept swipes on fw2v19 and later. Should not break on older firmwares.
});
};

View File

@ -1,6 +1,6 @@
{ "id": "dragboard",
"name": "Dragboard",
"version":"0.07",
"version":"0.09",
"description": "A library for text input via swiping keyboard",
"icon": "app.png",
"type":"textinput",

View File

@ -21,7 +21,7 @@
value: settings[key] == color,
onchange: () => {
if (color >= 0) {
settings[key] = color;
settings[key] = parseInt(color);
} else {
delete settings[key];
}

View File

@ -1 +1,4 @@
0.01: New App based on dragboard, but with a U shaped drag area
0.02: Catch and discard swipe events on fw2v19 and up (as well as some cutting
edge 2v18 ones), allowing compatability with the Back Swipe app.
0.03: Fix "Uncaught Error: Unhandled promise rejection: ReferenceError: "dragHandlerDB" is not defined"

View File

@ -104,45 +104,53 @@ exports.input = function(options) {
}
}
let dragHandlerUB = function(event) {
"ram";
// drag on middle bottom rectangle
if (event.x > MIDPADDING - 2 && event.x < (R.x2-MIDPADDING + 2) && event.y >= ( (R.y2) - 12 )) {
moveCharPos(MIDDLE, event.b == 0, (event.x-middleStart)/(middleWidth/MIDDLE.length));
}
// drag on left or right rectangle
else if (event.y > R.y && (event.x < MIDPADDING-2 || event.x > (R.x2-MIDPADDING + 2))) {
moveCharPos(event.x<MIDPADDING-2 ? LEFT : RIGHT, event.b == 0, (event.y-topStart)/((R.y2 - topStart)/vLength));
}
// drag on top rectangle for number or punctuation
else if ((event.y < ( (R.y2) - 12 )) && (event.y > ( (R.y2) - 52 ))) {
moveCharPos(NUM, event.b == 0, (event.x-NUMPADDING)/6);
}
// Make a space or backspace by tapping right or left on screen above green rectangle
else if (event.y > R.y && event.b == 0) {
if (event.x < (R.x2)/2) {
showChars('<-');
text = text.slice(0, -1);
} else {
//show space sign
showChars('->');
text += ' ';
}
prevChar = null;
updateTopString();
}
};
let catchSwipe = ()=>{
E.stopEventPropagation&&E.stopEventPropagation();
};
return new Promise((resolve,reject) => {
// Interpret touch input
Bangle.setUI({
mode: 'custom',
back: ()=>{
Bangle.setUI();
Bangle.prependListener&&Bangle.removeListener('swipe', catchSwipe); // Remove swipe lister if it was added with `Bangle.prependListener()` (fw2v19 and up).
g.clearRect(Bangle.appRect);
resolve(text);
},
drag: function(event) {
"ram";
// drag on middle bottom rectangle
if (event.x > MIDPADDING - 2 && event.x < (R.x2-MIDPADDING + 2) && event.y >= ( (R.y2) - 12 )) {
moveCharPos(MIDDLE, event.b == 0, (event.x-middleStart)/(middleWidth/MIDDLE.length));
}
// drag on left or right rectangle
else if (event.y > R.y && (event.x < MIDPADDING-2 || event.x > (R.x2-MIDPADDING + 2))) {
moveCharPos(event.x<MIDPADDING-2 ? LEFT : RIGHT, event.b == 0, (event.y-topStart)/((R.y2 - topStart)/vLength));
}
// drag on top rectangle for number or punctuation
else if ((event.y < ( (R.y2) - 12 )) && (event.y > ( (R.y2) - 52 ))) {
moveCharPos(NUM, event.b == 0, (event.x-NUMPADDING)/6);
}
// Make a space or backspace by tapping right or left on screen above green rectangle
else if (event.y > R.y && event.b == 0) {
if (event.x < (R.x2)/2) {
showChars('<-');
text = text.slice(0, -1);
} else {
//show space sign
showChars('->');
text += ' ';
}
prevChar = null;
updateTopString();
}
}
drag: dragHandlerUB
});
Bangle.prependListener&&Bangle.prependListener('swipe', catchSwipe); // Intercept swipes on fw2v19 and later. Should not break on older firmwares.
R = Bangle.appRect;
MIDPADDING = R.x + 35;

View File

@ -1,6 +1,6 @@
{ "id": "draguboard",
"name": "DragUboard",
"version":"0.01",
"version":"0.03",
"description": "A library for text input via swiping U-shaped keyboard.",
"icon": "app.png",
"type":"textinput",

View File

@ -21,7 +21,7 @@
value: settings[key] == color,
onchange: () => {
if (color >= 0) {
settings[key] = color;
settings[key] = parseInt(color);
} else {
delete settings[key];
}

3
apps/edgeclk/ChangeLog Normal file
View File

@ -0,0 +1,3 @@
0.01: Initial release.
0.02: Fix reset of progress bars on midnight. Fix display of 100k+ steps.
0.03: Added option to display weather.

31
apps/edgeclk/README.md Normal file
View File

@ -0,0 +1,31 @@
# Edge Clock
![Screenshot](screenshot.png)
![Screenshot](screenshot2.png)
![Screenshot](screenshot3.png)
![Screenshot](screenshot4.png)
Tinxx presents you a clock with as many straight edges as possible to allow for a crisp look and perfect readability.
It comes with a custom font to display weekday, date, time, and steps. Also displays battery percentage while charging.
There are three progress bars that indicate day of the week, time of the day, and daily step goal.
The watch face is monochrome and allows for applying your favorite color scheme.
The appearance is highly configurable. In the settings menu you can:
- De-/activate a buzz when the charger is connected while the watch face is active.
- Decide if month or day should be displayed first.
- Switch between 24h and 12h clock.
- Hide or display seconds.*
- Show AM/PM in place of the seconds.
- Show weather temperature and icon in place of the seconds.
- Set the daily step goal.
- En- or disable the individual progress bars.
- Set if your week should start with Monday or Sunday (for week progress bar).
*) Hiding seconds should further reduce power consumption as the draw interval is prolonged as well.
The clock implements Fast Loading for faster switching to and fro.
## Contributors
- [tinxx](https://github.com/tinxx)
- [peerdavid](https://github.com/peerdavid)

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgMgBAoFEuADCgP8sAFD/wLE/wXDCIIjFAAv/ABQRF5fegEPgfe5UbgEJgVS5ebBYMyr36BYdC7YXEGq4AFj8f/ED8f+ApHjAoMHjkA8HjxwFIgAFCC4IFJjk4AoodEAogXBAoI1BDoYFGL5Z3XmHv33whkfuAFE/Fgw0whuD/Fjz0wh/fuALCh/Y/Fv30wgOf7AFE"))

342
apps/edgeclk/app.js Normal file
View File

@ -0,0 +1,342 @@
{
/* Configuration
------------------------------------------------------------------------------*/
const settings = Object.assign({
buzzOnCharge: true,
monthFirst: true,
twentyFourH: true,
showAmPm: false,
showSeconds: true,
showWeather: false,
stepGoal: 10000,
stepBar: true,
weekBar: true,
mondayFirst: true,
dayBar: true,
}, require('Storage').readJSON('edgeclk.settings.json', true) || {});
/* Runtime Variables
------------------------------------------------------------------------------*/
let startTimeout;
let drawInterval;
let lcdPower = true;
let charging = Bangle.isCharging();
const font = atob('AA////wDwDwDwD////AAAAAAAAwAwA////AAAAAAAA8/8/wzwzwzwz/z/zAAAA4H4HwDxjxjxj////AAAA/w/wAwAwD/D/AwAwAAAA/j/jxjxjxjxjx/x/AAAA////xjxjxjxjx/x/AAAAwAwAwAwAwA////AAAAAA////xjxjxjxj////AAAA/j/jxjxjxjxj////AAAAAAAAAAMMMMAAAAAAAAAAAAAAABMOMMAAAAAAAAAABgBgDwDwGYGYMMMMAAAAAAGYGYGYGYGYGYAAAAAAMMMMGYGYDwDwBgBgAAAA4A4Ax7x7xgxg/g/gAAAA//gBv9shshv9gF/7AAAA////wwwwwwww////AAAA////xjxjxjxj////AAAA////wDwDwDwD4H4HAAAA////wDwDwD4Hf+P8AAAA////xjxjxjxjwDwDAAAA////xgxgxgxgwAwAAAAA////wDwDwzwz4/4/AAAA////BgBgBgBg////AAAAAAwDwD////wDwDAAAAAAAAwPwPwDwD////AAAAAA////DwH4OccO4HwDAAAA////ADADADADADADAAAA////YAGAGAYA////AAAA////MADAAwAM////AAAA////wDwDwDwD////AAAA////xgxgxgxg/g/gAAAA/+/+wGwOwOwO////AAAA////xgxgxwx8/v/jAAAA/j/jxjxjxjxjx/x/AAAAwAwAwA////wAwAwAAAAA////ADADADAD////AAAA/w/8AOAHAHAO/8/wAAAA////AGAYAYAG////AAAAwD4PecH4H4ec4PwDAAAAwA4AeBH/H/eA4AwAAAAAwPwfw7xzzj3D+D8DAAAAAAAAAAAA////wDAAAAAAAAAABgBgBgBgAAAAAAAAAAwD////AAAAAAAAAAAAAwDwPA8A8APADwAwAAAAAAAAAAAAAAAAAAAAAA');
const iconSize = [19, 26];
const plugIcon = atob('ExoBBxwA44AccAOOAHHAf/8P/+H//D//h//w//4P/4H/8B/8Af8ABwAA4AAcAAOAAHAADgABwAA4AAcAAOAAHAA=');
const stepIcon1 = atob('ExoBAfAAPgAHwAD4AB8AAAAB/wD/8D//Bn9wz+cZ/HM/hmfwAP4AAAAD+AD/gBxwB48A4OA8HgcBwcAcOAOGADA=');
const stepIcon2 = atob('ExoBAfAAPgMHwfD4dx8ccAcH/8B/8Af8AH8AD+AB/AA/gAfwAP4AAAAD+AD/gBxwB48A4OA8HgcBwcAcOAOGADA=');
/* Draw Functions
------------------------------------------------------------------------------*/
const drawAll = function () {
const date = new Date();
drawDate(date);
if (settings.showSeconds) drawSecs(date);
drawTime(date);
drawLower();
};
const drawLower = function (stepsOnlyCount) {
if (charging) {
drawCharge();
} else {
drawSteps(stepsOnlyCount);
}
drawWeather();
};
const drawWeather = function () {
if (!settings.showWeather){
return;
}
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
g.setFontAlign(1, 1); // right bottom
try{
const weather = require('weather');
const w = weather.get();
let temp = parseInt(w.temp-273.15);
temp = temp < 0 ? '\\' + String(temp*-1) : String(temp);
g.drawString(temp, g.getWidth()-40, g.getHeight() - 1, true);
// clear icon area in case weather condition changed
g.clearRect(g.getWidth()-40, g.getHeight()-30, g.getWidth(), g.getHeight());
weather.drawIcon(w, g.getWidth()-20, g.getHeight()-15, 14);
} catch(e) {
g.drawString("???", g.getWidth()-3, g.getHeight() - 1, true);
}
};
const drawDate = function (date) {
const top = 30;
g.reset();
// weekday
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
g.setFontAlign(-1, -1); // left top
g.drawString(date.toString().slice(0,3).toUpperCase(), 0, top + 12, true);
// date
g.setFontAlign(1, -1); // right top
// Note: to save space first and last two lines of ASCII are left out.
// That is why '-' is assigned to '\' and ' ' (space) to '_'.
if (settings.monthFirst) {
g.drawString((date.getMonth()+1).toString().padStart(2, '_')
+ '\\'
+ date.getDate().toString().padStart(2, 0),
g.getWidth(), top + 12, true);
} else {
g.drawString('_'
+ date.getDate().toString().padStart(2, 0)
+ '\\'
+ (date.getMonth()+1).toString(),
g.getWidth(), top + 12, true);
}
// line/progress bar
if (settings.weekBar) {
let weekday = date.getDay();
if (settings.mondayFirst) {
if (weekday === 0) { weekday = 7; }
} else {
weekday += 1;
}
drawBar(top, weekday/7);
} else {
drawLine(top);
}
};
const drawTime = function (date) {
const top = 72;
g.reset();
const h = date.getHours();
g.setFontCustom(font, 48, 10, 1024 + 12); // triple size (2<<9)
g.setFontAlign(-1, -1); // left top
g.drawString((settings.twentyFourH ? h : (h % 12 || 12)).toString().padStart(2, 0),
0, top+12, true);
g.setFontAlign(0, -1); // center top
g.drawString(':', g.getWidth()/2, top+12, false);
const m = date.getMinutes();
g.setFontAlign(1, -1); // right top
g.drawString(m.toString().padStart(2, 0),
g.getWidth(), top+12, true);
if (settings.showAmPm) {
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
g.setFontAlign(1, 1); // right bottom
g.drawString(h < 12 ? 'AM' : 'PM', g.getWidth(), g.getHeight() - 1, true);
}
if (settings.dayBar) {
drawBar(top, (h*60+m)/1440);
} else {
drawLine(top);
}
};
const drawSecs = function (date) {
g.reset();
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
g.setFontAlign(1, 1); // right bottom
g.drawString(date.getSeconds().toString().padStart(2, 0), g.getWidth(), g.getHeight() - 1, true);
};
const drawSteps = function (onlyCount) {
g.reset();
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
g.setFontAlign(-1, 1); // left bottom
const steps = Bangle.getHealthStatus('day').steps;
const toKSteps = settings.showWeather ? 1000 : 100000;
g.drawString((steps < toKSteps ? steps.toString() : ((steps / 1000).toFixed(0) + 'K')).padEnd(5, '_'),
iconSize[0] + 6, g.getHeight() - 1, true);
if (onlyCount === true) {
return;
}
const progress = steps / settings.stepGoal;
if (settings.stepBar) {
drawBar(g.getHeight() - 38, progress);
} else {
drawLine(g.getHeight() - 38);
}
// icon
if (progress < 1) {
g.drawImage(stepIcon1, 0, g.getHeight() - iconSize[1]);
} else {
g.drawImage(stepIcon2, 0, g.getHeight() - iconSize[1]);
}
};
const drawCharge = function () {
g.reset();
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
g.setFontAlign(-1, 1); // left bottom
const charge = E.getBattery();
g.drawString(charge.toString().padEnd(5, '_'), iconSize[0] + 6, g.getHeight() - 1, true);
drawBar(g.getHeight() - 38, charge / 100);
g.drawImage(plugIcon, 0, g.getHeight() - 26);
};
const drawBar = function (top, progress) {
// draw frame
g.drawRect(0, top, g.getWidth() - 1, top + 5);
g.drawRect(1, top + 1, g.getWidth() - 2, top + 4);
// clear bar area
g.clearRect(2, top + 2, g.getWidth() - 3, top + 3);
// draw bar
const barLen = progress >= 1 ? g.getWidth() : (g.getWidth() - 4) * progress;
if (barLen < 1) return;
g.drawLine(2, top + 2, barLen + 2, top + 2);
g.drawLine(2, top + 3, barLen + 2, top + 3);
};
const drawLine = function (top) {
const width = g.getWidth();
g.drawLine(0, top + 2, width, top + 2);
g.drawLine(0, top + 3, width, top + 3);
};
/* Event Handlers
------------------------------------------------------------------------------*/
const onSecondInterval = function () {
const date = new Date();
drawSecs(date);
if (date.getSeconds() === 0) {
onMinuteInterval();
}
};
const onMinuteInterval = function () {
const date = new Date();
drawTime(date);
drawLower(true);
};
const onMinuteIntervalStarter = function () {
drawInterval = setInterval(onMinuteInterval, 60000);
startTimeout = null;
onMinuteInterval();
};
const onLcdPower = function (on) {
lcdPower = on;
if (on) {
drawAll();
startTimers();
} else {
stopTimers();
}
};
const onMidnight = function () {
if (!lcdPower) return;
drawDate(new Date());
// Lower part (steps/charge) will be updated every minute.
// However, to save power while on battery only step count will get updated.
// This will update icon and progress bar as well:
if (!charging) drawSteps();
drawWeather();
};
const onHealth = function () {
if (!lcdPower || charging) return;
// This will update progress bar and icon:
drawSteps();
drawWeather();
};
const onLock = function (locked) {
if (locked) return;
drawLower();
};
const onCharging = function (isCharging) {
charging = isCharging;
if (isCharging && settings.buzzOnCharge) Bangle.buzz();
if (!lcdPower) return;
drawLower();
};
/* Lifecycle Functions
------------------------------------------------------------------------------*/
const registerEvents = function () {
// This is for original Bangle.js; version two has always-on display:
Bangle.on('lcdPower', onLcdPower);
// Midnight event is triggered when health data is reset and a new day begins:
Bangle.on('midnight', onMidnight);
// Health data is published via 10 mins interval:
Bangle.on('health', onHealth);
// Lock event signals screen (un)lock:
Bangle.on('lock', onLock);
// Charging event signals when charging status changes:
Bangle.on('charging', onCharging);
};
const deregisterEvents = function () {
Bangle.removeListener('lcdPower', onLcdPower);
Bangle.removeListener('midnight', onMidnight);
Bangle.removeListener('health', onHealth);
Bangle.removeListener('lock', onLock);
Bangle.removeListener('charging', onCharging);
};
const startTimers = function () {
if (drawInterval) return;
if (settings.showSeconds) {
drawInterval = setInterval( onSecondInterval, 1000);
} else {
startTimeout = setTimeout(onMinuteIntervalStarter, (60 - new Date().getSeconds()) * 1000);
}
};
const stopTimers = function () {
if (startTimeout) clearTimeout(startTimeout);
if (!drawInterval) return;
clearInterval(drawInterval);
drawInterval = null;
};
/* Startup Process
------------------------------------------------------------------------------*/
g.clear();
drawAll();
startTimers();
registerEvents();
Bangle.setUI({mode: 'clock', remove: function() {
stopTimers();
deregisterEvents();
}});
Bangle.loadWidgets();
Bangle.drawWidgets();
}

BIN
apps/edgeclk/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -0,0 +1,20 @@
{
"id": "edgeclk",
"name": "Edge Clock",
"shortName": "Edge Clock",
"version": "0.03",
"description": "Crisp clock with perfect readability.",
"readme": "README.md",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot2.png"}, {"url":"screenshot3.png"}, {"url":"screenshot4.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"edgeclk.app.js", "url": "app.js"},
{"name":"edgeclk.settings.js", "url": "settings.js"},
{"name":"edgeclk.img", "url": "app-icon.js", "evaluate": true}
],
"data": [{"name":"edgeclk.settings.json"}]
}

BIN
apps/edgeclk/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

125
apps/edgeclk/settings.js Normal file
View File

@ -0,0 +1,125 @@
(function(back) {
const SETTINGS_FILE = 'edgeclk.settings.json';
const storage = require('Storage');
const settings = {
buzzOnCharge: true,
monthFirst: true,
twentyFourH: true,
showAmPm: false,
showSeconds: true,
showWeather: false,
stepGoal: 10000,
stepBar: true,
weekBar: true,
mondayFirst: true,
dayBar: true,
};
const saved_settings = storage.readJSON(SETTINGS_FILE, true);
if (saved_settings) {
for (const key in saved_settings) {
if (!settings.hasOwnProperty(key)) continue;
settings[key] = saved_settings[key];
}
}
let save = function() {
storage.write(SETTINGS_FILE, settings);
}
E.showMenu({
'': { 'title': 'Edge Clock' },
'< Back': back,
'Charge Buzz': {
value: settings.buzzOnCharge,
onchange: () => {
settings.buzzOnCharge = !settings.buzzOnCharge;
save();
},
},
'Month First': {
value: settings.monthFirst,
onchange: () => {
settings.monthFirst = !settings.monthFirst;
save();
},
},
'24h Clock': {
value: settings.twentyFourH,
onchange: () => {
settings.twentyFourH = !settings.twentyFourH;
save();
},
},
'Show AM/PM': {
value: settings.showAmPm,
onchange: () => {
settings.showAmPm = !settings.showAmPm;
// TODO can this be visually changed?
if (settings.showAmPm && settings.showSeconds) settings.showSeconds = false;
if (settings.showAmPm && settings.showWeather) settings.showWeather = false;
save();
},
},
'Show Seconds': {
value: settings.showSeconds,
onchange: () => {
settings.showSeconds = !settings.showSeconds;
// TODO can this be visually changed?
if (settings.showSeconds && settings.showAmPm) settings.showAmPm = false;
if (settings.showSeconds && settings.showWeather) settings.showWeather = false;
save();
},
},
'Show Weather': {
value: settings.showWeather,
onchange: () => {
settings.showWeather = !settings.showWeather;
// TODO can this be visually changed?
if (settings.showWeather && settings.showAmPm) settings.showAmPm = false;
if (settings.showWeather && settings.showSeconds) settings.showSeconds = false;
save();
},
},
'Step Goal': {
value: settings.stepGoal,
min: 250,
max: 50000,
step: 250,
onchange: v => {
settings.stepGoal = v;
save();
}
},
'Step Progress': {
value: settings.stepBar,
onchange: () => {
settings.stepBar = !settings.stepBar;
save();
}
},
'Week Progress': {
value: settings.weekBar,
onchange: () => {
settings.weekBar = !settings.weekBar;
save();
},
},
'Week Start': {
value: settings.mondayFirst,
format: () => settings.mondayFirst ? 'Monday' : 'Sunday',
onchange: () => {
settings.mondayFirst = !settings.mondayFirst;
save();
},
},
'Day Progress': {
value: settings.dayBar,
onchange: () => {
settings.dayBar = !settings.dayBar;
save();
},
},
});
})

View File

@ -128,38 +128,36 @@ LED.set();NRF.sleep();`);
posteditor.on("change", editorChanged);
document.getElementById("upload").addEventListener("click", function() {
if (!hasWarnings()) {
var precode = preeditor.getValue();
var jscode = jseditor.getValue();
var postcode = posteditor.getValue();
var namePrefix = document.getElementById("nameprefix").value;
localStorage.setItem(LS_PRECODE, precode);
localStorage.setItem(LS_JSCODE, jscode);
localStorage.setItem(LS_POSTCODE, postcode);
localStorage.setItem(LS_NAMEPREFIX, namePrefix);
var precode = preeditor.getValue();
var jscode = jseditor.getValue();
var postcode = posteditor.getValue();
var namePrefix = document.getElementById("nameprefix").value;
localStorage.setItem(LS_PRECODE, precode);
localStorage.setItem(LS_JSCODE, jscode);
localStorage.setItem(LS_POSTCODE, postcode);
localStorage.setItem(LS_NAMEPREFIX, namePrefix);
// force version - as long as we're above 1v96 we get the ability to upload to different storage files
var ENV = Espruino.Core.Env.getData();
ENV.VERSION_MAJOR = 2;
ENV.VERSION_MINOR = 0;
// Now compile
Espruino.transform(jscode, {
SET_TIME_ON_WRITE : false, // time would just be out of date
SAVE_ON_SEND : 1, // save to flash
LOAD_STORAGE_FILE : 0, // do not load from storage after saving
// PRETOKENISE : true,
// MINIFICATION_LEVEL : "ESPRIMA", // maybe?
}).then(content => {
sendCustomizedApp({
storage: [{ name: "espruinoprog.json", content: JSON.stringify({
namePrefix : namePrefix,
pre : Espruino.Core.CodeWriter.reformatCode(precode),
code : Espruino.Core.CodeWriter.reformatCode(content),
post : Espruino.Core.CodeWriter.reformatCode(postcode)
})}]
});
// force version - as long as we're above 1v96 we get the ability to upload to different storage files
var ENV = Espruino.Core.Env.getData();
ENV.VERSION_MAJOR = 2;
ENV.VERSION_MINOR = 0;
// Now compile
Espruino.transform(jscode, {
SET_TIME_ON_WRITE : false, // time would just be out of date
SAVE_ON_SEND : 1, // save to flash
LOAD_STORAGE_FILE : 0, // do not load from storage after saving
// PRETOKENISE : true,
// MINIFICATION_LEVEL : "ESPRIMA", // maybe?
}).then(content => {
sendCustomizedApp({
storage: [{ name: "espruinoprog.json", content: JSON.stringify({
namePrefix : namePrefix,
pre : Espruino.Core.CodeWriter.reformatCode(precode),
code : Espruino.Core.CodeWriter.reformatCode(content),
post : Espruino.Core.CodeWriter.reformatCode(postcode)
})}]
});
}
});
});
document.getElementById("setdefault").addEventListener("click", function(e) {
e.preventDefault();

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