mirror of https://github.com/espruino/BangleApps
Merge branch 'bttfclock' of https://github.com/NoobEjby/BangleApps into bttfclock
commit
754820fcdc
|
@ -5,3 +5,5 @@ updates:
|
|||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "gfwilliams"
|
||||
|
|
|
@ -289,6 +289,7 @@ and which gives information about the app for the Launcher.
|
|||
"dependencies" : { "message":"widget" } // optional, depend on a specific type of widget - see provides_widgets
|
||||
"provides_modules" : ["messageicons"] // optional, this app provides a module that can be used with 'require'
|
||||
"provides_widgets" : ["battery"] // optional, this app provides a type of widget - 'alarm/battery/bluetooth/pedometer/message'
|
||||
"provides_features" : ["welcome"] // optional, this app provides some feature, used to ensure two aren't installed at once. Currently just 'welcome'
|
||||
"default" : true, // set if an app is the default implementer of something (a widget/module/etc)
|
||||
"readme": "README.md", // if supplied, a link to a markdown-style text file
|
||||
// that contains more information about this app (usage, etc)
|
||||
|
|
|
@ -15,3 +15,4 @@
|
|||
0.13: Show day of the week in date
|
||||
0.14: Fixed "Today" and "Yesterday" wrongly displayed for allDay events on some time zones
|
||||
0.15: Minor code improvements
|
||||
0.16: Correct date for all day events in negative timezones, improve locale display
|
||||
|
|
|
@ -30,11 +30,16 @@ var settings = require("Storage").readJSON("agenda.settings.json",true)||{};
|
|||
|
||||
CALENDAR=CALENDAR.sort((a,b)=>a.timestamp - b.timestamp);
|
||||
|
||||
function getDate(timestamp) {
|
||||
return new Date(timestamp*1000);
|
||||
function getDate(timestamp, allDay) {
|
||||
// All day events are always in UTC and always start at 00:00:00, so we
|
||||
// need to "undo" the timezone offsetting to make sure that the day is
|
||||
// correct.
|
||||
var offset = allDay ? new Date().getTimezoneOffset() * 60 : 0
|
||||
return new Date((timestamp+offset)*1000);
|
||||
}
|
||||
|
||||
function formatDay(date) {
|
||||
let formattedDate = Locale.dow(date,1) + " " + Locale.date(date).replace(/\d\d\d\d/,"");
|
||||
let formattedDate = Locale.dow(date,1) + " " + Locale.date(date).replace(/,*\s*\d\d\d\d/,"");
|
||||
if (!settings.useToday) {
|
||||
return formattedDate;
|
||||
}
|
||||
|
@ -57,8 +62,9 @@ function formatDateLong(date, includeDay, allDay) {
|
|||
}
|
||||
return shortTime;
|
||||
}
|
||||
|
||||
function formatDateShort(date, allDay) {
|
||||
return formatDay(date)+(allDay?"":Locale.time(date,1)+Locale.meridian(date));
|
||||
return formatDay(date)+(allDay?"":" "+Locale.time(date,1)+Locale.meridian(date));
|
||||
}
|
||||
|
||||
var lines = [];
|
||||
|
@ -69,16 +75,19 @@ function showEvent(ev) {
|
|||
//var lines = [];
|
||||
if (ev.title) lines = g.wrapString(ev.title, g.getWidth()-10);
|
||||
var titleCnt = lines.length;
|
||||
var start = getDate(ev.timestamp);
|
||||
var end = getDate((+ev.timestamp) + (+ev.durationInSeconds));
|
||||
var start = getDate(ev.timestamp, ev.allDay);
|
||||
// All day events end at the midnight boundary of the following day. Here, we
|
||||
// subtract one second for all day events so the days display correctly.
|
||||
const allDayEndCorrection = ev.allDay ? 1 : 0;
|
||||
var end = getDate((+ev.timestamp) + (+ev.durationInSeconds) - allDayEndCorrection, ev.allDay);
|
||||
var includeDay = true;
|
||||
if (titleCnt) lines.push(""); // add blank line after title
|
||||
if(start.getDay() == end.getDay() && start.getMonth() == end.getMonth())
|
||||
includeDay = false;
|
||||
if(includeDay && ev.allDay) {
|
||||
//single day all day (average to avoid getting previous day)
|
||||
if(!includeDay && ev.allDay) {
|
||||
//single day all day
|
||||
lines = lines.concat(
|
||||
g.wrapString(formatDateLong(new Date((start+end)/2), includeDay, ev.allDay), g.getWidth()-10));
|
||||
g.wrapString(formatDateLong(start, includeDay, ev.allDay), g.getWidth()-10));
|
||||
} else if(includeDay || ev.allDay) {
|
||||
lines = lines.concat(
|
||||
/*LANG*/"Start"+":",
|
||||
|
@ -137,7 +146,7 @@ function showList() {
|
|||
if (!ev) return;
|
||||
var isPast = false;
|
||||
var x = r.x+2, title = ev.title;
|
||||
var body = formatDateShort(getDate(ev.timestamp),ev.allDay)+"\n"+(ev.location?ev.location:/*LANG*/"No location");
|
||||
var body = formatDateShort(getDate(ev.timestamp, ev.allDay),ev.allDay)+"\n"+(ev.location?ev.location:/*LANG*/"No location");
|
||||
if(settings.pastEvents) isPast = ev.timestamp + ev.durationInSeconds < (new Date())/1000;
|
||||
if (title) g.setFontAlign(-1,-1).setFont(fontBig)
|
||||
.setColor(isPast ? "#888" : g.theme.fg).drawString(title, x+4,r.y+2);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "agenda",
|
||||
"name": "Agenda",
|
||||
"version": "0.15",
|
||||
"version": "0.16",
|
||||
"description": "Simple agenda",
|
||||
"icon": "agenda.png",
|
||||
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
||||
|
|
|
@ -4,11 +4,24 @@ App that performs GPS monitoring to track and display position relative to a giv
|
|||
|
||||

|
||||
|
||||
[compass 5]
|
||||
|
||||
altitude
|
||||
[start 1] [current 2]
|
||||
|
||||
distance
|
||||
[from start 3] [track 4]
|
||||
|
||||
|
||||
[btn1 -- screen lock]
|
||||
[btn2 -- remove points]
|
||||
[btn3 -- pause]
|
||||
|
||||
Functions
|
||||
---------
|
||||
Note if you've not used GPS yet I suggest using one of the GPS apps to get your first fix and confirm as I've found that helps initially.
|
||||
Note if you've not used GPS yet, I suggest using one of the GPS apps to get your first fix and confirm, as I've found that helps initially.
|
||||
|
||||
The GPS and magnetometer will be turned on and after a few moments, when the watch buzzes and the dot turns from red to pink, that means the GPS is fixed. all your movements now will be displayed with a line drawn back to show your position relative to the start. New waypoints will be added based on checking every 10 seconds for at least 5 meters of movement. The map will scale to your distance travelled so the route will always remain within the window, the accelerometer/pedometer is not used - this is a purely GPS and compass solution so can be used for driving/cycling etc. A log file will be recorded that tracks upto 1000 waypoints, this isn't a big file and you could remove the limit but I've kept it fairly conservative here as it's not intended as a main feature, there's already good GPS recorders for the Bangle. The following other items are displayed:
|
||||
The GPS and magnetometer will be turned on and after a few moments, when the watch buzzes and the dot turns from red to pink, that means the GPS is fixed. All your movements now will be displayed with a line drawn back to show your position relative to the start. New waypoints will be added based on checking every 10 seconds for at least 5 meters of movement. The map will scale to your distance travelled so the route will always remain within the window, the accelerometer/pedometer is not used - this is a purely GPS and compass solution so can be used for driving/cycling etc. A log file will be recorded that tracks upto 1000 waypoints, this isn't a big file and you could remove the limit, but I've kept it fairly conservative here, as it's not intended as a main feature, there's already good GPS recorders for the Bangle. The following other items are displayed:
|
||||
|
||||
1. altitude at origin, this is displayed left of the centre.
|
||||
2. current altitude, displayed centre right
|
||||
|
@ -16,12 +29,12 @@ The GPS and magnetometer will be turned on and after a few moments, when the wat
|
|||
4. distance travelled, bottom right (meters)
|
||||
5. compass heading, at the top
|
||||
|
||||
For the display, the route is kept at a set resolution, so there's no risk of running into memory problems if you run this for long periods or any length of time because the waypoints will be reduced when it reaches a set threshold so you may see the path smooth out slightly at intervals.
|
||||
For the display, the route is kept at a set resolution, so there's no risk of running into memory problems if you run this for long periods or any length of time, because the waypoints will be reduced when it reaches a set threshold, so you may see the path smooth out slightly at intervals.
|
||||
|
||||
If you get strange values or dashes for the compass, it just needs calibration so you need to move the watch around briefly for this each time, ideally 360 degrees around itself, which involves taking the watch off. If you don't want to do that you can also just wave your hand around for a few seconds like you're at a rave or Dr Strange making a Sling Ring but often just moving your wrist a bit is enough.
|
||||
If you get strange values or dashes for the compass, it just needs calibration so you need to move the watch around briefly for this each time, ideally 360 degrees around itself, which involves taking the watch off. If you don't want to do that you can also just wave your hand around for a few seconds like you're at a rave or Dr Strange making a Sling Ring, but often just moving your wrist a bit is enough.
|
||||
|
||||
The buttons do the following:
|
||||
BTN1: this will display an 'X' in the bottom of the screen and lock all the buttons, this is to prevent you accidentally pressing either of the below. Remember to press this again to unlock it! soft and hard reset will both still work.
|
||||
BTN1: this will display an 'X' in the bottom of the screen and lock all the buttons, this is to prevent you accidentally pressing either of the below. Remember to press this again to unlock it! Soft and hard reset will both still work.
|
||||
BTN2: this removes all waypoints aside from the origin and your current location; sometimes during smaller journeys and walks, the GPS can give sporadic differences in locations because of the error margins of GPS and this can add noise to your route.
|
||||
BTN3: this will pause the GPS and magnetometer, useful for saving power for situations where you don't necessarily need to track parts of your route e.g. you're going indoors/shelter for some time. You'll know it's paused because the compass won't update it's reading and all the metrics will be blacked out on the screen.
|
||||
|
||||
|
|
|
@ -5,3 +5,5 @@
|
|||
0.05: Fix support for dark theme + support widgets +
|
||||
add settings for widgets, order of drawing and hour hand length
|
||||
0.06: Fix issue showing widgets when app is fast-loaded into from launcher with widgets disabled
|
||||
0.07: Enable fast loading and queue updates to the second
|
||||
0.08: Restore redraw on charging event + fixup for safer fast-loading
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
const defaultSettings = {
|
||||
loadWidgets : false,
|
||||
textAboveHands : false,
|
||||
|
@ -25,18 +26,15 @@ const zahlpos=(function() {
|
|||
return z;
|
||||
})();
|
||||
|
||||
let unlock = false;
|
||||
|
||||
function zeiger(len,dia,tim){
|
||||
const zeiger = function(len,dia,tim) {
|
||||
const x=c.x+ Math.cos(tim)*len/2,
|
||||
y=c.y + Math.sin(tim)*len/2,
|
||||
d={"d":3,"x":dia/2*Math.cos(tim+Math.PI/2),"y":dia/2*Math.sin(tim+Math.PI/2)},
|
||||
pol=[c.x-d.x,c.y-d.y,c.x+d.x,c.y+d.y,x+d.x,y+d.y,x-d.x,y-d.y];
|
||||
return pol;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function drawHands(d) {
|
||||
const drawHands = function(d) {
|
||||
let m=d.getMinutes(), h=d.getHours(), s=d.getSeconds();
|
||||
g.setColor(1,1,1);
|
||||
|
||||
|
@ -61,22 +59,22 @@ function drawHands(d) {
|
|||
g.fillPoly(sekz,true);
|
||||
}
|
||||
g.fillCircle(c.x,c.y,4);
|
||||
}
|
||||
};
|
||||
|
||||
function drawText(d) {
|
||||
const drawText = function(d) {
|
||||
g.setFont("Vector",10);
|
||||
g.setBgColor(0,0,0);
|
||||
g.setColor(1,1,1);
|
||||
let dateStr = require("locale").date(d);
|
||||
const dateStr = require("locale").date(d);
|
||||
g.drawString(dateStr, c.x, c.y+20, true);
|
||||
let batStr = Math.round(E.getBattery()/5)*5+"%";
|
||||
const batStr = Math.round(E.getBattery()/5)*5+"%";
|
||||
if (Bangle.isCharging()) {
|
||||
g.setBgColor(1,0,0);
|
||||
}
|
||||
g.drawString(batStr, c.x, c.y+40, true);
|
||||
}
|
||||
};
|
||||
|
||||
function drawNumbers() {
|
||||
const drawNumbers = function() {
|
||||
//draws the numbers on the screen
|
||||
g.setFont("Vector",20);
|
||||
g.setColor(1,1,1);
|
||||
|
@ -84,9 +82,37 @@ function drawNumbers() {
|
|||
for(let i = 0;i<12;i++){
|
||||
g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2],true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function draw(){
|
||||
let drawTimeout;
|
||||
let queueMillis = 1000;
|
||||
let unlock = true;
|
||||
|
||||
const updateState = function() {
|
||||
if (Bangle.isLCDOn()) {
|
||||
if (!Bangle.isLocked()) {
|
||||
queueMillis = 1000;
|
||||
unlock = true;
|
||||
} else {
|
||||
queueMillis = 60000;
|
||||
unlock = false;
|
||||
}
|
||||
draw();
|
||||
} else {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const queueDraw = function() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, queueMillis - (Date.now() % queueMillis));
|
||||
};
|
||||
|
||||
const draw = function() {
|
||||
// draw black rectangle in the middle to clear screen from scale and hands
|
||||
g.setColor(0,0,0);
|
||||
g.fillRect(10,10,2*c.x-10,2*c.x-10);
|
||||
|
@ -100,10 +126,11 @@ function draw(){
|
|||
} else {
|
||||
drawText(d); drawHands(d);
|
||||
}
|
||||
}
|
||||
queueDraw();
|
||||
};
|
||||
|
||||
//draws the scale once the app is startet
|
||||
function drawScale(){
|
||||
const drawScale = function() {
|
||||
// clear the screen
|
||||
g.setBgColor(0,0,0);
|
||||
g.clear();
|
||||
|
@ -117,36 +144,35 @@ function drawScale(){
|
|||
g.fillRect(10,10,2*c.x-10,2*c.x-10);
|
||||
g.setColor(1,1,1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//// main running sequence ////
|
||||
|
||||
// Show launcher when middle button pressed, and widgets that we're clock
|
||||
Bangle.setUI("clock");
|
||||
Bangle.setUI({
|
||||
mode: "clock",
|
||||
remove: function() {
|
||||
Bangle.removeListener('lcdPower', updateState);
|
||||
Bangle.removeListener('lock', updateState);
|
||||
Bangle.removeListener('charging', draw);
|
||||
// We clear drawTimout after removing all listeners, because they can add one again
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
require("widget_utils").show();
|
||||
}
|
||||
});
|
||||
// Load widgets if needed, and make them show swipeable
|
||||
if (settings.loadWidgets) {
|
||||
Bangle.loadWidgets();
|
||||
require("widget_utils").swipeOn();
|
||||
} else if (global.WIDGETS) require("widget_utils").hide();
|
||||
// Clear the screen once, at startup
|
||||
drawScale();
|
||||
draw();
|
||||
|
||||
let secondInterval = setInterval(draw, 1000);
|
||||
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
if (on) {
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
draw(); // draw immediately
|
||||
Bangle.on('lcdPower', updateState);
|
||||
Bangle.on('lock', updateState);
|
||||
Bangle.on('charging', draw); // Immediately redraw when charger (dis)connected
|
||||
|
||||
updateState();
|
||||
drawScale();
|
||||
draw();
|
||||
}
|
||||
});
|
||||
Bangle.on('lock',on=>{
|
||||
unlock = !on;
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = setInterval(draw, unlock ? 1000 : 60000);
|
||||
draw(); // draw immediately
|
||||
});
|
||||
Bangle.on('charging',on=>{draw();});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "andark",
|
||||
"name": "Analog Dark",
|
||||
"shortName":"AnDark",
|
||||
"version":"0.06",
|
||||
"version":"0.08",
|
||||
"description": "analog clock face without disturbing widgets",
|
||||
"icon": "andark_icon.png",
|
||||
"type": "clock",
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
<input type="radio" name="gnss_select" value="6"><i class="form-icon"></i> BDS+GLONASS
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="6"><i class="form-icon"></i> GPS+BDS+GLONASS
|
||||
<input type="radio" name="gnss_select" value="7"><i class="form-icon"></i> GPS+BDS+GLONASS
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
0.01: First release
|
||||
0.02: Fix icon, utilize sched, show running timer on app relaunch
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgIKHgwFKo0gAofmsALEGR0H/+f//+gEP/4ACAoXAn4FDAQn8g0DAoX4g0BAoXx4E4AoXhAoN/8EP4AzBn/4h/IC4M//kPzgjBz/+h+MAoMfj0PNYUfh4FDh8HAo0wg/454RBmBDBAoRnBCIIjCAAMPF4IFDHYOIgEBj5HBzkAIIPAIIIFBn4hBLIU+AoPgwEQvwFBOIX8CgP5w0RAoSJC/AsB/0EJwIgB/+Aj/wAoN/VgPgQwQFBwBKCXAQWBAAfgAoocCAoQcCAAPAj7XEcYIABcYLIBAAJBBA=="))
|
|
@ -0,0 +1,156 @@
|
|||
// Tea Timer Application for Bangle.js 2 using sched library
|
||||
|
||||
let timerDuration = (() => {
|
||||
let file = require("Storage").open("ateatimer.data", "r");
|
||||
let data = file.read(4); // Assuming 4 bytes for storage
|
||||
return data ? parseInt(data, 10) : 4 * 60; // Default to 4 minutes
|
||||
})();
|
||||
let timeRemaining = timerDuration;
|
||||
let timerRunning = false;
|
||||
|
||||
function saveDefaultDuration() {
|
||||
let file = require("Storage").open("ateatimer.data", "w");
|
||||
file.write(timerDuration.toString());
|
||||
}
|
||||
|
||||
function drawTime() {
|
||||
g.clear();
|
||||
g.setFont("Vector", 40);
|
||||
g.setFontAlign(0, 0); // Center align
|
||||
|
||||
const minutes = Math.floor(Math.abs(timeRemaining) / 60);
|
||||
const seconds = Math.abs(timeRemaining) % 60;
|
||||
const sign = timeRemaining < 0 ? "-" : "";
|
||||
const timeStr = `${sign}${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||||
|
||||
g.drawString(timeStr, g.getWidth() / 2, g.getHeight() / 2);
|
||||
|
||||
// Draw Increase button (triangle pointing up)
|
||||
g.fillPoly([
|
||||
g.getWidth() / 2, g.getHeight() / 2 - 80, // Top vertex
|
||||
g.getWidth() / 2 - 20, g.getHeight() / 2 - 60, // Bottom-left vertex
|
||||
g.getWidth() / 2 + 20, g.getHeight() / 2 - 60 // Bottom-right vertex
|
||||
]);
|
||||
|
||||
// Draw Decrease button (triangle pointing down)
|
||||
g.fillPoly([
|
||||
g.getWidth() / 2, g.getHeight() / 2 + 80, // Bottom vertex
|
||||
g.getWidth() / 2 - 20, g.getHeight() / 2 + 60, // Top-left vertex
|
||||
g.getWidth() / 2 + 20, g.getHeight() / 2 + 60 // Top-right vertex
|
||||
]);
|
||||
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
if (timerRunning) return;
|
||||
if (timeRemaining == 0) return;
|
||||
timerRunning = true;
|
||||
|
||||
// Save the default duration on timer start
|
||||
timerDuration = timeRemaining;
|
||||
saveDefaultDuration();
|
||||
scheduleTimer();
|
||||
|
||||
// Start the secondary timer to update the display
|
||||
setInterval(updateDisplay, 1000);
|
||||
}
|
||||
|
||||
function scheduleTimer() {
|
||||
// Schedule a new timer using the sched library
|
||||
require("sched").setAlarm("ateatimer", {
|
||||
msg: "Tea is ready!",
|
||||
timer: timeRemaining * 1000, // Convert to milliseconds
|
||||
vibrate: ".." // Default vibration pattern
|
||||
});
|
||||
|
||||
// Ensure the scheduler updates
|
||||
require("sched").reload();
|
||||
}
|
||||
|
||||
function resetTimer() {
|
||||
// Cancel the existing timer
|
||||
require("sched").setAlarm("ateatimer", undefined);
|
||||
require("sched").reload();
|
||||
|
||||
timerRunning = false;
|
||||
timeRemaining = timerDuration;
|
||||
drawTime();
|
||||
}
|
||||
|
||||
function adjustTime(amount) {
|
||||
if (-amount > timeRemaining) {
|
||||
// Return if result will be negative
|
||||
return;
|
||||
}
|
||||
timeRemaining += amount;
|
||||
timeRemaining = Math.max(0, timeRemaining); // Ensure time doesn't go negative
|
||||
if (timerRunning) {
|
||||
// Update the existing timer with the new remaining time
|
||||
let alarm = require("sched").getAlarm("ateatimer");
|
||||
if (alarm) {
|
||||
// Cancel the current alarm
|
||||
require("sched").setAlarm("ateatimer", undefined);
|
||||
|
||||
// Set a new alarm with the updated time
|
||||
scheduleTimer();
|
||||
}
|
||||
}
|
||||
|
||||
drawTime();
|
||||
}
|
||||
|
||||
function handleTouch(x, y) {
|
||||
const centerY = g.getHeight() / 2;
|
||||
|
||||
if (y < centerY - 40) {
|
||||
// Increase button area
|
||||
adjustTime(60);
|
||||
} else if (y > centerY + 40) {
|
||||
// Decrease button area
|
||||
adjustTime(-60);
|
||||
} else {
|
||||
// Center area
|
||||
if (!timerRunning) {
|
||||
startTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update the display every second
|
||||
function updateDisplay() {
|
||||
if (timerRunning) {
|
||||
let alarm = require("sched").getAlarm("ateatimer");
|
||||
timeRemaining = Math.floor(require("sched").getTimeToAlarm(alarm) / 1000);
|
||||
drawTime();
|
||||
if (timeRemaining <= 0) {
|
||||
timeRemaining = 0;
|
||||
clearInterval(updateDisplay);
|
||||
timerRunning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle physical button press for resetting timer
|
||||
setWatch(() => {
|
||||
if (timerRunning) {
|
||||
resetTimer();
|
||||
} else {
|
||||
startTimer();
|
||||
}
|
||||
}, BTN1, { repeat: true, edge: "falling" });
|
||||
|
||||
// Handle touch
|
||||
Bangle.on("touch", (zone, xy) => {
|
||||
handleTouch(xy.x, xy.y, false);
|
||||
});
|
||||
|
||||
let isRunning = require("sched").getAlarm("ateatimer");
|
||||
if (isRunning) {
|
||||
timerRunning = true;
|
||||
// Start the timer to update the display
|
||||
setInterval(updateDisplay, 1000);
|
||||
} else {
|
||||
// Draw the initial timer display
|
||||
drawTime();
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{ "duration": 240 }
|
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
|
@ -0,0 +1,14 @@
|
|||
{ "id": "ateatimer",
|
||||
"name": "A Tea Timer",
|
||||
"shortName":"A Tea Timer",
|
||||
"icon": "app.png",
|
||||
"version":"0.02",
|
||||
"description": "Simple app for setting timers for tea. Touch up and down to change time, and time or button to start counting. When timer is running, button will stop timer and reset counter to last used value.",
|
||||
"tags": "timer",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"ateatimer.app.js","url":"app.js"},
|
||||
{"name":"ateatimer.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"dependencies": {"scheduler":"type"}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
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.
|
||||
0.04: Fix messages auto opened by `messagegui` could not be blacklisted. Needs
|
||||
a refresh by deselecting and reselecting the "Messages" app throught Back Swipe
|
||||
settings.
|
||||
0.04: Fix messages auto opened by `messagegui` could not be blacklisted. Needs a refresh by deselecting and reselecting the "Messages" app throught Back Swipe settings.
|
||||
0.05: React on swipes before the active app (for the most part) by using `prependListener`.
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
// if we're in an app that has a back button, run the callback for it
|
||||
if (global.BACK && countHandlers("swipe")<=settings.standardNumSwipeHandlers && countHandlers("drag")<=settings.standardNumDragHandlers) {
|
||||
global.BACK();
|
||||
E.stopEventPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,5 +57,5 @@
|
|||
}
|
||||
|
||||
// Listen to left to right swipe
|
||||
Bangle.on("swipe", goBack);
|
||||
Bangle.prependListener("swipe", goBack);
|
||||
})();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "backswipe",
|
||||
"name": "Back Swipe",
|
||||
"shortName":"BackSwipe",
|
||||
"version":"0.04",
|
||||
"version":"0.05",
|
||||
"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",
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
0.08: Fixed typo in settings.js for DRAGDOWN to make option work
|
||||
0.09: You can now back out of the calendar using the button
|
||||
0.10: Fix linter warnings
|
||||
0.11: Added option to show prior weeks on clock calendar
|
||||
|
|
|
@ -2,7 +2,8 @@ Bangle.setUI("clock");
|
|||
Bangle.loadWidgets();
|
||||
|
||||
var s = Object.assign({
|
||||
CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
|
||||
CAL_ROWS: 4, //total number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
|
||||
CAL_ROWS_PRIOR: 0, //number of calendar rows.(weeks) that show above the current week
|
||||
BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually
|
||||
MODE24: true, //24h mode vs 12h mode
|
||||
FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su
|
||||
|
@ -178,7 +179,7 @@ function drawWatch() {
|
|||
const dow = (s.FIRSTDAY + d.getDay()) % 7; //MO=0, SU=6
|
||||
const today = d.getDate();
|
||||
var rD = new Date(d.getTime());
|
||||
rD.setDate(rD.getDate() - dow);
|
||||
rD.setDate(rD.getDate() - dow - s.CAL_ROWS_PRIOR * 7);
|
||||
var rDate = rD.getDate();
|
||||
g.setFontAlign(1, 1);
|
||||
for (var y = 1; y <= s.CAL_ROWS; y++) {
|
||||
|
@ -187,7 +188,7 @@ function drawWatch() {
|
|||
bottomrightY = y * CELL_H + CAL_Y;
|
||||
g.setFont("Vector", 16);
|
||||
var fg = ((s.REDSUN && rD.getDay() == 0) || (s.REDSAT && rD.getDay() == 6)) ? '#f00' : '#fff';
|
||||
if (y == 1 && today == rDate) {
|
||||
if (y == s.CAL_ROWS_PRIOR + 1 && today == rDate) {
|
||||
g.setColor('#0f0');
|
||||
g.fillRect(bottomrightX - CELL_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2);
|
||||
g.setColor('#000');
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "clockcal",
|
||||
"name": "Clock & Calendar",
|
||||
"version": "0.10",
|
||||
"version": "0.11",
|
||||
"description": "Clock with Calendar",
|
||||
"readme":"README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
(function (back) {
|
||||
var FILE = "clockcal.json";
|
||||
const defaults={
|
||||
CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
|
||||
CAL_ROWS: 4, //total number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
|
||||
CAL_ROWS_PRIOR: 0, //number of calendar rows.(weeks) that show above the current week
|
||||
BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually
|
||||
MODE24: true, //24h mode vs 12h mode
|
||||
FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su
|
||||
|
@ -39,6 +40,14 @@
|
|||
writeSettings();
|
||||
}
|
||||
},
|
||||
'#Cal Rows Prior': {
|
||||
value: settings.CAL_ROWS_PRIOR,
|
||||
min: 0, max: 4,
|
||||
onchange: v => {
|
||||
settings.CAL_ROWS_PRIOR = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Clock mode': {
|
||||
value: settings.MODE24,
|
||||
format: v => v ? "24h" : "12h",
|
||||
|
|
|
@ -58,6 +58,7 @@ var draw = function () {
|
|||
}, 60000 - (date.getTime() % 60000));
|
||||
};
|
||||
var reload = function () {
|
||||
var scroller;
|
||||
var showMenu = function () {
|
||||
var menu = {
|
||||
"Restore to full power": drainedRestore,
|
||||
|
@ -69,9 +70,12 @@ var reload = function () {
|
|||
menu["Settings"] = function () { return load("setting.app.js"); };
|
||||
menu["Recovery"] = function () { return Bangle.showRecoveryMenu(); };
|
||||
menu["Exit menu"] = reload;
|
||||
if (scroller) {
|
||||
menu[""] = { selected: scroller.scroll };
|
||||
}
|
||||
if (nextDraw)
|
||||
clearTimeout(nextDraw);
|
||||
E.showMenu(menu);
|
||||
(scroller = E.showMenu(menu).scroller);
|
||||
};
|
||||
Bangle.setUI({
|
||||
mode: "custom",
|
||||
|
|
|
@ -78,8 +78,9 @@ const draw = () => {
|
|||
};
|
||||
|
||||
const reload = () => {
|
||||
let scroller: MenuInstance["scroller"] | undefined;
|
||||
const showMenu = () => {
|
||||
const menu: { [k: string]: () => void } = {
|
||||
const menu: Menu = {
|
||||
"Restore to full power": drainedRestore,
|
||||
};
|
||||
|
||||
|
@ -92,8 +93,12 @@ const reload = () => {
|
|||
menu["Recovery"] = () => Bangle.showRecoveryMenu();
|
||||
menu["Exit menu"] = reload;
|
||||
|
||||
if(scroller){
|
||||
menu[""] = { selected: scroller.scroll };
|
||||
}
|
||||
|
||||
if(nextDraw) clearTimeout(nextDraw);
|
||||
E.showMenu(menu);
|
||||
({ scroller } = E.showMenu(menu));
|
||||
};
|
||||
|
||||
Bangle.setUI({
|
||||
|
|
|
@ -31,3 +31,4 @@ when moving pages. Add caching for faster startups.
|
|||
0.24: Add buzz-on-interaction setting
|
||||
0.25: Minor code improvements
|
||||
0.26: Bangle 2: Postpone loading icons that are not needed initially.
|
||||
0.27: Bangle 2: Add setting to remember and present the last open page between instances of dtlaunch.
|
||||
|
|
|
@ -17,7 +17,9 @@ Bangle 2:
|
|||

|
||||
|
||||
|
||||
## Controls- Bangle
|
||||
## Controls
|
||||
|
||||
### Bangle 1
|
||||
|
||||
**BTN1** - move backward through app icons on a page
|
||||
|
||||
|
@ -35,10 +37,28 @@ Bangle 2:
|
|||
|
||||
**Touch Middle(1+2) area** - run the selected app
|
||||
|
||||
## Controls- Bangle 2
|
||||
### Bangle 2
|
||||
|
||||
**Touch** - icon to select, second touch launches app
|
||||
|
||||
**Swipe Left/Up** - move to next page of app icons
|
||||
|
||||
**Swipe Right/Down** - move to previous page of app icons
|
||||
|
||||
## Settings
|
||||
|
||||
**Show clocks**
|
||||
|
||||
**Show launchers**
|
||||
|
||||
### Only Bangle 2
|
||||
|
||||
**Direct launch** - launch on first touch.
|
||||
|
||||
**Swipe Exit** - Swipe left to exit.
|
||||
|
||||
**Time Out** - Return to clock after a short while.
|
||||
|
||||
**Interaction buzz**
|
||||
|
||||
**Remember Page** - Remember page when leaving and coming back to the launcher.
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
swipeExit: false,
|
||||
timeOut: "Off",
|
||||
interactionBuzz: false,
|
||||
rememberPage: false,
|
||||
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
||||
|
||||
let s = require("Storage");
|
||||
|
@ -33,7 +34,17 @@
|
|||
s.writeJSON("launch.cache.json", launchCache);
|
||||
}
|
||||
let apps = launchCache.apps;
|
||||
for (let i = 0; i < 4; i++) { // Initially only load icons for the current page.
|
||||
let page = 0;
|
||||
let initPageAppZeroth = 0;
|
||||
let initPageAppLast = 3;
|
||||
if (settings.rememberPage) {
|
||||
page = (global.dtlaunch&&global.dtlaunch.handlePagePersist()) ??
|
||||
(parseInt(s.read("dtlaunch.page")) ?? 0);
|
||||
initPageAppZeroth = page*4;
|
||||
initPageAppLast = Math.min(page*4+3, apps.length-1);
|
||||
}
|
||||
|
||||
for (let i = initPageAppZeroth; i <= initPageAppLast; i++) { // Initially only load icons for the current page.
|
||||
if (apps[i].icon)
|
||||
apps[i].icon = s.read(apps[i].icon); // should just be a link to a memory area
|
||||
}
|
||||
|
@ -43,7 +54,6 @@
|
|||
let maxPage = Npages-1;
|
||||
let selected = -1;
|
||||
//let oldselected = -1;
|
||||
let page = 0;
|
||||
const XOFF = 24;
|
||||
const YOFF = 30;
|
||||
|
||||
|
@ -99,13 +109,32 @@
|
|||
|
||||
Bangle.drawWidgets(); // To immediately update widget field to follow current theme - remove leftovers if previous app set custom theme.
|
||||
Bangle.loadWidgets();
|
||||
drawPage(0);
|
||||
drawPage(page);
|
||||
|
||||
for (let i = 4; i < apps.length; i++) { // Load the rest of the app icons that were not initially.
|
||||
for (let i = 0; i < apps.length; i++) { // Load the rest of the app icons that were not initially.
|
||||
if (i >= initPageAppZeroth && i <= initPageAppLast) continue;
|
||||
if (apps[i].icon)
|
||||
apps[i].icon = s.read(apps[i].icon); // should just be a link to a memory area
|
||||
}
|
||||
|
||||
if (!global.dtlaunch) {
|
||||
global.dtlaunch = {};
|
||||
global.dtlaunch.handlePagePersist = function(page) {
|
||||
// Function for persisting the active page when leaving dtlaunch.
|
||||
if (page===undefined) {return this.page||0;}
|
||||
|
||||
if (!this.killHandler) { // Only register kill listener once.
|
||||
this.killHandler = () => {
|
||||
s.write("dtlaunch.page", this.page.toString());
|
||||
};
|
||||
E.on("kill", this.killHandler); // This is intentionally left around after fastloading into other apps. I.e. not removed in uiRemove.
|
||||
}
|
||||
|
||||
this.page = page;
|
||||
};
|
||||
global.dtlaunch.handlePagePersist(page);
|
||||
}
|
||||
|
||||
let swipeListenerDt = function(dirLeftRight, dirUpDown){
|
||||
updateTimeoutToClock();
|
||||
selected = -1;
|
||||
|
@ -142,6 +171,7 @@
|
|||
drawIcon(page,selected,false);
|
||||
} else {
|
||||
buzzLong();
|
||||
global.dtlaunch.handlePagePersist(page);
|
||||
load(apps[page*4+i].src);
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +192,10 @@
|
|||
back : Bangle.showClock,
|
||||
swipe : swipeListenerDt,
|
||||
touch : touchListenerDt,
|
||||
remove : ()=>{if (timeoutToClock) clearTimeout(timeoutToClock);}
|
||||
remove : ()=>{
|
||||
if (timeoutToClock) {clearTimeout(timeoutToClock);}
|
||||
global.dtlaunch.handlePagePersist(page);
|
||||
}
|
||||
});
|
||||
|
||||
// taken from Icon Launcher with minor alterations
|
||||
|
@ -177,4 +210,3 @@
|
|||
updateTimeoutToClock();
|
||||
|
||||
} // end of app scope
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "dtlaunch",
|
||||
"name": "Desktop Launcher",
|
||||
"version": "0.26",
|
||||
"version": "0.27",
|
||||
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
|
||||
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
||||
"icon": "icon.png",
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
swipeExit: false,
|
||||
timeOut: "Off",
|
||||
interactionBuzz: false,
|
||||
rememberPage: false,
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
|
@ -64,5 +65,12 @@
|
|||
writeSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/'Remember Page': {
|
||||
value: settings.rememberPage,
|
||||
onchange: v => {
|
||||
settings.rememberPage = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -17,7 +17,8 @@
|
|||
</ul>
|
||||
<div id="fw-ok" style="display:none">
|
||||
<p id="fw-old-bootloader-msg">If you have an early (KickStarter or developer) Bangle.js device and still have the old 2v10.x DFU, the Firmware Update
|
||||
will fail with a message about the DFU version. If so, please <a href="bootloader_espruino_2v20_banglejs2.hex" class="fw-link">click here to update to DFU 2v20</a> and then click the 'Upload' button that appears.</p>
|
||||
will fail with a message about the DFU version. If so, please <a href="bootloader_espruino_2v25_banglejs2.hex" class="fw-link">click here to update to DFU 2v25</a> and then click the 'Upload' button that appears.</p>
|
||||
<p><button id="upload" class="btn btn-primary" style="display:none">Upload</button></p>
|
||||
<div id="latest-firmware" style="display:none">
|
||||
<p>The currently available Espruino firmware releases are:</p>
|
||||
<ul id="latest-firmware-list">
|
||||
|
@ -25,6 +26,7 @@
|
|||
<p>To update, click a link above and then click the 'Upload' button that appears.</p>
|
||||
</div>
|
||||
|
||||
|
||||
<p><a href="#" id="info-btn">What is DFU? ▼</a></p>
|
||||
<div id="info-div" style="display:none">
|
||||
<p><b>What is DFU?</b></p>
|
||||
|
@ -33,7 +35,7 @@
|
|||
Bangle.js firmware. Normally you would update firmware via this Firmware
|
||||
Updater app, but if for some reason Bangle.js will not boot, you can
|
||||
<a href="https://www.espruino.com/Bangle.js2#firmware-updates" target="_blank">always use DFU to do the update manually</a>.
|
||||
On DFU 2v19 and earlier, iOS devices could have issues updating firmware - 2v20 fixes this.</p>
|
||||
On DFU 2v19 and earlier, iOS devices could have issues updating firmware - 2v20 at later fixes this.</p>
|
||||
<p>DFU is itself a bootloader, but here we're calling it DFU to avoid confusion
|
||||
with the Bootloader app in the app loader (which prepares Bangle.js for running apps).</p>
|
||||
</div>
|
||||
|
@ -51,7 +53,7 @@
|
|||
potentially overwrite your DFU with the wrong binary and brick your Bangle.</p>
|
||||
<input class="form-input" type="file" id="fileLoader" accept=".hex,.app_hex,.zip"/><br>
|
||||
</div>
|
||||
<p><button id="upload" class="btn btn-primary" style="display:none">Upload</button></p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -107,6 +109,7 @@ function onInit(device) {
|
|||
else if (crcs[0] == 3329616485) version = "2v22";
|
||||
else if (crcs[0] == 1569433504) version = "2v23";
|
||||
else if (crcs[0] == 680675961) version = "2v24";
|
||||
else if (crcs[0] == 4148062987 || crcs[0] == 3675049818) version = "2v25";
|
||||
else { // for other versions all 7 pages are used, check those
|
||||
var crc = crcs[1];
|
||||
if (crc==1339551013) { version = "2v10.219"; ok = false; }
|
||||
|
@ -127,7 +130,7 @@ function onInit(device) {
|
|||
}
|
||||
document.getElementById("boot-version").innerHTML = version;
|
||||
var versionNumber = parseFloat(version.replace(".","").replace("v","."));
|
||||
if (versionNumber>=2.20)
|
||||
if (versionNumber>=2.25)
|
||||
document.getElementById("fw-old-bootloader-msg").style.display = "none";
|
||||
});
|
||||
}
|
||||
|
@ -385,6 +388,7 @@ function createJS_bootloader(binary, startAddress, endAddress) {
|
|||
for (var i=startAddress;i<endAddress;i+=4096)
|
||||
hexJS += 'f.erasePage(0x'+i.toString(16)+');\n';
|
||||
hexJS += `f.write(_fw,${startAddress});\n`;
|
||||
hexJS += 'E.showMessage("Update Complete.")\n';
|
||||
hexJS += `})()\n`;
|
||||
log("DFU ready for upload");
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
## gpsnav - navigate to waypoints
|
||||
|
||||
The app is aimed at small boat navigation although it can also be used to mark the location of your car, bicycle etc and then get directions back to it. Please note that it would be foolish in the extreme to rely on this as your only boat navigation aid!
|
||||
The app is aimed at small boat navigation, although it can also be used to mark the location of your car, bicycle etc and then get directions back to it. Please note that it would be foolish in the extreme to rely on this as your only boat navigation aid!
|
||||
|
||||
The app displays direction of travel (course), speed, direction to waypoint (bearing) and distance to waypoint. The screen shot below is before the app has got a GPS fix.
|
||||
|
||||
|
@ -13,11 +13,11 @@ The large digits are the course and speed. The top of the display is a linear co
|
|||
|
||||

|
||||
|
||||
The display shows that Stone Henge is 108.75Km from the location where I made the screenshot and the direction is 255 degrees - approximately west. The display shows that I am currently moving approximately north - albeit slowly!. The position of the blue circle indicates that I need to turn left to get on course to Stone Henge. When the circle and red triangle line up you are on course and course will equal bearing.
|
||||
The display shows that Stone Henge is 108.75km from the location where I made the screenshot and the direction is 255 degrees - approximately west. The display shows that I am currently moving approximately north - albeit slowly! The position of the blue circle indicates that I need to turn left to get on course to Stone Henge. When the circle and red triangle line up you are on course and course will equal bearing.
|
||||
|
||||
### Marking Waypoints
|
||||
|
||||
The app lets you mark your current location as follows. There are vacant slots in the waypoint file which can be allocated a location. In the distributed waypoint file these are labelled WP0 to WP4. Select one of these - WP2 is shown below.
|
||||
The app lets you mark your current location as follows. There are vacant slots in the waypoint file which can be allocated a location. In the distributed waypoint file, these are labelled WP0 to WP4. Select one of these - WP2 is shown below.
|
||||
|
||||

|
||||
|
||||
|
@ -25,7 +25,7 @@ Bearing and distance are both zero as WP1 has currently no GPS location associat
|
|||
|
||||

|
||||
|
||||
The app indicates that WP2 is now marked by adding the prefix @ to it's name. The distance should be small as shown in the screen shot as you have just marked your current location.
|
||||
The app indicates that WP2 is now marked by adding the prefix @ to it's name. The distance should be small as shown in the screen shot, as you have just marked your current location.
|
||||
|
||||
### Waypoint JSON file
|
||||
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: First version of GPS Setup app
|
||||
0.02: Created gppsetup module
|
||||
0.03: Added support for Bangle.js2
|
||||
|
|
|
@ -50,11 +50,15 @@ used. These settings will remain for all apps that use the GPS.
|
|||
open country an update once every 60 seconds is adequate to put
|
||||
you within a 6 digit grid refernce sqaure.
|
||||
|
||||
**Note:** For the Bangle.js2, the GPS module does not have a PSMOO mode, and thus this is emulated using on/off timeouts specified using the update and search options.
|
||||
|
||||
- update - the time between two position fix attempts.
|
||||
|
||||
- search - the time between two acquisition attempts if the receiver
|
||||
is unable to get a position fix.
|
||||
|
||||
- fix_req (Bangle.js2 only) - the number of fixes required before the GPS turns off until next search for GPS signal. default is 1.
|
||||
|
||||
## Module
|
||||
|
||||
A module is provided that'll allow you to set GPS configuration from your own
|
||||
|
|
|
@ -34,6 +34,7 @@ function loadSettings() {
|
|||
settings = require("Storage").readJSON(SETTINGS_FILE,1)||{};
|
||||
settings.update = settings.update||120;
|
||||
settings.search = settings.search||5;
|
||||
settings.fix_req = settings.fix_req||1;
|
||||
settings.power_mode = settings.power_mode||"SuperE";
|
||||
log_debug(settings);
|
||||
}
|
||||
|
@ -85,6 +86,16 @@ function showMainMenu() {
|
|||
settings.search = v;
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
'Fix Req (#)': {
|
||||
value: settings.fix_req,
|
||||
min: 1,
|
||||
max: 100,
|
||||
step: 1,
|
||||
onchange: v => {
|
||||
settings.fix_req = v;
|
||||
updateSettings();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const SETTINGS_FILE = "gpssetup.settings.json";
|
||||
|
||||
const BANGLE_VER = process.env.HWVERSION; //BangleJS2 support
|
||||
function log_debug(o) {
|
||||
//let timestamp = new Date().getTime();
|
||||
//console.log(timestamp + " : " + o);
|
||||
|
@ -106,6 +106,8 @@ function delay(ms) {
|
|||
|
||||
function setupSuperE() {
|
||||
log_debug("setupGPS() Super-E");
|
||||
switch(BANGLE_VER){
|
||||
case(1): {
|
||||
return Promise.resolve().then(function() {
|
||||
UBX_CFG_RESET();
|
||||
return delay(100);
|
||||
|
@ -125,9 +127,18 @@ function setupSuperE() {
|
|||
return delay(20);
|
||||
});
|
||||
}
|
||||
case(2):{
|
||||
//nothing more to do.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setupPSMOO(settings) {
|
||||
log_debug("setupGPS() PSMOO");
|
||||
switch(BANGLE_VER){
|
||||
case(1):{
|
||||
return Promise.resolve().then(function() {
|
||||
UBX_CFG_RESET();
|
||||
return delay(100);
|
||||
|
@ -150,6 +161,43 @@ function setupPSMOO(settings) {
|
|||
return delay(20);
|
||||
});
|
||||
}
|
||||
case(2): {
|
||||
var gpsTimeout = null;
|
||||
var gpsActive = false;
|
||||
var fix = 0;
|
||||
function cb(f){
|
||||
if(parseInt(f.fix) === 1){
|
||||
fix++;
|
||||
if(fix >= settings.fix_req){
|
||||
fix = 0;
|
||||
turnOffGPS();
|
||||
}
|
||||
}
|
||||
}
|
||||
function turnOffGPS() {
|
||||
if (!gpsActive) return;
|
||||
gpsActive = false;
|
||||
clearTimeout(gpsTimeout);
|
||||
Bangle.setGPSPower(0,settings.appName);
|
||||
Bangle.removeListener('GPS', cb); // cleaning it up
|
||||
gpsTimeout = setTimeout(() => {
|
||||
turnOnGPS();
|
||||
}, settings.update * 1000);
|
||||
}
|
||||
function turnOnGPS(){
|
||||
if (gpsActive) return;
|
||||
if(!Bangle.isGPSOn()) Bangle.setGPSPower(1,settings.appName);
|
||||
Bangle.on('GPS',cb);
|
||||
gpsActive = true;
|
||||
gpsTimeout = setTimeout(() => {
|
||||
turnOffGPS();
|
||||
}, settings.search * 1000);
|
||||
}
|
||||
turnOnGPS();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Set GPS power mode (assumes GPS on), returns a promise.
|
||||
Either:
|
||||
|
@ -161,16 +209,21 @@ require("gpssetup").setPowerMode({power_mode:"SuperE"}) // <-- Super E mode
|
|||
See the README for more information
|
||||
*/
|
||||
exports.setPowerMode = function(options) {
|
||||
settings = require("Storage").readJSON(SETTINGS_FILE,1)||{};
|
||||
var settings = require("Storage").readJSON(SETTINGS_FILE,1)||{};
|
||||
if (options) {
|
||||
if (options.update) settings.update = options.update;
|
||||
if (options.search) settings.search = options.search;
|
||||
if (options.fix_req) settings.fix_req = options.fix_req;
|
||||
if (options.power_mode) settings.power_mode = options.power_mode;
|
||||
if (options.appName) settings.appName = options.appName;
|
||||
}
|
||||
settings.update = settings.update||120;
|
||||
settings.search = settings.search||5;
|
||||
settings.fix_req = settings.fix_req||1; //default to just one fix and will turn off
|
||||
settings.power_mode = settings.power_mode||"SuperE";
|
||||
settings.appName = settings.appName || "gpssetup";
|
||||
if (options) require("Storage").write(SETTINGS_FILE, settings);
|
||||
if(!Bangle.isGPSOn()) Bangle.setGPSPower(1,settings.appName); //always know its on - no point calling this otherwise!!!
|
||||
if (settings.power_mode === "PSMOO") {
|
||||
return setupPSMOO(settings);
|
||||
} else {
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
"id": "gpssetup",
|
||||
"name": "GPS Setup",
|
||||
"shortName": "GPS Setup",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Configure the GPS power options and store them in the GPS nvram",
|
||||
"icon": "gpssetup.png",
|
||||
"tags": "gps,tools,outdoors",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"gpssetup","url":"gpssetup.js"},
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
0.01: New app!
|
||||
0.02: Add sensor icons
|
||||
Customize code directly, remove config file
|
||||
0.03: Add HRM sensor
|
||||
Add step count sensor
|
||||
Add pressure and temperature sensors
|
||||
Document Home Assistant `unique ID` workaround
|
||||
|
|
|
@ -22,3 +22,29 @@ You need to fill out these fields:
|
|||
Currently creates these sensors:
|
||||
* `<sensor id>_battery_level`: Your watch battery level as percentage
|
||||
* `<sensor id>_battery_state`: `charging` or `discharging`
|
||||
* `<sensor id>_hrm`: Heart rate (only if measured: this app doesn't enable/disable the sensor)
|
||||
* `<sensor id>_steps`: Step Count
|
||||
* `<sensor id>_pressure`: Pressure
|
||||
* `<sensor id>_temperature`: Temperature
|
||||
|
||||
## Home Assistant `unique ID` workaround
|
||||
|
||||
If you try to customize the created entities, Home Assistant will complain that
|
||||
> This entity ('sensor.…') does not have a unique ID, therefore its settings
|
||||
> cannot be managed from the UI.
|
||||
|
||||
The problem is that these sensors are created "dynamically", and there is no way
|
||||
to supply a `unique ID`.
|
||||
There is a workaround though:
|
||||
1. Make note of the sensor name you want to customize (e.g. `banglejs_battery_state`).
|
||||
2. Disconnect your Bangle.js from your phone, so it doesn't send updates.
|
||||
3. Restart Home Assistant, the sensor is now gone.
|
||||
4. <a href="https://my.home-assistant.io/redirect/config_flow_start?domain=template" target="_blank">Create a template sensor</a>: choose "Template a sensor".
|
||||
- Use the name from step 1 (without `sensor.` prefix).
|
||||
- Set the state template to `unknown`.
|
||||
5. Reconnect your Bangle.js: it will now update the new template sensor, which
|
||||
*does* have a `unique ID`.
|
||||
|
||||
**Warning:** Do not customize the `Entity ID`: the app sends values by sensor
|
||||
ID, so you end up with both a non-updating template sensor and "dynamic" sensor
|
||||
without `unique ID`.
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
(function () {
|
||||
const sb = () => require("hasensors").sendBattery();
|
||||
Bangle.on("charging", sb);
|
||||
NRF.on("connect", () => setTimeout(sb, 2000));
|
||||
setInterval(sb, 10 * 60 * 1000);
|
||||
const su = () => require("hasensors").sendUpdate();
|
||||
Bangle.on("charging", su);
|
||||
NRF.on("connect", () => setTimeout(su, 2000));
|
||||
su();
|
||||
setInterval(su, 10 * 60 * 1000);
|
||||
Bangle.on('HRM', h=>require("hasensors").sendHRM(h));
|
||||
})();
|
|
@ -12,8 +12,7 @@ function post(sensor, data) {
|
|||
});
|
||||
}
|
||||
|
||||
exports.sendBattery = function () {
|
||||
if (!NRF.getSecurityStatus().connected) return;
|
||||
function sendBattery() {
|
||||
const b = E.getBattery(),
|
||||
c = Bangle.isCharging();
|
||||
let i = "mdi:battery";
|
||||
|
@ -41,3 +40,74 @@ exports.sendBattery = function () {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendSteps() {
|
||||
post("steps", {
|
||||
state: Bangle.getStepCount(),
|
||||
attributes: {
|
||||
friendly_name: "{name} Step Count",
|
||||
unit_of_measurement: "steps",
|
||||
state_class: "total",
|
||||
icon: "mdi:shoe-print",
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends pressure *and temperature*
|
||||
*/
|
||||
function sendPressure() {
|
||||
if (!Bangle.getPressure) return; // not a Bangle 2
|
||||
const promise = Bangle.getPressure();
|
||||
if (!promise) return; // emulator?
|
||||
promise.then(values=>{
|
||||
post("pressure", {
|
||||
state: Math.round(values.pressure*10)/10,
|
||||
attributes: {
|
||||
friendly_name: "{name} Pressure",
|
||||
unit_of_measurement: "hPa",
|
||||
device_class: "atmospheric pressure",
|
||||
state_class: "measurement",
|
||||
icon: "mdi:gauge",
|
||||
}
|
||||
});
|
||||
post("temperature", {
|
||||
state: Math.round(values.temperature*10)/10,
|
||||
attributes: {
|
||||
friendly_name: "{name} Temperature",
|
||||
unit_of_measurement: "°C",
|
||||
device_class: "temperature",
|
||||
state_class: "measurement",
|
||||
icon: "mdi:thermometer",
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
exports.sendUpdate = function() {
|
||||
if (!NRF.getSecurityStatus().connected) return;
|
||||
sendBattery();
|
||||
sendSteps();
|
||||
sendPressure();
|
||||
}
|
||||
|
||||
|
||||
let hrm_last = 0;
|
||||
const HRM_INTERVAL = 10*60*1000;
|
||||
exports.sendHRM = function (hrm) {
|
||||
if (!NRF.getSecurityStatus().connected) return;
|
||||
const now = (new Date).getTime();
|
||||
if (hrm_last > now-HRM_INTERVAL) return;
|
||||
post("hrm", {
|
||||
state: hrm.bpm,
|
||||
attributes: {
|
||||
confidence: hrm.confidence,
|
||||
raw: hrm.raw,
|
||||
friendly_name: "{name} Heart Rate",
|
||||
icon: "mdi:heart",
|
||||
unit_of_measurement: "bpm",
|
||||
state_class: "measurement",
|
||||
}
|
||||
});
|
||||
hrm_last = now;
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "hasensors",
|
||||
"name": "Home Assistant Sensors",
|
||||
"shortName": "HA sensors",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Send sensor values to Home Assistant using Android Integration/Gadgetbridge",
|
||||
"icon": "ha.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Accents and extended mode characters
|
||||
0.03: Bugfix - draw initial text after the back button has been removed
|
||||
|
|
|
@ -352,11 +352,6 @@ exports.input = function(options) {
|
|||
}
|
||||
};
|
||||
|
||||
// Draw initial string
|
||||
require("widget_utils").hide();
|
||||
g.setBgColor(g.theme.bg);
|
||||
wrapText();
|
||||
draw();
|
||||
|
||||
return new Promise((resolve,reject) => {
|
||||
Bangle.setUI({
|
||||
|
@ -385,6 +380,13 @@ exports.input = function(options) {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Draw initial string
|
||||
require("widget_utils").hide();
|
||||
g.setBgColor(g.theme.bg);
|
||||
wrapText();
|
||||
draw();
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "kbedgewrite",
|
||||
"name": "EdgeWrite keyboard",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "A library for text input via EdgeWrite swipe gestures",
|
||||
"icon": "app.png",
|
||||
"type":"textinput",
|
||||
|
|
|
@ -111,3 +111,5 @@
|
|||
Make sure play button image is transparent
|
||||
Add top-right menu to music playback to allow message to be deleted
|
||||
0.82: Stop buzzing when a message is removed (e.g. call answered)
|
||||
0.83: Add option to not open the first unread message
|
||||
0.84: Fix: Assign show message entry to the settings menu and not the message itself.
|
||||
|
|
|
@ -155,7 +155,7 @@ function showMapMessage(msg) {
|
|||
function back() { // mark as not new and return to menu
|
||||
msg.new = false;
|
||||
layout = undefined;
|
||||
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1,openMusic:0});
|
||||
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,ignoreUnread:settings.ignoreUnread,openMusic:0});
|
||||
}
|
||||
Bangle.setUI({mode:"updown", back: back}, back); // any input takes us back
|
||||
}
|
||||
|
@ -195,8 +195,8 @@ function showMusicMessage(msg) {
|
|||
var wasNew = msg.new;
|
||||
msg.new = false;
|
||||
layout = undefined;
|
||||
if (wasNew) checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:0,openMusic:0});
|
||||
else checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0});
|
||||
if (wasNew) checkMessages({clockIfNoMsg:1,clockIfAllRead:1,ignoreUnread:1,openMusic:0});
|
||||
else returnToMain();
|
||||
}
|
||||
function updateLabels() {
|
||||
trackName = reduceStringAndPad(msg.track, trackScrollOffset, 13);
|
||||
|
@ -283,7 +283,7 @@ function showMessageSettings(msg) {
|
|||
};
|
||||
|
||||
if (msg.id!="music")
|
||||
msg[/*LANG*/"View Message"] = () => showMessageScroller(msg);
|
||||
menu[/*LANG*/"View Message"] = () => showMessageScroller(msg);
|
||||
|
||||
if (msg.reply && reply) {
|
||||
menu[/*LANG*/"Reply"] = () => {
|
||||
|
@ -304,7 +304,7 @@ function showMessageSettings(msg) {
|
|||
menu = Object.assign(menu, {
|
||||
/*LANG*/"Delete" : () => {
|
||||
MESSAGES = MESSAGES.filter(m=>m.id!=msg.id);
|
||||
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0});
|
||||
returnToMain();
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -315,25 +315,25 @@ function showMessageSettings(msg) {
|
|||
Bangle.messageIgnore(msg);
|
||||
MESSAGES = MESSAGES.filter(m=>m.id!=msg.id);
|
||||
}
|
||||
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0});
|
||||
returnToMain();
|
||||
});
|
||||
};
|
||||
|
||||
menu = Object.assign(menu, {
|
||||
/*LANG*/"Mark Unread" : () => {
|
||||
msg.new = true;
|
||||
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0});
|
||||
returnToMain();
|
||||
},
|
||||
/*LANG*/"Mark all read" : () => {
|
||||
MESSAGES.forEach(msg => msg.new = false);
|
||||
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0});
|
||||
returnToMain();
|
||||
},
|
||||
/*LANG*/"Delete all messages" : () => {
|
||||
E.showPrompt(/*LANG*/"Are you sure?", {title:/*LANG*/"Delete All Messages"}).then(isYes => {
|
||||
if (isYes) {
|
||||
MESSAGES = [];
|
||||
}
|
||||
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0});
|
||||
returnToMain();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -349,7 +349,7 @@ function showMessage(msgid, persist) {
|
|||
clearInterval(updateLabelsInterval);
|
||||
updateLabelsInterval=undefined;
|
||||
}
|
||||
if (!msg) return checkMessages({clockIfNoMsg:1,clockIfAllRead:0,showMsgIfUnread:0,openMusic:openMusic}); // go home if no message found
|
||||
if (!msg) return returnToClockIfEmpty(); // go home if no message found
|
||||
if (msg.id=="music") {
|
||||
cancelReloadTimeout(); // don't auto-reload to clock now
|
||||
return showMusicMessage(msg);
|
||||
|
@ -399,7 +399,7 @@ function showMessage(msgid, persist) {
|
|||
layout = undefined;
|
||||
msg.new = false; // read mail
|
||||
cancelReloadTimeout(); // don't auto-reload to clock now
|
||||
checkMessages({clockIfNoMsg:1,clockIfAllRead:0,showMsgIfUnread:0,openMusic:openMusic});
|
||||
returnToClockIfEmpty();
|
||||
}
|
||||
var negHandler,posHandler,footer = [ ];
|
||||
if (msg.negative) {
|
||||
|
@ -407,7 +407,7 @@ function showMessage(msgid, persist) {
|
|||
msg.new = false;
|
||||
cancelReloadTimeout(); // don't auto-reload to clock now
|
||||
Bangle.messageResponse(msg,false);
|
||||
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1,openMusic:openMusic});
|
||||
returnToCheckMessages();
|
||||
}; footer.push({type:"img",src:atob("PhAB4A8AAAAAAAPAfAMAAAAAD4PwHAAAAAA/H4DwAAAAAH78B8AAAAAA/+A/AAAAAAH/Af//////w/gP//////8P4D///////H/Af//////z/4D8AAAAAB+/AfAAAAAA/H4DwAAAAAPg/AcAAAAADwHwDAAAAAA4A8AAAAAAAA=="),col:"#f00",cb:negHandler});
|
||||
}
|
||||
footer.push({fillx:1}); // push images to left/right
|
||||
|
@ -421,7 +421,7 @@ function showMessage(msgid, persist) {
|
|||
Bluetooth.println(JSON.stringify(result));
|
||||
replying = false;
|
||||
layout.render();
|
||||
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1,openMusic:openMusic});
|
||||
returnToCheckMessages();
|
||||
})
|
||||
.catch(() => {
|
||||
replying = false;
|
||||
|
@ -435,7 +435,7 @@ function showMessage(msgid, persist) {
|
|||
msg.new = false;
|
||||
cancelReloadTimeout(); // don't auto-reload to clock now
|
||||
Bangle.messageResponse(msg,true);
|
||||
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1,openMusic:openMusic});
|
||||
returnToCheckMessages();
|
||||
}; footer.push({type:"img",src:atob("QRABAAAAAAAAAAOAAAAABgAAA8AAAAADgAAD4AAAAAHgAAPgAAAAAPgAA+AAAAAAfgAD4///////gAPh///////gA+D///////AD4H//////8cPgAAAAAAPw8+AAAAAAAfB/4AAAAAAA8B/gAAAAAABwB+AAAAAAADAB4AAAAAAAAABgAA=="),col:"#0f0",cb:posHandler});
|
||||
}
|
||||
|
||||
|
@ -447,7 +447,7 @@ function showMessage(msgid, persist) {
|
|||
]},
|
||||
{ type:"btn",
|
||||
src:require("messageicons").getImage(msg),
|
||||
col:require("messageicons").getColor(msg, {settings:settings, default:g.theme.fg2}),
|
||||
col:require("messageicons").getColor(msg, {settings, default:g.theme.fg2}),
|
||||
pad: 3, cb:()=>{
|
||||
cancelReloadTimeout(); // don't auto-reload to clock now
|
||||
showMessageSettings(msg);
|
||||
|
@ -476,7 +476,7 @@ function showMessage(msgid, persist) {
|
|||
/* options = {
|
||||
clockIfNoMsg : bool
|
||||
clockIfAllRead : bool
|
||||
showMsgIfUnread : bool
|
||||
ignoreUnread : bool // don't automatically navigate to the first unread message
|
||||
openMusic : bool // open music if it's playing
|
||||
dontStopBuzz : bool // don't stuf buzzing (any time other than the first this is undefined/false)
|
||||
}
|
||||
|
@ -500,7 +500,7 @@ function checkMessages(options) {
|
|||
// we have >0 messages
|
||||
var newMessages = MESSAGES.filter(m=>m.new&&m.id!="music");
|
||||
// If we have a new message, show it
|
||||
if (options.showMsgIfUnread && newMessages.length) {
|
||||
if (!options.ignoreUnread && newMessages.length) {
|
||||
delete newMessages[0].show; // stop us getting stuck here if we're called a second time
|
||||
showMessage(newMessages[0].id, false);
|
||||
// buzz after showMessage, so being busy during layout doesn't affect the buzz pattern
|
||||
|
@ -538,7 +538,7 @@ function checkMessages(options) {
|
|||
}
|
||||
if (img) {
|
||||
var fg = g.getColor(),
|
||||
col = require("messageicons").getColor(msg, {settings:settings, default:fg});
|
||||
col = require("messageicons").getColor(msg, {settings, default:fg});
|
||||
g.setColor(col).drawImage(img, x+24, r.y+24, {rotate:0}) // force centering
|
||||
.setColor(fg); // only color the icon
|
||||
x += 50;
|
||||
|
@ -570,6 +570,17 @@ function checkMessages(options) {
|
|||
});
|
||||
}
|
||||
|
||||
function returnToCheckMessages(clock) {
|
||||
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,ignoreUnread:settings.ignoreUnread,openMusic});
|
||||
}
|
||||
|
||||
function returnToMain() {
|
||||
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,ignoreUnread:1,openMusic:0});
|
||||
}
|
||||
|
||||
function returnToClockIfEmpty() {
|
||||
checkMessages({clockIfNoMsg:1,clockIfAllRead:0,ignoreUnread:1,openMusic});
|
||||
}
|
||||
|
||||
function cancelReloadTimeout() {
|
||||
if (!unreadTimeout) return;
|
||||
|
@ -594,7 +605,7 @@ setTimeout(() => {
|
|||
// only openMusic on launch if music is new, or state=="show" (set by messagesmusic)
|
||||
var musicMsg = MESSAGES.find(m => m.id === "music");
|
||||
checkMessages({
|
||||
clockIfNoMsg: 0, clockIfAllRead: 0, showMsgIfUnread: 1,
|
||||
clockIfNoMsg: 0, clockIfAllRead: 0, ignoreUnread: settings.ignoreUnread,
|
||||
openMusic: ((musicMsg&&musicMsg.new) && settings.openMusic) || (musicMsg&&musicMsg.state=="show"),
|
||||
dontStopBuzz: 1 });
|
||||
}, 10); // if checkMessages wants to 'load', do that
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "messagegui",
|
||||
"name": "Message UI",
|
||||
"shortName": "Messages",
|
||||
"version": "0.82",
|
||||
"version": "0.84",
|
||||
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -6,3 +6,6 @@
|
|||
0.60: Bump version to allow new buzz.js module to be loaded - fixes memory/performance hog when buzz called
|
||||
0.61: Add repeatCalls option to allow different repeat settings for messages vs calls
|
||||
0.62: Add option for driving on left (affects roundabout icons in navigation)
|
||||
0.63: Add option to not open the first unread message
|
||||
0.64: Only load from storage once in settings
|
||||
0.65: Fix settings error introduced by two conflicting changes
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "messages",
|
||||
"name": "Messages",
|
||||
"version": "0.62",
|
||||
"version": "0.65",
|
||||
"description": "Library to handle, load and store message events received from Android/iOS",
|
||||
"icon": "app.png",
|
||||
"type": "module",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
(function(back) {
|
||||
const iconColorModes = ['color', 'mono'];
|
||||
|
||||
function settings() {
|
||||
function loadSettings() {
|
||||
let settings = require('Storage').readJSON("messages.settings.json", true) || {};
|
||||
if (settings.vibrate===undefined) settings.vibrate=":";
|
||||
if (settings.vibrateCalls===undefined) settings.vibrateCalls=":";
|
||||
|
@ -11,6 +11,7 @@
|
|||
if (settings.unreadTimeout===undefined) settings.unreadTimeout=60;
|
||||
if (settings.maxMessages===undefined) settings.maxMessages=3;
|
||||
if (settings.iconColorMode === undefined) settings.iconColorMode = iconColorModes[0];
|
||||
if (settings.ignoreUnread === undefined) settings.ignoreUnread = 0;
|
||||
settings.unlockWatch=!!settings.unlockWatch;
|
||||
settings.openMusic=!!settings.openMusic;
|
||||
settings.maxUnreadTimeout=240;
|
||||
|
@ -18,80 +19,84 @@
|
|||
return settings;
|
||||
}
|
||||
function updateSetting(setting, value) {
|
||||
let settings = require('Storage').readJSON("messages.settings.json", true) || {};
|
||||
settings[setting] = value;
|
||||
require('Storage').writeJSON("messages.settings.json", settings);
|
||||
}
|
||||
var settings = loadSettings();
|
||||
|
||||
var mainmenu = {
|
||||
"" : { "title" : /*LANG*/"Messages" },
|
||||
"< Back" : back,
|
||||
/*LANG*/'Vibrate': require("buzz_menu").pattern(settings().vibrate, v => updateSetting("vibrate", v)),
|
||||
/*LANG*/'Vibrate for calls': require("buzz_menu").pattern(settings().vibrateCalls, v => updateSetting("vibrateCalls", v)),
|
||||
/*LANG*/'Vibrate': require("buzz_menu").pattern(settings.vibrate, v => updateSetting("vibrate", v)),
|
||||
/*LANG*/'Vibrate for calls': require("buzz_menu").pattern(settings.vibrateCalls, v => updateSetting("vibrateCalls", v)),
|
||||
/*LANG*/'Repeat': {
|
||||
value: settings().repeat,
|
||||
value: settings.repeat,
|
||||
min: 0, max: 10,
|
||||
format: v => v?v+"s":/*LANG*/"Off",
|
||||
onchange: v => updateSetting("repeat", v)
|
||||
},
|
||||
/*LANG*/'Repeat for calls': {
|
||||
value: settings().repeatCalls,
|
||||
value: settings.repeatCalls,
|
||||
min: 0, max: 10,
|
||||
format: v => v?v+"s":/*LANG*/"Off",
|
||||
onchange: v => updateSetting("repeatCalls", v)
|
||||
},
|
||||
/*LANG*/'Vibrate timer': {
|
||||
value: settings().vibrateTimeout,
|
||||
min: 0, max: settings().maxUnreadTimeout, step : 10,
|
||||
value: settings.vibrateTimeout,
|
||||
min: 0, max: settings.maxUnreadTimeout, step : 10,
|
||||
format: v => v?v+"s":/*LANG*/"Off",
|
||||
onchange: v => updateSetting("vibrateTimeout", v)
|
||||
},
|
||||
/*LANG*/'Unread timer': {
|
||||
value: settings().unreadTimeout,
|
||||
min: 0, max: settings().maxUnreadTimeout, step : 10,
|
||||
value: settings.unreadTimeout,
|
||||
min: 0, max: settings.maxUnreadTimeout, step : 10,
|
||||
format: v => v?v+"s":/*LANG*/"Off",
|
||||
onchange: v => updateSetting("unreadTimeout", v)
|
||||
},
|
||||
/*LANG*/'Min Font': {
|
||||
value: 0|settings().fontSize,
|
||||
value: 0|settings.fontSize,
|
||||
min: 0, max: 1,
|
||||
format: v => [/*LANG*/"Small",/*LANG*/"Medium"][v],
|
||||
onchange: v => updateSetting("fontSize", v)
|
||||
},
|
||||
/*LANG*/'Auto-Open Unread Msg': {
|
||||
value: !!settings.ignoreUnread,
|
||||
onchange: v => updateSetting("ignoreUnread", v)
|
||||
},
|
||||
/*LANG*/'Auto-Open Music': {
|
||||
value: !!settings().openMusic,
|
||||
value: !!settings.openMusic,
|
||||
onchange: v => updateSetting("openMusic", v)
|
||||
},
|
||||
/*LANG*/'Unlock Watch': {
|
||||
value: !!settings().unlockWatch,
|
||||
value: !!settings.unlockWatch,
|
||||
onchange: v => updateSetting("unlockWatch", v)
|
||||
},
|
||||
/*LANG*/'Flash Icon': {
|
||||
value: !!settings().flash,
|
||||
value: !!settings.flash,
|
||||
onchange: v => updateSetting("flash", v)
|
||||
},
|
||||
/*LANG*/'Quiet mode disables auto-open': {
|
||||
value: !!settings().quietNoAutOpn,
|
||||
value: !!settings.quietNoAutOpn,
|
||||
onchange: v => updateSetting("quietNoAutOpn", v)
|
||||
},
|
||||
/*LANG*/'Disable auto-open': {
|
||||
value: !!settings().noAutOpn,
|
||||
value: !!settings.noAutOpn,
|
||||
onchange: v => updateSetting("noAutOpn", v)
|
||||
},
|
||||
/*LANG*/'Widget messages': {
|
||||
value:0|settings().maxMessages,
|
||||
value:0|settings.maxMessages,
|
||||
min: 0, max: 5,
|
||||
format: v => v ? v :/*LANG*/"Hide",
|
||||
onchange: v => updateSetting("maxMessages", v)
|
||||
},
|
||||
/*LANG*/'Icon color mode': {
|
||||
value: Math.max(0,iconColorModes.indexOf(settings().iconColorMode)),
|
||||
value: Math.max(0,iconColorModes.indexOf(settings.iconColorMode)),
|
||||
min: 0, max: iconColorModes.length - 1,
|
||||
format: v => iconColorModes[v],
|
||||
onchange: v => updateSetting("iconColorMode", iconColorModes[v])
|
||||
},
|
||||
/*LANG*/'Car driver pos': { // used by messagegui
|
||||
value:!!settings().carIsRHD,
|
||||
value:!!settings.carIsRHD,
|
||||
format: v => v ? /*LANG*/"Right" :/*LANG*/"Left",
|
||||
onchange: v => updateSetting("carIsRHD", v)
|
||||
},
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<head>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.3/dist/leaflet.css" />
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet-geosearch@3.6.0/dist/geosearch.css"/>
|
||||
</head>
|
||||
<style>
|
||||
|
@ -34,7 +35,9 @@
|
|||
</div>
|
||||
<div id="controls">
|
||||
<span id="select-hint">Click the map to select a location</span>
|
||||
<button id="select" class="btn btn-primary" style="display:none">Save</button><br/>
|
||||
<button id="locate-me" class="btn" title="Locate me">⛯</button>
|
||||
<button id="locate-marker" class="btn" style="display:none" title="Locate marker"><i class="icon icon-location"></i></button>
|
||||
<button id="select" class="btn btn-primary" style="display:none" title="Save to device">Save</button><br/>
|
||||
</div>
|
||||
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
|
@ -76,18 +79,36 @@
|
|||
map.removeLayer(marker);
|
||||
}
|
||||
marker = new L.marker(latlon).addTo(map);
|
||||
|
||||
document.getElementById("select-hint").style.display="none";
|
||||
document.getElementById("select").style.display="";
|
||||
document.getElementById("locate-marker").style.display="";
|
||||
}
|
||||
|
||||
map.on('click', function(e){
|
||||
setPosition(e.latlng);
|
||||
});
|
||||
|
||||
function convertMapToFile(map) {
|
||||
return {lat: map.lat, lon: map.lng};
|
||||
}
|
||||
|
||||
function convertFileToMap(file) {
|
||||
return {lat: file.lat, lng: file.lon};
|
||||
}
|
||||
|
||||
document.getElementById("locate-me").addEventListener("click", function() {
|
||||
map.locate({setView: true, maxZoom: 16, enableHighAccuracy:true});
|
||||
});
|
||||
|
||||
document.getElementById("locate-marker").addEventListener("click", function() {
|
||||
if (latlon && latlon.lng != null && latlon.lat != null) {
|
||||
map.setView(latlon);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("select").addEventListener("click", function() {
|
||||
let settings = {}; // {"lat":48.8566,"lon":2.3522,"location":"Paris"}
|
||||
settings.lat = latlon.lat;
|
||||
settings.lon = latlon.lng;
|
||||
let settings = convertMapToFile(latlon); // {"lat":48.8566,"lon":2.3522,"location":"Paris"}
|
||||
settings.location = "custom";
|
||||
Util.showModal("Saving...");
|
||||
Util.writeStorage("mylocation.json", JSON.stringify(settings), ()=>{
|
||||
|
@ -101,7 +122,7 @@
|
|||
Util.readStorageJSON("mylocation.json", function(data) {
|
||||
if (data===undefined) return; // no file
|
||||
try {
|
||||
setPosition(data);
|
||||
setPosition(convertFileToMap(data));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
|
|
@ -15,3 +15,4 @@
|
|||
0.11: Skip double buffering, use 240x240 size
|
||||
0.12: Fix swipe direction (#800)
|
||||
0.13: Bangle.js 2 support
|
||||
0.14: Brighter Christmas tree on Bangle.js 2
|
|
@ -16,7 +16,7 @@
|
|||
</div>
|
||||
<p><button id="try" class="btn">Try in Emulator</button></p>
|
||||
<p><button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
<p>This is currently Christmas-themed, but more themes will be added in the future.</p>
|
||||
<p>To turn the Bangle off so the Welcome screen runs at startup go to <code>Settings -> Apps -> My Welcome</code> and choose <code>Turn off & run next</code></p>
|
||||
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
|
||||
|
@ -76,7 +76,7 @@ var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/
|
|||
// if (style=="Christmas")
|
||||
return `(function() {
|
||||
var isnow = require("heatshrink").decompress(atob("jEagQWTgfAAocf+gFDh4FDiARBggVB3AFBl3Agf8jfkn/AgX/v/9/+Agfv/2//YrBgfwh4wCgfghYFJCIYdFFIw1EIIpNFL44FFOIoAP"));
|
||||
var itree = require("heatshrink").decompress(atob("mtWxH+ADHHDTI0aGuXH5vNGmhqvTYIzBGtoxF6fTG4g4oGgQyBAAZssGoI0Ga1g1FGdo01ZgIAEGmHHNoLSuAAN/rdb0YFBGlgCBGYIABA4YArGYY1CGn4znAAM6GeVd5PQ5Iyurc/vQ0oGZFAn+d4XC3d5GddiGYIEBy+7zoEBGlFhoEcsQ9GT08+oFk1mkGdaVBMgNArnJ6/KzswGs/J6GlrlbqtbvPC5PCy8wGohniMIPJvIpCqmX3e7vI0BqhqlMIY0DqhtBqoEBa0xgBMIIoEqoABGQwzfsIhBv4qHABM50vQGjg1CGaN66DoBGt1ioGd5LoBGjo1PGYNhvLoCa7wnBqgvGA4YzCAgN5GUAsCqoDBmAHCAYU/wPQ0oSDGcBiDqkwAYcxoFd5PX6GdGjrIIqtUAAc3jk5vPC4fCy5pef5I2BTQMcnAHBy+7y95T0oADnFk1ekBpI2aGRUin7NGAA9hsIzVsIgHTAKZBZoPJ5LNDGhBpXGolcwOsrtcA4TNB3bNDGb/+sVin9AoGe6HX5InEvN/TkP+5XQwM/sRsBzqWB4QuKGjvC6HQ4QdDvKWBZYMwmAuHmFUCYNbqibX3fD5O7qolEZQQ0FBwgKDqgJBGiphEDwNUEgJbBFIQqCAgYOCB4IzCnE6GyhYFGoQnDABYzGAAQ1UAAo2NBoQSBnOB0t/Gjo2EABIPCoGe6HX4QzTGRIAEqtVF4QEBBQc4oE4y/J5PCvIxeABk/oADBvO73eXTyAyZMwM/Awd5vIOFGslAr2Av4PLNcU/jmA6HX5I1KasFcn8dTIOd5PJ4SZGGiNhAAIyNn0ckU+ZYe7AAJpJEYJnNGZk+n9kw9cBAcwGoN5aZg1JJJQABm8/oEjoDKC5ALCrUwqh/NrvQ6HDGp04n9doEdoE/sQJBZQZhCqgABGZk6zw0K/1dnVAoNAFwOlCYL1FubJBy4GCGh1AnOX4XC3YzHFYOeCgdV5PQ5OdD4rKBqqYNGYlbv+X3edGY3CGgKMDAAO7JAJgDAClcr2BEYgADaIZ0DL4uXGbDuB6HX5I1GsP+sNhOgWXIhBmWd4Od5PK4TwFGIJoBAYI2BAD0/jlcQoO7AAJaEGQQADGr0/sjNEvOdAoZmDGgw2ZsVAkeAZpQACGZI2VsU/kVGn1bZoPJZogpGGhA4GfRYwBoGC1mlBQbNFFoo0JNxAGCEod/wM6oFAn9iv/J6/Kzo1Ey9/MZQAKCg4GCFgTDEvPCSwI0BC5I0RN4ocEYYPQ5OdHgeXSwTFKGaJyKFYPC3f+MIdbpzFLAD4zB/1OqtbqtOGgYArGAIADGl9UAAI0wGQN5GoQ0vvIABGoI0uGYQABqo0zNOg0uaQY0/GllOGn40//w="));
|
||||
var itree = require("heatshrink").decompress(atob("mtWwcBkmSpICFnAIHARV2CKFJk1sEyNO7cSEyFt22EEx2d23bCgPYChsnCIIUBxI7OEyKJCEyMk9o7BO6A7CEx+TEwKzQndhwxiQuMpklxHaGGjBiQkoDBEx+So0YsOZEyNJ//JJp9hy/+KBs5suWpO5kmEEx/bjVJwJNMEwNN0uWrI7NrN3/8ZsuRMRmW7d7/+CrNkExdP/lhw+ekOWggmLz/8hMpk0IkDIMn15O4QCCxIUKsmZkGCEwVYWBYjDAQWDzgUJqxxBAwdZnP/HZOVywmEw96vDsLEYLIBrM30mQKB+XvNgTxWUyUJOgMJn/+pLvKJoUIsGDj/5wwmJ8mSpCeBhM3k+RkgmJnBNDycYpMGjBiJpwDBEwP8wVZkuWogUHkomCsnf/wFCR4LFKydttPkDYKhBsTFJr9140SoImLpMpm3n/tky0JZAVkEw+f4dZtPmd4YUBrAmHz/1y2SomGd4OQjMgEw+Tk+YEYUhy0ZsAFBMQ8mpMChEgwEJsECgDLBprvGpxKDBwIXBAoNg4zIHdgcIgEACgOCv9keIIUFdgYCEs//zA7FyYDCHAQCDt1/gJNFrAmIm/GyVhxLXFrIbEhAwBtMl0zIFktlEw0Z/IFDZAq/ByxXE73/8oyBso7EybjBEweHttp/4FBCgJ3EnNkdIQpBm3WrVJCIMly1EEwkYCIMYsnfrDsBzAICsOBEwVJsmSjMgyaYB61IEYNJsoPB//JCgNGcYPXtu1w2ePoMZQAef/xQCy3btv/8UJlMkrLFBeoImB3MkHYX+OgOf8LIHsObjVIEwOZ/sZkMkSYLsDQgf9PQLIBqV5DoNJEw9g4/8UINY8uUzLFGAQdN5fkVQORnIRBYogCEs3WiVBBwLsEAQYmCkuTvtl0nZd4TFBCIQmFjux/6hBd4Y7CFI0m7dT/LsB8uWGINWooRFunf/o4ByA4BkqJBkniKANkp1ZtrLBt/+rNpk1ZsR/EKANEAQMf+OWj15BAdESgopBktln+xHAKnBOIOUVJFJrVJh1ZsBEBOIIRIAQUwyGCgAOKCg1hgIROAQNEiECEx8k8OGgg7QfYMJEx+Td4J0NAQdJ0hiQp0YTwIUP4MmyBiQjFhwJiQgmaoA7QsOGiA7PpkStLaIoAA="));
|
||||
var W=g.getWidth(),H=g.getHeight();
|
||||
var titleFont = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
|
||||
var flakes = [];
|
||||
|
@ -112,6 +112,8 @@ var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/
|
|||
setInterval(draw,50);
|
||||
})();
|
||||
`;
|
||||
// or an 8 bit tree, but 3 bit (above) renders better on Bangle.js 2
|
||||
// var itree = require("heatshrink").decompress(atob("mtWxH+ADHHDTI0aGuXH5vNGmhqvTYIzBGtoxF6fTG4g4oGgQyBAAZssGoI0Ga1g1FGdo01ZgIAEGmHHNoLSuAAN/rdb0YFBGlgCBGYIABA4YArGYY1CGn4znAAM6GeVd5PQ5Iyurc/vQ0oGZFAn+d4XC3d5GddiGYIEBy+7zoEBGlFhoEcsQ9GT08+oFk1mkGdaVBMgNArnJ6/KzswGs/J6GlrlbqtbvPC5PCy8wGohniMIPJvIpCqmX3e7vI0BqhqlMIY0DqhtBqoEBa0xgBMIIoEqoABGQwzfsIhBv4qHABM50vQGjg1CGaN66DoBGt1ioGd5LoBGjo1PGYNhvLoCa7wnBqgvGA4YzCAgN5GUAsCqoDBmAHCAYU/wPQ0oSDGcBiDqkwAYcxoFd5PX6GdGjrIIqtUAAc3jk5vPC4fCy5pef5I2BTQMcnAHBy+7y95T0oADnFk1ekBpI2aGRUin7NGAA9hsIzVsIgHTAKZBZoPJ5LNDGhBpXGolcwOsrtcA4TNB3bNDGb/+sVin9AoGe6HX5InEvN/TkP+5XQwM/sRsBzqWB4QuKGjvC6HQ4QdDvKWBZYMwmAuHmFUCYNbqibX3fD5O7qolEZQQ0FBwgKDqgJBGiphEDwNUEgJbBFIQqCAgYOCB4IzCnE6GyhYFGoQnDABYzGAAQ1UAAo2NBoQSBnOB0t/Gjo2EABIPCoGe6HX4QzTGRIAEqtVF4QEBBQc4oE4y/J5PCvIxeABk/oADBvO73eXTyAyZMwM/Awd5vIOFGslAr2Av4PLNcU/jmA6HX5I1KasFcn8dTIOd5PJ4SZGGiNhAAIyNn0ckU+ZYe7AAJpJEYJnNGZk+n9kw9cBAcwGoN5aZg1JJJQABm8/oEjoDKC5ALCrUwqh/NrvQ6HDGp04n9doEdoE/sQJBZQZhCqgABGZk6zw0K/1dnVAoNAFwOlCYL1FubJBy4GCGh1AnOX4XC3YzHFYOeCgdV5PQ5OdD4rKBqqYNGYlbv+X3edGY3CGgKMDAAO7JAJgDAClcr2BEYgADaIZ0DL4uXGbDuB6HX5I1GsP+sNhOgWXIhBmWd4Od5PK4TwFGIJoBAYI2BAD0/jlcQoO7AAJaEGQQADGr0/sjNEvOdAoZmDGgw2ZsVAkeAZpQACGZI2VsU/kVGn1bZoPJZogpGGhA4GfRYwBoGC1mlBQbNFFoo0JNxAGCEod/wM6oFAn9iv/J6/Kzo1Ey9/MZQAKCg4GCFgTDEvPCSwI0BC5I0RN4ocEYYPQ5OdHgeXSwTFKGaJyKFYPC3f+MIdbpzFLAD4zB/1OqtbqtOGgYArGAIADGl9UAAI0wGQN5GoQ0vvIABGoI0uGYQABqo0zNOg0uaQY0/GllOGn40//w="))
|
||||
}
|
||||
// when 'try' is clicked, load the emulator...
|
||||
document.getElementById("try").addEventListener("click", function() {
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
"id": "mywelcome",
|
||||
"name": "Customised Welcome",
|
||||
"shortName": "My Welcome",
|
||||
"version": "0.13",
|
||||
"description": "Appears at first boot and explains how to use Bangle.js. Like 'Welcome', but can be customised with a greeting",
|
||||
"version": "0.14",
|
||||
"description": "Appears at first boot and explains how to use Bangle.js. Like 'Welcome', but can be customised with a greeting for Christmas or Birthdays!",
|
||||
"icon": "app.png",
|
||||
"tags": "start,welcome",
|
||||
"tags": "start,welcome,birthday,christmas,xmas",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"provides_features" : ["welcome"],
|
||||
"custom": "custom.html",
|
||||
"screenshots": [{"url":"bangle1-customized-welcome-screenshot.png"}],
|
||||
"storage": [
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Keep advertising when connected
|
||||
0.03: Use ble_advert module to work with other BLE advert apps
|
||||
0.04: Fix installation after broken in 0.03 (fix #3667)
|
||||
|
|
|
@ -46,7 +46,7 @@ require("ble_advert").push(adv, {whenConnected: true, interval: 1000}); // adver
|
|||
// send finished app
|
||||
sendCustomizedApp({
|
||||
storage:[
|
||||
{name:"openhaystack.boot.js", content:appJS},
|
||||
{name:"openhaystack.boot.js", content:appJS, url:"openhaystack.boot.js"/* not a real URL but this lets the App Loader know it's a JS file which should be parsed */},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "openhaystack",
|
||||
"name": "OpenHaystack (AirTag)",
|
||||
"icon": "icon.png",
|
||||
"version":"0.03",
|
||||
"version":"0.04",
|
||||
"description": "Copy a base64 key from https://github.com/seemoo-lab/openhaystack and make your Bangle.js trackable as if it's an AirTag",
|
||||
"tags": "openhaystack,bluetooth,ble,tracking,airtag",
|
||||
"type": "bootloader",
|
||||
|
|
|
@ -36,3 +36,4 @@
|
|||
0.29: Keep exit at bottom of menu
|
||||
Speed up latLonToXY for track rendering
|
||||
0.30: Minor code improvements
|
||||
0.31: Reset draw colours before rendering (to allow black and white maps)
|
|
@ -45,10 +45,12 @@
|
|||
<div id="map">
|
||||
</div>
|
||||
<div id="controls">
|
||||
<div style="display:inline-block;text-align:left;vertical-align: top;" id="3bitdiv">
|
||||
<input type="checkbox" id="3bit"></input><span>3 bit</span>
|
||||
<br>
|
||||
<input type="checkbox" id="preview"><span>Preview</span>
|
||||
<div class="form-group" style="display:inline-block;">
|
||||
<select class="form-select" id="mapStyle">
|
||||
<option value="3bit" selected>3 bit</option>
|
||||
<option value="8bit">8 bit</option>
|
||||
<option value="1bit">1 bit</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="display:inline-block;">
|
||||
<select class="form-select" id="mapSize">
|
||||
|
@ -94,20 +96,47 @@ TODO:
|
|||
var TILELAYER = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
var PREVIEWTILELAYER = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
|
||||
MAPSTYLES = {
|
||||
"3bit" : {
|
||||
layer : 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
attribution: 'Map data © <a href="https://openstreetmap.org/copyright">OpenStreetMap</a> contributors</a>',
|
||||
img : { compression:false, output:"raw", mode:"3bit",diffusion:"bayer2"}
|
||||
}, "8bit" : {
|
||||
layer : 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
attribution: 'Map data © <a href="https://openstreetmap.org/copyright">OpenStreetMap</a> contributors</a>',
|
||||
img : { compression:false, output:"raw", mode:"web" }
|
||||
}, "1bit" : {
|
||||
layer : 'https://tiles.stadiamaps.com/tiles/stamen_toner/{z}/{x}/{y}{r}.png',
|
||||
attribution: '© <a href="https://www.stadiamaps.com/" target="_blank">Stadia Maps</a> © <a href="https://www.stamen.com/" target="_blank">Stamen Design</a> © <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
img : { compression:false, output:"raw", mode:"1bit",inverted:true }
|
||||
}
|
||||
};
|
||||
|
||||
var loadedMaps = [];
|
||||
|
||||
// Tiles used for Bangle.js itself
|
||||
var bangleTileLayer = L.tileLayer(TILELAYER, {
|
||||
maxZoom: 18,
|
||||
attribution: 'Map data © <a href="https://openstreetmap.org/copyright">OpenStreetMap</a> contributors</a>'
|
||||
});
|
||||
var bangleTileLayer;
|
||||
// Tiles used for the may the user sees (faster)
|
||||
var previewTileLayer = L.tileLayer(PREVIEWTILELAYER, {
|
||||
maxZoom: 18,
|
||||
attribution: 'Map data © <a href="https://openstreetmap.org/copyright">OpenStreetMap</a> contributors</a>'
|
||||
});
|
||||
var previewTileLayer;
|
||||
// Currently selected version of MAPSTYLES
|
||||
var currentStyle;
|
||||
|
||||
// Could optionally overlay trails: https://wiki.openstreetmap.org/wiki/Tiles
|
||||
|
||||
|
||||
function createMapLayers(style) {
|
||||
currentStyle = style;
|
||||
bangleTileLayer = L.tileLayer(style.layer, {
|
||||
maxZoom: 18,
|
||||
attribution: style.attribution
|
||||
});
|
||||
previewTileLayer = L.tileLayer(style.layer, {
|
||||
maxZoom: 18,
|
||||
attribution: style.attribution
|
||||
});
|
||||
}
|
||||
createMapLayers(MAPSTYLES["3bit"]);
|
||||
|
||||
// Create map and try and set the location to where the browser thinks we are
|
||||
var map = L.map('map').locate({setView: true, maxZoom: 16, enableHighAccuracy:true});
|
||||
previewTileLayer.addTo(map);
|
||||
|
@ -130,10 +159,11 @@ TODO:
|
|||
if (device && device.info && device.info.g) {
|
||||
// On 3 bit devices, 3 bit is the best way
|
||||
// still allow 8 bit as it makes zoom out much nicer
|
||||
if (device.info.g.bpp==3) {
|
||||
// ... but lets just default to 3 bit anyway now
|
||||
/*if (device.info.g.bpp==3) {
|
||||
document.getElementById("3bit").checked = true;
|
||||
//document.getElementById("3bitdiv").style = "display:none";
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
showLoadedMaps();
|
||||
|
@ -257,17 +287,7 @@ TODO:
|
|||
|
||||
// convert canvas into an actual tiled image file
|
||||
function tilesLoaded(ctx, width, height, mapImageFile) {
|
||||
var options = {
|
||||
compression:false, output:"raw",
|
||||
mode:"web"
|
||||
};
|
||||
if (document.getElementById("3bit").checked) {
|
||||
options = {
|
||||
compression:false, output:"raw",
|
||||
mode:"3bit",
|
||||
diffusion:"bayer2"
|
||||
};
|
||||
}
|
||||
var options = currentStyle.img; // compression options
|
||||
/* Go through all the data beforehand and
|
||||
turn the saturation up to maximum, so if thresholded to 3 bits it
|
||||
works a lot better */
|
||||
|
@ -289,9 +309,9 @@ TODO:
|
|||
options.width = TILESIZE;
|
||||
options.height = TILESIZE;
|
||||
var imgstr = imageconverter.RGBAtoString(rgba, options);
|
||||
if (document.getElementById("preview").checked) {
|
||||
/*if (document.getElementById("preview").checked) {
|
||||
ctx.putImageData(imageData,x*TILESIZE, y*TILESIZE); // write preview
|
||||
}
|
||||
}*/
|
||||
/*var compress = 'require("heatshrink").decompress('
|
||||
if (!imgstr.startsWith(compress)) throw "Data in wrong format";
|
||||
imgstr = imgstr.slice(compress.length,-1);*/
|
||||
|
@ -408,6 +428,14 @@ TODO:
|
|||
});
|
||||
});
|
||||
|
||||
document.getElementById("mapStyle").addEventListener("click", function() {
|
||||
var style = document.getElementById("mapStyle").value;
|
||||
if (!style in MAPSTYLES) return;
|
||||
map.removeLayer(previewTileLayer);
|
||||
createMapLayers(MAPSTYLES[style]);
|
||||
previewTileLayer.addTo(map);
|
||||
});
|
||||
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
Util.showModal("Uploading...");
|
||||
let promise = Promise.resolve();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "openstmap",
|
||||
"name": "OpenStreetMap",
|
||||
"shortName": "OpenStMap",
|
||||
"version": "0.30",
|
||||
"version": "0.31",
|
||||
"description": "Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are. Once installed this also adds map functionality to `GPS Recorder` and `Recorder` apps",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -73,6 +73,7 @@ exports.draw = function() {
|
|||
}
|
||||
var mx = g.getWidth();
|
||||
var my = g.getHeight();
|
||||
g.setColor(g.theme.fg).setBgColor(g.theme.bg); // reset draw colours
|
||||
for (var x=ox,ttx=tx; x<mx && ttx<map.w; x+=s,ttx++) {
|
||||
for (var y=oy,tty=ty;y<my && tty<map.h;y+=s,tty++) {
|
||||
o.frame = ttx+(tty*map.w);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
0.01: attempt to import
|
||||
0.02: Minor code improvements
|
||||
0.03: Minor code improvements
|
||||
0.10: Restart, start from "andark" adding astronomical features to it
|
||||
|
|
|
@ -5,21 +5,4 @@ Astronomical clock.
|
|||
Written by: [Pavel Machek](https://github.com/pavelmachek)
|
||||
|
||||
The plan is to have an (analog) astronomical clock with a lot of
|
||||
information on single dial.
|
||||
|
||||
It continuously displays information that can be obtained "cheaply",
|
||||
that is current time, sunset/sunrise times, battery status and
|
||||
altitude. One-second updates with useful compass can be activated by
|
||||
tapping bottom right corner.
|
||||
|
||||
Display is split in three rings. Outside ring is for time-based data
|
||||
with base of one week, and for non time-based data. Black dot
|
||||
indicates day of week. Green foot indicates number of steps taken, red
|
||||
battery symbol indicates remaining charge, black thermometer symbol
|
||||
represents temperature, and black ruler symbol indicates
|
||||
altitude. Number in bottom left corner is day of month.
|
||||
|
||||
In the middle ring, hour-based data are displayed. Black dot indicates
|
||||
current hour, yellow symbols indicate sunset and sunrise, and black
|
||||
symbols indicate moonset and moonrise.
|
||||
|
||||
information on single dial. Thanks a lot to "Dark Analog Clock".
|
||||
|
|
|
@ -1,405 +1,277 @@
|
|||
const SunCalc = require("suncalc"); // from modules folder
|
||||
/* sun version 0.0.3 */
|
||||
let sun = {
|
||||
SunCalc: null,
|
||||
lat: 50,
|
||||
lon: 14,
|
||||
rise: 0, /* Unix time of sunrise/sunset */
|
||||
set: 0,
|
||||
init: function() {
|
||||
try {
|
||||
this.SunCalc = require("suncalc"); // from modules folder
|
||||
} catch (e) {
|
||||
print("Require error", e);
|
||||
}
|
||||
print("Have suncalc: ", this.SunCalc);
|
||||
},
|
||||
sunPos: function() {
|
||||
let d = new Date();
|
||||
if (!this.SunCalc) {
|
||||
let sun = {};
|
||||
sun.azimuth = 175;
|
||||
sun.altitude = 15;
|
||||
return sun;
|
||||
}
|
||||
let sun = this.SunCalc.getPosition(d, this.lat, this.lon);
|
||||
print(sun.azimuth, sun.altitude);
|
||||
return sun;
|
||||
},
|
||||
sunTime: function() {
|
||||
let d = new Date();
|
||||
if (!this.SunCalc) {
|
||||
let sun = {};
|
||||
sun.sunrise = d;
|
||||
sun.sunset = d;
|
||||
return sun;
|
||||
}
|
||||
let sun = this.SunCalc.getTimes(d, this.lat, this.lon);
|
||||
return sun;
|
||||
},
|
||||
adj: function (x) {
|
||||
if (x < 0)
|
||||
return x + 24*60*60;
|
||||
return x;
|
||||
},
|
||||
toSunrise: function () {
|
||||
return this.adj(this.rise - getTime());
|
||||
},
|
||||
toSunset: function () {
|
||||
return this.adj(this.set - getTime());
|
||||
},
|
||||
update: function () {
|
||||
let t = this.sunTime();
|
||||
this.rise = t.sunrise.getTime() / 1000;
|
||||
this.set = t.sunset.getTime() / 1000;
|
||||
},
|
||||
// < 0 : next is sunrise, in abs(ret) seconds
|
||||
// > 0
|
||||
getNext: function () {
|
||||
let rise = this.toSunrise();
|
||||
let set = this.toSunset();
|
||||
if (rise < set) {
|
||||
return -rise;
|
||||
}
|
||||
return set;
|
||||
// set = set / 60;
|
||||
// return s + (set / 60).toFixed(0) + ":" + (set % 60).toFixed(0);
|
||||
},
|
||||
};
|
||||
|
||||
// ################################################################################
|
||||
sun.init();
|
||||
|
||||
let ScreenWidth = g.getWidth(), CenterX = ScreenWidth/2;
|
||||
let ScreenHeight = g.getHeight(), CenterY = ScreenHeight/2;
|
||||
let outerRadius = Math.min(CenterX,CenterY) * 0.9;
|
||||
const defaultSettings = {
|
||||
loadWidgets : false,
|
||||
textAboveHands : false,
|
||||
shortHrHand : true
|
||||
};
|
||||
const white = 0;
|
||||
const settings = Object.assign(defaultSettings, require('Storage').readJSON('andark.json',1)||{});
|
||||
|
||||
const lat = 50.1;
|
||||
const lon = 14.45;
|
||||
const c={"x":g.getWidth()/2,"y":g.getHeight()/2};
|
||||
|
||||
const h = g.getHeight();
|
||||
const w = g.getWidth();
|
||||
const sm = 15;
|
||||
var altitude, temperature;
|
||||
const zahlpos=(function() {
|
||||
let z=[];
|
||||
let sk=1;
|
||||
for(let i=-10;i<50;i+=5){
|
||||
let win=i*2*Math.PI/60;
|
||||
let xsk =c.x+2+Math.cos(win)*(c.x-10),
|
||||
ysk =c.y+2+Math.sin(win)*(c.x-10);
|
||||
if(sk==3){xsk-=10;}
|
||||
if(sk==6){ysk-=10;}
|
||||
if(sk==9){xsk+=10;}
|
||||
if(sk==12){ysk+=10;}
|
||||
if(sk==10){xsk+=3;}
|
||||
z.push([sk,xsk,ysk]);
|
||||
sk+=1;
|
||||
}
|
||||
return z;
|
||||
})();
|
||||
|
||||
var img_north = Graphics.createImage(`
|
||||
X
|
||||
XXX
|
||||
XXX
|
||||
X XXX
|
||||
X XXX
|
||||
X XXXX
|
||||
X XXXX
|
||||
X XXXXX
|
||||
X XXXXX
|
||||
XXXXXXXXX
|
||||
`);
|
||||
let unlock = false;
|
||||
|
||||
var img_sunrise = Graphics.createImage(`
|
||||
XXX
|
||||
XXXXX
|
||||
XXXXXXXXX
|
||||
`);
|
||||
function zeiger(len,dia,tim){
|
||||
const x=c.x+ Math.cos(tim)*len/2,
|
||||
y=c.y + Math.sin(tim)*len/2,
|
||||
d={"d":3,"x":dia/2*Math.cos(tim+Math.PI/2),"y":dia/2*Math.sin(tim+Math.PI/2)},
|
||||
pol=[c.x-d.x,c.y-d.y,c.x+d.x,c.y+d.y,x+d.x,y+d.y,x-d.x,y-d.y];
|
||||
return pol;
|
||||
}
|
||||
|
||||
var img_moonrise = Graphics.createImage(`
|
||||
XXX
|
||||
XX X
|
||||
XXXXXXXXX
|
||||
`);
|
||||
function drawHands(d) {
|
||||
let m=d.getMinutes(), h=d.getHours(), s=d.getSeconds();
|
||||
g.setColor(white,white,white);
|
||||
|
||||
var img_altitude = Graphics.createImage(`
|
||||
X X
|
||||
X X X
|
||||
XXXXXXXXX
|
||||
X X X
|
||||
X X
|
||||
`);
|
||||
if(h>12){
|
||||
h=h-12;
|
||||
}
|
||||
//calculates the position of the minute, second and hour hand
|
||||
h=2*Math.PI/12*(h+m/60)-Math.PI/2;
|
||||
//more accurate
|
||||
//m=2*Math.PI/60*(m+s/60)-Math.PI/2;
|
||||
m=2*Math.PI/60*(m)-Math.PI/2;
|
||||
|
||||
var img_temperature = Graphics.createImage(`
|
||||
XX
|
||||
XXXXXXXX
|
||||
X XX
|
||||
XXXXXXXX
|
||||
XX
|
||||
`);
|
||||
s=2*Math.PI/60*s-Math.PI/2;
|
||||
//g.setColor(1,0,0);
|
||||
const hz = zeiger(settings.shortHrHand?88:100,5,h);
|
||||
g.fillPoly(hz,true);
|
||||
//g.setColor(1,1,1);
|
||||
const minz = zeiger(150,5,m);
|
||||
g.fillPoly(minz,true);
|
||||
if (unlock){
|
||||
const sekz = zeiger(150,2,s);
|
||||
g.fillPoly(sekz,true);
|
||||
}
|
||||
g.fillCircle(c.x,c.y,4);
|
||||
}
|
||||
|
||||
var img_battery = Graphics.createImage(`
|
||||
XXXXXXXX
|
||||
XXX X
|
||||
XXXX XX
|
||||
XXXXX X
|
||||
XXXXXXXX
|
||||
`);
|
||||
function setColor() {
|
||||
g.setBgColor(!white,!white,!white);
|
||||
g.setColor(white,white,white);
|
||||
}
|
||||
|
||||
var img_step = Graphics.createImage(`
|
||||
XXX
|
||||
XX XXXXX
|
||||
XXX XXXXX
|
||||
XXX XXXXX
|
||||
XX XXXX
|
||||
`);
|
||||
function drawText(d) {
|
||||
g.setFont("Vector",20);
|
||||
//let dateStr = require("locale").date(d);
|
||||
//g.drawString(dateStr, c.x, c.y+20, true);
|
||||
let bat = E.getBattery();
|
||||
let batStr = Math.round(bat/5)*5+"%";
|
||||
if (Bangle.isCharging()) {
|
||||
g.setBgColor(1,0,0);
|
||||
}
|
||||
if (bat < 30)
|
||||
g.drawString(batStr, c.x, c.y+40, true);
|
||||
}
|
||||
|
||||
var img_sun = Graphics.createImage(`
|
||||
X X
|
||||
XXX
|
||||
XXXXXXX
|
||||
XXXXXXXXX
|
||||
XXXXXXXXX
|
||||
XXXXXXXXX
|
||||
XXXXXXX
|
||||
XXX
|
||||
X X
|
||||
`);
|
||||
|
||||
var img_moon = Graphics.createImage(`
|
||||
XXX
|
||||
XX XXX
|
||||
X XXXX
|
||||
X XXX
|
||||
X XXX
|
||||
X XXX
|
||||
X XXXX
|
||||
X XXX
|
||||
XXX
|
||||
`);
|
||||
|
||||
let use_compass = 0;
|
||||
function drawNumbers(d) {
|
||||
let hour = d.getHours();
|
||||
if (d.getMinutes() > 30) {
|
||||
hour += 1;
|
||||
}
|
||||
let day = d.getDate();
|
||||
if (day > 12) {
|
||||
day = day % 10;
|
||||
if (!day)
|
||||
day = 10;
|
||||
}
|
||||
//draws the numbers on the screen
|
||||
for(let i = 0;i<12;i++){
|
||||
let on = false;
|
||||
let j = i+1;
|
||||
g.setFont("Vector",20);
|
||||
if (j == day) {
|
||||
on = true;
|
||||
g.setFont("Vector",29);
|
||||
}
|
||||
if ((j % 12) == (hour % 12))
|
||||
on = true;
|
||||
setColor();
|
||||
if (!on)
|
||||
g.setColor(white/2, !white, white);
|
||||
if (1 || on)
|
||||
g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2],true);
|
||||
}
|
||||
}
|
||||
|
||||
function draw(){
|
||||
drawBorders();
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
function radA(p) { return p*(Math.PI*2); }
|
||||
function radD(d) { return d*(h/2); }
|
||||
|
||||
function radX(p, d) {
|
||||
let a = radA(p);
|
||||
return h/2 + Math.sin(a)*radD(d);
|
||||
}
|
||||
|
||||
function radY(p, d) {
|
||||
let a = radA(p);
|
||||
return w/2 - Math.cos(a)*radD(d);
|
||||
}
|
||||
|
||||
function fracHour(d) {
|
||||
let hour = d.getHours();
|
||||
let min = d.getMinutes();
|
||||
hour = hour + min/60;
|
||||
if (hour > 12)
|
||||
hour -= 12;
|
||||
return hour;
|
||||
}
|
||||
|
||||
let HourHandLength = outerRadius * 0.5;
|
||||
let HourHandWidth = 2*3, halfHourHandWidth = HourHandWidth/2;
|
||||
|
||||
let MinuteHandLength = outerRadius * 0.7;
|
||||
let MinuteHandWidth = 2*2, halfMinuteHandWidth = MinuteHandWidth/2;
|
||||
|
||||
let SecondHandLength = outerRadius * 0.9;
|
||||
let SecondHandOffset = 6;
|
||||
|
||||
let twoPi = 2*Math.PI;
|
||||
let Pi = Math.PI;
|
||||
|
||||
let sin = Math.sin, cos = Math.cos;
|
||||
|
||||
let HourHandPolygon = [
|
||||
-halfHourHandWidth,halfHourHandWidth,
|
||||
-halfHourHandWidth,halfHourHandWidth-HourHandLength,
|
||||
halfHourHandWidth,halfHourHandWidth-HourHandLength,
|
||||
halfHourHandWidth,halfHourHandWidth,
|
||||
];
|
||||
|
||||
let MinuteHandPolygon = [
|
||||
-halfMinuteHandWidth,halfMinuteHandWidth,
|
||||
-halfMinuteHandWidth,halfMinuteHandWidth-MinuteHandLength,
|
||||
halfMinuteHandWidth,halfMinuteHandWidth-MinuteHandLength,
|
||||
halfMinuteHandWidth,halfMinuteHandWidth,
|
||||
];
|
||||
|
||||
/**** drawClockFace ****/
|
||||
|
||||
function drawClockFace () {
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont('Vector', 22);
|
||||
|
||||
g.setFontAlign(0,-1);
|
||||
g.drawString('12', CenterX,CenterY-outerRadius);
|
||||
|
||||
g.setFontAlign(1,0);
|
||||
g.drawString('3', CenterX+outerRadius,CenterY);
|
||||
|
||||
g.setFontAlign(0,1);
|
||||
g.drawString('6', CenterX,CenterY+outerRadius);
|
||||
|
||||
g.setFontAlign(-1,0);
|
||||
g.drawString('9', CenterX-outerRadius,CenterY);
|
||||
}
|
||||
|
||||
/**** transforme polygon ****/
|
||||
|
||||
let transformedPolygon = new Array(HourHandPolygon.length);
|
||||
|
||||
function transformPolygon (originalPolygon, OriginX,OriginY, Phi) {
|
||||
let sPhi = sin(Phi), cPhi = cos(Phi), x,y;
|
||||
|
||||
for (let i = 0, l = originalPolygon.length; i < l; i+=2) {
|
||||
x = originalPolygon[i];
|
||||
y = originalPolygon[i+1];
|
||||
|
||||
transformedPolygon[i] = OriginX + x*cPhi + y*sPhi;
|
||||
transformedPolygon[i+1] = OriginY + x*sPhi - y*cPhi;
|
||||
// draw black rectangle in the middle to clear screen from scale and hands
|
||||
g.setColor(!white,!white,!white);
|
||||
g.fillRect(10,10,2*c.x-10,2*c.x-10);
|
||||
// prepare for drawing the text
|
||||
g.setFontAlign(0,0);
|
||||
// do drawing
|
||||
const d=new Date();
|
||||
drawScale(d); // FIXME: it is enough to do once in 12 hours or so
|
||||
drawNumbers(d);
|
||||
if (settings.textAboveHands) {
|
||||
drawHands(d); drawText(d);
|
||||
} else {
|
||||
drawText(d); drawHands(d);
|
||||
}
|
||||
}
|
||||
|
||||
/**** draw clock hands ****/
|
||||
|
||||
function drawClockHands () {
|
||||
let now = new Date();
|
||||
|
||||
let Hours = now.getHours() % 12;
|
||||
let Minutes = now.getMinutes();
|
||||
let Seconds = now.getSeconds();
|
||||
|
||||
let HoursAngle = (Hours+(Minutes/60))/12 * twoPi - Pi;
|
||||
let MinutesAngle = (Minutes/60) * twoPi - Pi;
|
||||
let SecondsAngle = (Seconds/60) * twoPi - Pi;
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
|
||||
transformPolygon(HourHandPolygon, CenterX,CenterY, HoursAngle);
|
||||
g.fillPoly(transformedPolygon);
|
||||
|
||||
transformPolygon(MinuteHandPolygon, CenterX,CenterY, MinutesAngle);
|
||||
g.fillPoly(transformedPolygon);
|
||||
|
||||
let sPhi = Math.sin(SecondsAngle), cPhi = Math.cos(SecondsAngle);
|
||||
|
||||
g.setColor(g.theme.fg2);
|
||||
g.drawLine(
|
||||
CenterX + SecondHandOffset*sPhi,
|
||||
CenterY - SecondHandOffset*cPhi,
|
||||
CenterX - SecondHandLength*sPhi,
|
||||
CenterY + SecondHandLength*cPhi
|
||||
);
|
||||
|
||||
g.setFont('Vector', 22);
|
||||
|
||||
g.setFontAlign(-1, 1);
|
||||
g.drawString(now.getDate(), CenterX-outerRadius,CenterY+outerRadius);
|
||||
|
||||
/* 0..12 -> angle suitable for drawScale */
|
||||
function conv(m) { return -15 + (m / 12) * 60; }
|
||||
/* datetime -> 0..12 float */
|
||||
function hour12(d) {
|
||||
let h = d.getHours() + d.getMinutes() / 60;
|
||||
if (h > 12)
|
||||
h = h - 12;
|
||||
return h;
|
||||
}
|
||||
|
||||
function drawTimeIcon(time, icon, options) {
|
||||
let h = fracHour(time);
|
||||
let x = radX(h/12, 0.7);
|
||||
let y = radY(h/12, 0.7);
|
||||
g.drawImage(icon, x,y, options);
|
||||
}
|
||||
//draws the scale once the app is started
|
||||
function drawScale(d){
|
||||
// clear the screen
|
||||
g.setBgColor(!white,!white,!white);
|
||||
g.clear();
|
||||
// Display month as a wider mark
|
||||
let m = conv(d.getMonth() + 1);
|
||||
print(m);
|
||||
|
||||
function drawOutsideIcon(h, icon, options) {
|
||||
let x = radX(h, 0.95);
|
||||
let y = radY(h, 0.95);
|
||||
g.drawImage(icon, x,y, options);
|
||||
}
|
||||
let pos = sun.sunPos().azimuth;
|
||||
pos = conv(12*(pos/360));
|
||||
|
||||
function drawBorders() {
|
||||
g.reset();
|
||||
g.setColor(0);
|
||||
g.fillRect(Bangle.appRect);
|
||||
let t = sun.sunTime();
|
||||
// FIXME
|
||||
let set = conv(hour12(t.sunset));
|
||||
let dark = conv(hour12(t.sunset) + 0.25);
|
||||
print(set, dark, pos);
|
||||
|
||||
g.setColor(-1);
|
||||
g.fillCircle(w/2, h/2, h/2 - 2);
|
||||
if (0) {
|
||||
g.fillCircle(sm+1, sm+1, sm);
|
||||
g.fillCircle(sm+1, h-sm-1, sm);
|
||||
g.fillCircle(w-sm-1, h-sm-1, sm);
|
||||
g.fillCircle(h-sm-1, sm+1, sm);
|
||||
}
|
||||
g.setColor(0, 1, 0);
|
||||
g.drawCircle(h/2, w/2, radD(0.7));
|
||||
g.drawCircle(h/2, w/2, radD(0.5));
|
||||
|
||||
outerRadius = radD(0.7);
|
||||
drawClockHands();
|
||||
|
||||
let d = new Date();
|
||||
let hour = fracHour(d);
|
||||
let min = d.getMinutes();
|
||||
let day = d.getDay();
|
||||
day = day + hour/24;
|
||||
{
|
||||
let x = radX(hour/12, 0.7);
|
||||
let y = radY(hour/12, 0.7);
|
||||
g.setColor(0, 0, 0);
|
||||
g.fillCircle(x,y, 5);
|
||||
}
|
||||
{
|
||||
let x = radX(min/60, 0.5);
|
||||
let y = radY(min/60, 0.5);
|
||||
g.setColor(0, 0, 0);
|
||||
g.drawLine(h/2, w/2, x, y);
|
||||
}
|
||||
{
|
||||
let x = radX(hour/12, 0.3);
|
||||
let y = radY(hour/12, 0.3);
|
||||
g.setColor(0, 0, 0);
|
||||
g.drawLine(h/2, w/2, x, y);
|
||||
}
|
||||
{
|
||||
let km = 0.001 * 0.719 * Bangle.getHealthStatus("day").steps;
|
||||
let x = radX(km/12 + 0, 0.95);
|
||||
let y = radY(km/12 + 0, 0.95);
|
||||
g.setColor(0, 0.7, 0);
|
||||
g.drawImage(img_step, x,y, { scale: 2, rotate: Math.PI*0.0 } );
|
||||
}
|
||||
{
|
||||
let bat = E.getBattery();
|
||||
let x = radX(bat/100, 0.95);
|
||||
let y = radY(bat/100, 0.95);
|
||||
g.setColor(0.7, 0, 0);
|
||||
g.drawImage(img_battery, x,y, { scale: 2, rotate: Math.PI*0.0 } );
|
||||
}
|
||||
{
|
||||
d = new Date();
|
||||
const sun = SunCalc.getTimes(d, lat, lon);
|
||||
g.setColor(0.5, 0.5, 0);
|
||||
print("sun", sun);
|
||||
drawTimeIcon(sun.sunset, img_sunrise, { rotate: Math.PI, scale: 2 });
|
||||
drawTimeIcon(sun.sunrise, img_sunrise, { scale: 2 });
|
||||
g.setColor(0, 0, 0);
|
||||
const moon = SunCalc.getMoonTimes(d, lat, lon);
|
||||
print("moon", moon);
|
||||
drawTimeIcon(moon.set, img_moonrise, { rotate: Math.PI, scale: 2 });
|
||||
drawTimeIcon(moon.rise, img_sunrise, { scale: 2 });
|
||||
let pos = SunCalc.getPosition(d, lat, lon);
|
||||
print("sun:", pos);
|
||||
if (pos.altitude > -0.1) {
|
||||
g.setColor(0.5, 0.5, 0);
|
||||
az = pos.azimuth;
|
||||
drawOutsideIcon(az / (2*Math.PI), img_sun, { scale: 2 });
|
||||
}
|
||||
pos = SunCalc.getMoonPosition(d, lat, lon);
|
||||
print("moon:", pos);
|
||||
if (pos.altitude > -0.05) {
|
||||
g.setColor(0, 0, 0);
|
||||
az = pos.azimuth;
|
||||
drawOutsideIcon(az / (2*Math.PI), img_moon, { scale: 2 });
|
||||
}
|
||||
}
|
||||
{
|
||||
Bangle.getPressure().then((x) =>
|
||||
{ altitude = x.altitude; temperature = x.temperature; },
|
||||
print);
|
||||
print(altitude, temperature);
|
||||
drawOutsideIcon(altitude / 120, img_altitude, { scale: 2 });
|
||||
drawOutsideIcon(temperature / 12, img_temperature, { scale: 2 });
|
||||
}
|
||||
if (use_compass) {
|
||||
let obj = Bangle.getCompass();
|
||||
if (obj) {
|
||||
let h = 360-obj.heading;
|
||||
let x = radX(h/360, 0.7);
|
||||
let y = radY(h/360, 0.7);
|
||||
g.setColor(0, 0, 1);
|
||||
g.drawImage(img_north, x,y, {scale:2});
|
||||
}
|
||||
}
|
||||
{
|
||||
let x = radX(day/7, 0.95);
|
||||
let y = radY(day/7, 0.95);
|
||||
g.setColor(0, 0, 0);
|
||||
g.fillCircle(x,y, 5);
|
||||
// draw the ticks of the scale
|
||||
for(let i=-14;i<47;i++){
|
||||
const win=i*2*Math.PI/60;
|
||||
let d=2;
|
||||
if(i%5==0){d=5;}
|
||||
if(i==m){d=10;}
|
||||
if (i>=pos && i<=(pos+2))
|
||||
g.setColor(!white,!white,white/2);
|
||||
else if (i>=set && i<=dark)
|
||||
g.setColor(white/2,!white,white/2);
|
||||
else
|
||||
g.setColor(white,white,white);
|
||||
g.fillPoly(zeiger(300,d,win),true);
|
||||
g.setColor(!white,!white,!white);
|
||||
g.fillRect(10,10,2*c.x-10,2*c.x-10);
|
||||
}
|
||||
}
|
||||
|
||||
function drawEmpty() {
|
||||
g.reset();
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(Bangle.appRect);
|
||||
}
|
||||
//// main running sequence ////
|
||||
|
||||
Bangle.on('touch', function(button, xy) {
|
||||
var x = xy.x;
|
||||
var y = xy.y;
|
||||
if (y > h) y = h;
|
||||
if (y < 0) y = 0;
|
||||
if (x > w) x = w;
|
||||
if (x < 0) x = 0;
|
||||
});
|
||||
|
||||
// if we get a step then we are not idle
|
||||
Bangle.on('step', s => {
|
||||
});
|
||||
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
next = 60000;
|
||||
if (use_compass) next = 250;
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
// Show launcher when middle button pressed, and widgets that we're clock
|
||||
Bangle.setUI("clock");
|
||||
// Load widgets if needed, and make them show swipeable
|
||||
if (settings.loadWidgets) {
|
||||
Bangle.loadWidgets();
|
||||
require("widget_utils").swipeOn();
|
||||
} else if (global.WIDGETS) require("widget_utils").hide();
|
||||
// Clear the screen once, at startup
|
||||
drawScale(new Date());
|
||||
draw();
|
||||
}, next - (Date.now() % next));
|
||||
}
|
||||
|
||||
let secondInterval = setInterval(draw, 1000);
|
||||
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
draw(); // draw immediately
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.setUI("clockupdown", btn=> {
|
||||
if (btn<0) use_compass = 0;
|
||||
if (btn>0) use_compass = 1;
|
||||
Bangle.setCompassPower(use_compass, 'orloj');
|
||||
draw();
|
||||
Bangle.on('lock',on=>{
|
||||
unlock = !on;
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = setInterval(draw, unlock ? 1000 : 60000);
|
||||
draw(); // draw immediately
|
||||
});
|
||||
|
||||
if (use_compass)
|
||||
Bangle.setCompassPower(true, 'orloj');
|
||||
g.clear();
|
||||
draw();
|
||||
|
||||
Bangle.on('charging',on=>{draw();});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "orloj",
|
||||
"name": "Orloj",
|
||||
"version": "0.03",
|
||||
"version": "0.10",
|
||||
"description": "Astronomical clock",
|
||||
"icon": "app.png",
|
||||
"readme": "README.md",
|
||||
|
|
|
@ -4,3 +4,6 @@
|
|||
0.04: Minor code improvements
|
||||
0.05: Upgrade OWM to One Call API 3.0. Add pressure to weather.json
|
||||
0.06: Fix One Call API 3.0 not returning city names, which are required by the weather app
|
||||
0.07: Update weather after reconnecting bluetooth if update is due, refactor code
|
||||
0.08: Undo change to One Call API 3.0
|
||||
0.09: Fix infinite loop when settings.updated is not defined
|
|
@ -1,33 +1,47 @@
|
|||
{
|
||||
let waiting = false;
|
||||
let loading = false;
|
||||
let timeoutRef = null;
|
||||
let settings = Object.assign(
|
||||
require('Storage').readJSON("owmweather.default.json", true) || {},
|
||||
require('Storage').readJSON("owmweather.json", true) || {}
|
||||
);
|
||||
|
||||
let completion = function(){
|
||||
waiting = false;
|
||||
let refreshMillis = function () {
|
||||
return settings.refresh * 1000 * 60 + 1; // +1 <- leave some slack
|
||||
};
|
||||
|
||||
let onCompleted = function () {
|
||||
loading = false;
|
||||
settings.updated = Date.now();
|
||||
require('Storage').writeJSON("owmweather.json", settings);
|
||||
if (timeoutRef) clearTimeout(timeoutRef);
|
||||
timeoutRef = setTimeout(loadIfDueAndReschedule, refreshMillis());
|
||||
};
|
||||
|
||||
let loadIfDueAndReschedule = function () {
|
||||
// also check if the weather.json file has been updated (e.g. force refresh)
|
||||
let weather = require("Storage").readJSON('weather.json') || {};
|
||||
let lastWeatherUpdate = weather && weather.weather && weather.weather.time && weather.weather.time || 0;
|
||||
if (lastWeatherUpdate > settings.updated) {
|
||||
settings.updated = lastWeatherUpdate;
|
||||
}
|
||||
|
||||
let MillisUntilDue = settings.updated + refreshMillis() - Date.now();
|
||||
if (!MillisUntilDue || MillisUntilDue <= 0) {
|
||||
if (!loading) {
|
||||
loading = true;
|
||||
require("owmweather").pull(onCompleted);
|
||||
}
|
||||
} else {
|
||||
// called to early, reschedule
|
||||
// console.log('Weather data is not due yet, rescheduling in ' + (MillisUntilDue || 0) + 'ms');
|
||||
if (timeoutRef) clearTimeout(timeoutRef);
|
||||
timeoutRef = setTimeout(loadIfDueAndReschedule, MillisUntilDue + 1);
|
||||
}
|
||||
};
|
||||
|
||||
if (settings.enabled) {
|
||||
let weather = require("Storage").readJSON('weather.json') || {};
|
||||
if (weather && weather.weather && weather.weather.time) lastUpdate = weather.weather.time;
|
||||
|
||||
if (!settings.updated || settings.updated + settings.refresh * 1000 * 60 < Date.now()){
|
||||
setTimeout(() => {
|
||||
if (!waiting){
|
||||
waiting = true;
|
||||
require("owmweather").pull(completion);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
setInterval(() => {
|
||||
if (!waiting && NRF.getSecurityStatus().connected){
|
||||
waiting = true;
|
||||
require("owmweather").pull(completion);
|
||||
}
|
||||
}, settings.refresh * 1000 * 60);
|
||||
setTimeout(loadIfDueAndReschedule, 5000); // run 5 seconds after boot
|
||||
NRF.on('connect', loadIfDueAndReschedule); // after reconnect, fetch the weather data right away if it's due
|
||||
}
|
||||
}
|
|
@ -1,23 +1,20 @@
|
|||
function parseWeather(response) {
|
||||
let owmData = JSON.parse(response);
|
||||
|
||||
let isOwmData = false;
|
||||
try {
|
||||
isOwmData = (owmData.lat && owmData.lon) && owmData.current.weather && owmData.current;
|
||||
} catch (_e) {}
|
||||
let isOwmData = owmData.coord && owmData.weather && owmData.main;
|
||||
|
||||
if (isOwmData) {
|
||||
let json = require("Storage").readJSON('weather.json') || {};
|
||||
let weather = {};
|
||||
weather.time = Date.now();
|
||||
weather.hum = owmData.current.humidity;
|
||||
weather.temp = owmData.current.temp;
|
||||
weather.code = owmData.current.weather[0].id;
|
||||
weather.wdir = owmData.current.wind_deg;
|
||||
weather.wind = owmData.current.wind_speed;
|
||||
weather.loc = owmData.name || "";
|
||||
weather.txt = owmData.current.weather[0].main;
|
||||
weather.hpa = owmData.current.pressure || 0;
|
||||
weather.hum = owmData.main.humidity;
|
||||
weather.temp = owmData.main.temp;
|
||||
weather.code = owmData.weather[0].id;
|
||||
weather.wdir = owmData.wind.deg;
|
||||
weather.wind = owmData.wind.speed;
|
||||
weather.loc = owmData.name;
|
||||
weather.txt = owmData.weather[0].main;
|
||||
weather.hpa = owmData.main.pressure || 0;
|
||||
|
||||
if (weather.wdir != null) {
|
||||
let deg = weather.wdir;
|
||||
|
@ -43,7 +40,7 @@ exports.pull = function(completionCallback) {
|
|||
"location": "London"
|
||||
};
|
||||
let settings = require("Storage").readJSON("owmweather.json", 1);
|
||||
let uri = "https://api.openweathermap.org/data/3.0/onecall?lat=" + location.lat.toFixed(2) + "&lon=" + location.lon.toFixed(2) + "&exclude=minutely,hourly,daily,alerts&appid=" + settings.apikey;
|
||||
let uri = "https://api.openweathermap.org/data/2.5/weather?lat=" + location.lat.toFixed(2) + "&lon=" + location.lon.toFixed(2) + "&exclude=hourly,daily&appid=" + settings.apikey;
|
||||
if (Bangle.http){
|
||||
Bangle.http(uri, {timeout:10000}).then(event => {
|
||||
let result = parseWeather(event.resp);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "owmweather",
|
||||
"name": "OpenWeatherMap weather provider",
|
||||
"shortName":"OWM Weather",
|
||||
"version": "0.06",
|
||||
"version": "0.09",
|
||||
"description": "Pulls weather from OpenWeatherMap (OWM) API",
|
||||
"icon": "app.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New app!
|
||||
0.02: Show elapsed time on pause screen
|
||||
|
|
|
@ -85,26 +85,30 @@
|
|||
var h = g.getHeight();
|
||||
var max = splits_1.reduce(function (a, s) { return Math.max(a, s.time); }, 0);
|
||||
g.setFont("6x8", 2).setFontAlign(-1, -1);
|
||||
var y = Bangle.appRect.y + barSpacing / 2;
|
||||
g
|
||||
.setColor(g.theme.fg)
|
||||
.drawString(formatDuration_1(exs_1.state.duration), 0, y);
|
||||
var i = 0;
|
||||
for (;; i++) {
|
||||
var split = splits_1[i + splitOffset_1];
|
||||
if (split == null)
|
||||
break;
|
||||
var y_1 = Bangle.appRect.y + i * (barSize + barSpacing) + barSpacing / 2;
|
||||
var y_1 = Bangle.appRect.y + (i + 1) * (barSize + barSpacing) + barSpacing / 2;
|
||||
if (y_1 > h)
|
||||
break;
|
||||
var size = w * split.time / max;
|
||||
g.setColor("#00f").fillRect(0, y_1, size, y_1 + barSize);
|
||||
var splitPace = calculatePace_1(split);
|
||||
g.setColor(g.theme.fg);
|
||||
drawSplit_1(i, y_1, splitPace);
|
||||
}
|
||||
var pace = exs_1.stats.pacec.getString();
|
||||
var y = Bangle.appRect.y + i * (barSize + barSpacing) + barSpacing / 2;
|
||||
y = Bangle.appRect.y + (i + 1) * (barSize + barSpacing) + barSpacing / 2;
|
||||
drawSplit_1(i, y, pace);
|
||||
};
|
||||
var drawSplit_1 = function (i, y, pace) {
|
||||
g
|
||||
.setColor(g.theme.fg)
|
||||
return g
|
||||
.drawString("".concat(i + 1 + splitOffset_1, " ").concat(typeof pace === "number" ? pace.toFixed(2) : pace), 0, y);
|
||||
};
|
||||
var pauseRun_1 = function () {
|
||||
|
|
|
@ -114,36 +114,40 @@ const drawSplits = () => {
|
|||
|
||||
g.setFont("6x8", 2).setFontAlign(-1, -1);
|
||||
|
||||
let y = Bangle.appRect.y + barSpacing / 2;
|
||||
g
|
||||
.setColor(g.theme.fg)
|
||||
.drawString(formatDuration(exs.state.duration), 0, y);
|
||||
|
||||
let i = 0;
|
||||
for(; ; i++) {
|
||||
const split = splits[i + splitOffset];
|
||||
if (split == null) break;
|
||||
|
||||
const y = Bangle.appRect.y + i * (barSize + barSpacing) + barSpacing / 2;
|
||||
const y = Bangle.appRect.y + (i + 1) * (barSize + barSpacing) + barSpacing / 2;
|
||||
if (y > h) break;
|
||||
|
||||
const size = w * split.time / max; // Scale bar height based on pace
|
||||
g.setColor("#00f").fillRect(0, y, size, y + barSize);
|
||||
|
||||
const splitPace = calculatePace(split); // Pace per km
|
||||
g.setColor(g.theme.fg)
|
||||
drawSplit(i, y, splitPace);
|
||||
}
|
||||
|
||||
const pace = exs.stats.pacec.getString();
|
||||
|
||||
const y = Bangle.appRect.y + i * (barSize + barSpacing) + barSpacing / 2;
|
||||
y = Bangle.appRect.y + (i + 1) * (barSize + barSpacing) + barSpacing / 2;
|
||||
drawSplit(i, y, pace);
|
||||
};
|
||||
|
||||
const drawSplit = (i: number, y: number, pace: number | string) => {
|
||||
const drawSplit = (i: number, y: number, pace: number | string) =>
|
||||
g
|
||||
.setColor(g.theme.fg)
|
||||
.drawString(
|
||||
`${i + 1 + splitOffset} ${typeof pace === "number" ? pace.toFixed(2) : pace}`,
|
||||
0,
|
||||
y
|
||||
);
|
||||
};
|
||||
|
||||
const pauseRun = () => {
|
||||
exs.stop();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "pace",
|
||||
"name": "Pace",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Show pace and time running splits",
|
||||
"icon": "app.png",
|
||||
"tags": "run,running,fitness,outdoors",
|
||||
|
|
|
@ -8,6 +8,6 @@ Addict.
|
|||
0.07: Remove just the specific listeners to not interfere with Quick Launch
|
||||
when fastloading.
|
||||
0.08: Issue newline before GB commands (solves issue with console.log and ignored commands)
|
||||
0.09: Don't send the gadgetbridge wake command twice. Once should do since we
|
||||
issue newline before GB commands.
|
||||
0.09: Don't send the gadgetbridge wake command twice. Once should do since we issue newline before GB commands.
|
||||
0.10: Minor code improvements
|
||||
0.11: Fix a warning from the linter.
|
||||
|
|
|
@ -127,7 +127,7 @@ The functions for interacting with Android and the Podcast Addict app
|
|||
let pkg = "com.bambuna.podcastaddict";
|
||||
let standardCls = pkg + ".receiver.PodcastAddictPlayerReceiver";
|
||||
let updateCls = pkg + ".receiver.PodcastAddictBroadcastReceiver";
|
||||
let speed = 1.0;
|
||||
//let speed = 1.0;
|
||||
|
||||
let simpleSearch = "";
|
||||
|
||||
|
@ -269,15 +269,15 @@ let speedMenu = {
|
|||
}
|
||||
},
|
||||
"Regular Speed": () => {
|
||||
speed = 1.0;
|
||||
//speed = 1.0;
|
||||
btMsg("service", standardCls, "player.1xspeed");
|
||||
},
|
||||
"1.5x Regular Speed": () => {
|
||||
speed = 1.5;
|
||||
//speed = 1.5;
|
||||
btMsg("service", standardCls, "player.1.5xspeed");
|
||||
},
|
||||
"2x Regular Speed": () => {
|
||||
speed = 2.0;
|
||||
//speed = 2.0;
|
||||
btMsg("service", standardCls, "player.2xspeed");
|
||||
},
|
||||
//"Faster" : ()=>{speed+=0.1; speed=((speed>5.0)?5.0:speed); btMsg("service",standardCls,"player.customspeed",{arg1:speed});},
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "podadrem",
|
||||
"name": "Podcast Addict Remote",
|
||||
"shortName": "PA Remote",
|
||||
"version": "0.10",
|
||||
"version": "0.11",
|
||||
"description": "Control Podcast Addict on your android device.",
|
||||
"readme": "README.md",
|
||||
"type": "app",
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
0.01: Packaged app
|
||||
0.02: Fix alert buzz time, Indicate when paused
|
||||
0.03: Start app with paused timer
|
||||
0.04: Added 20-second warning buzz
|
||||
0.05: Added screenshots
|
||||
0.06: Fix bug when play/pause during alert
|
|
@ -0,0 +1,9 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2024 Keith Irwin
|
||||
|
||||
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.
|
|
@ -0,0 +1,55 @@
|
|||
# Poker Timer
|
||||
*v.0.06*
|
||||
|
||||
A blinds timer for poker. Don't know what that means? See [Wikipedia: Blind (poker)](https://en.wikipedia.org/wiki/Blind_(poker)) and [Wikipedia: Texas hold 'em](https://en.wikipedia.org/wiki/Texas_hold_%27em).
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
The blinds are hardcoded and go up every ten minutes:
|
||||
|
||||
- 1, 2
|
||||
- 2, 4
|
||||
- 4, 8
|
||||
- 5, 10
|
||||
- 10, 20
|
||||
- 20, 40
|
||||
- 40, 80
|
||||
|
||||
... and so on, doubling each round.
|
||||
|
||||
## Features
|
||||
|
||||
- Starts paused
|
||||
- Button to pause/resume
|
||||
- 20-second warning buzz
|
||||
- Auto-exit after round 25
|
||||
|
||||
## Usage
|
||||
|
||||
The timer will start as soon as you open the app. Time left in the round is on the top of the screen, currnt small and big blinds are shown below. After ten minutes, it will vibrate and flash and show the new blind. Then it starts over.
|
||||
|
||||
### Auto-exit
|
||||
|
||||
The program will automatically exit after the 25 round. This is not a bug. If the blinds double again, it will perform some kind of overflow and convert the blind values to floats.
|
||||
|
||||
The blinds in round 25 are `20971520 / 41943040`. You probably aren't still playing poker at that point and just forgot to exit the program.
|
||||
|
||||
## Controls
|
||||
|
||||
- **Pause/Resume:** Press the button
|
||||
- **Exit:** hold down the button
|
||||
|
||||
## Roadmap
|
||||
|
||||
- Set settings
|
||||
- Better graphics
|
||||
|
||||
## Requests
|
||||
|
||||
[Contact Keith Irwin](https://www.ki9.us/contact/)
|
||||
|
||||
## Creator
|
||||
|
||||
[Keith Irwin](https://www.ki9.us)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("kso4cB7nW///7fWm2Vw8p7fOgH6lM6hEqgQCDkNCgmSpMkyUQqkEAQsJBYIOBkGohACD8dIiQaCpFBhUQAQVD+9qDQeQwWgkGCikg1lxknZBwUhgkooICCnMjpO1qIOBoUKwGCAQXt32TtMkwUkwkVgIdFg3SGQOBNYoCChuklfAgBQCPokqjdBluCNAJTBkN4UwVBm2C5ckxFBqEQq/+BYKMBtstz1JiBuC/+T2gjC7VYlmSkMFoFOQQON0mShO0iFNkhuC8iCBk4aBwdpFgNENwMOVgft1Ms9uiwNJLIM/VgWTxUFjdrtsUiUQ0NPXYe0qE27XRoLdC/wpCpxvBtuzpMiboX8DQXtQYPbtto0jdCm87zvOIwMh23YgDdBNwNBh/bYoL7BtuapDdE2VGWYMgw3brUpbocF/mcIYMIlu10WCpL4FihEB3PboBaBiFUGQOhAQcbqkgLQL1CdIYCBm2oIgNIru36VJkD+BboUgxMkyGcyOk1VIkGFboWhiRuBrHiqpBBIgUVboJEBoUf3dSK4IpBhUTtEiIgOEjlpGolBg3QoiuBJoZWCAQMN0kJFIIyCIIQCBkNb1JEBc4Ul2zKBAQW7qLzDBw7pBBYYCDwgpCoUEBYoCC0A7CBY4CCykooALIBwcRAoQ"))
|
|
@ -0,0 +1,142 @@
|
|||
const BLIND_INTERVAL = 600; // 10 minutes in seconds
|
||||
const BLINDSUP_ALERT_DURATION = 10000; // 10 seconds in ms
|
||||
|
||||
// Convert seconds to mm:ss
|
||||
const secondsToMinutes = (s) => {
|
||||
const mm = Math.floor(s/60);
|
||||
const ss = s - mm * 60;
|
||||
return `${mm}:${String(ss).padStart(2,'0')}`;
|
||||
};
|
||||
|
||||
// Format screen
|
||||
const fmtDark = () => {
|
||||
g.clear();
|
||||
g.setFontAlign(0,0);
|
||||
g.setBgColor(0,0.5,0);
|
||||
g.setColor(1,1,1);
|
||||
};
|
||||
const fmtLight = () => {
|
||||
g.clear();
|
||||
g.setFontAlign(0,0);
|
||||
g.setBgColor(0.5,1,0.5);
|
||||
g.setColor(0,0,0);
|
||||
};
|
||||
|
||||
// Start/stop/pause/resume timer
|
||||
const startTimer = () => {
|
||||
timer_running = true; tick();
|
||||
timer = setInterval(tick, 1000);
|
||||
};
|
||||
const stopTimer = () => {
|
||||
clearInterval(timer);
|
||||
timer_running = false;
|
||||
};
|
||||
const pauseResume = () => {
|
||||
if (is_alerting) return;
|
||||
if (timer_running) {
|
||||
stopTimer();
|
||||
g.setFont('Vector',15);
|
||||
g.drawString('(PAUSED)',
|
||||
g.getWidth()/2, g.getHeight()*7/8);
|
||||
}
|
||||
else startTimer();
|
||||
};
|
||||
|
||||
// Calculate blinds for a round
|
||||
const getBlinds = (i) => {
|
||||
let small;
|
||||
if (i===0) small = 1;
|
||||
else if (i===1) small = 2;
|
||||
else if (i===2) small = 4;
|
||||
else small = 5*(Math.pow(2,(i-3)));
|
||||
return [small, small*2];
|
||||
};
|
||||
|
||||
// Sound the alarm
|
||||
const blindsUp = () => {
|
||||
is_alerting = true;
|
||||
// Display message
|
||||
const showMessage = () => {
|
||||
g.clear();
|
||||
g.setFont('Vector',34);
|
||||
g.drawString('Blinds Up!',
|
||||
g.getWidth()/2, g.getHeight()/3);
|
||||
g.setFont('Vector',40);
|
||||
g.drawString(`${blinds[0]}/${blinds[1]}`,
|
||||
g.getWidth()/2, g.getHeight()*2/3);
|
||||
};
|
||||
stopTimer();
|
||||
// Increase blinds
|
||||
b++;
|
||||
// TODO: Kill program between round 25 and 26
|
||||
blinds = getBlinds(b);
|
||||
console.log(`Blinds for round ${b} are ${blinds[0]} / ${blinds[1]}`);
|
||||
// Buzz and light up every second
|
||||
const buzzInterval = setInterval(() => {
|
||||
Bangle.buzz(500);
|
||||
Bangle.setLCDPower(1);
|
||||
}, 1000);
|
||||
// Invert colors every second
|
||||
fmtLight(); showMessage(); let dark = false;
|
||||
const flashInterval = setInterval(() => {
|
||||
if (dark) {
|
||||
fmtLight();
|
||||
dark = false;
|
||||
} else {
|
||||
fmtDark();
|
||||
dark = true;
|
||||
} showMessage();
|
||||
}, 500);
|
||||
// Restart timer
|
||||
setTimeout(() => {
|
||||
is_alerting = false;
|
||||
fmtDark(); tick();
|
||||
clearInterval(buzzInterval);
|
||||
clearInterval(flashInterval);
|
||||
time_left = BLIND_INTERVAL + 1;
|
||||
startTimer();
|
||||
}, BLINDSUP_ALERT_DURATION);
|
||||
};
|
||||
|
||||
// Tick every second
|
||||
const tick = () => {
|
||||
if (!timer_running) return;
|
||||
time_left--;
|
||||
// 20-second warning buzz
|
||||
if (time_left==20) {
|
||||
const buzzInterval = setInterval(Bangle.buzz, 500);
|
||||
setTimeout(() => {
|
||||
clearInterval(buzzInterval);
|
||||
}, 5000);
|
||||
}
|
||||
if (time_left<=0) blindsUp();
|
||||
else {
|
||||
g.clear();
|
||||
g.setFont('Vector',40);
|
||||
g.drawString(
|
||||
secondsToMinutes(time_left),
|
||||
g.getWidth()/2, g.getHeight()/3);
|
||||
g.drawString(
|
||||
`${blinds[0]}/${blinds[1]}`,
|
||||
g.getWidth()/2, g.getHeight()*2/3);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
// button listener
|
||||
Bangle.setUI({
|
||||
mode: 'custom',
|
||||
btn: pauseResume,
|
||||
});
|
||||
|
||||
// RUNTIME
|
||||
fmtDark();
|
||||
let time_left = BLIND_INTERVAL + 1;
|
||||
let b = 0;
|
||||
let blinds = getBlinds(b);
|
||||
let timer_running = true;
|
||||
let is_alerting = false;
|
||||
let timer = setInterval(tick, 1000);
|
||||
tick();
|
||||
// Start paused
|
||||
pauseResume();
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "pokertimer",
|
||||
"name": "Poker Timer",
|
||||
"shortName":"Poker Timer",
|
||||
"readme":"README.md",
|
||||
"icon": "app.png",
|
||||
"version":"0.06",
|
||||
"description": "A blinds timer for use with Texas Hold 'Em",
|
||||
"tags": "poker",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"pokertimer.app.js","url":"app.js"},
|
||||
{"name":"pokertimer.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
|
@ -11,3 +11,4 @@
|
|||
0.07: Fix bug with alarms app (scroller) and correctly show images
|
||||
0.08: Fix bug with modifying menu - allows hadash to save scroll positions
|
||||
0.09: Don't show "..." if a string isn't truncated (i.e. scrolled)
|
||||
0.10: Trigger `remove` callbacks when ending the menu
|
||||
|
|
|
@ -193,9 +193,11 @@ E.showMenu = function (items) {
|
|||
mode: "updown",
|
||||
back: back,
|
||||
remove: function () {
|
||||
var _a;
|
||||
if (nameScroller)
|
||||
clearInterval(nameScroller);
|
||||
Bangle.removeListener("swipe", onSwipe);
|
||||
(_a = options.remove) === null || _a === void 0 ? void 0 : _a.call(options);
|
||||
},
|
||||
}, function (dir) {
|
||||
if (dir)
|
||||
|
|
|
@ -240,6 +240,7 @@ E.showMenu = (items?: Menu): MenuInstance => {
|
|||
remove: () => {
|
||||
if (nameScroller) clearInterval(nameScroller);
|
||||
Bangle.removeListener("swipe", onSwipe);
|
||||
options.remove?.();
|
||||
},
|
||||
} as SetUIArg<"updown">,
|
||||
dir => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "promenu",
|
||||
"name": "Pro Menu",
|
||||
"version": "0.09",
|
||||
"version": "0.10",
|
||||
"description": "Replace the built in menu function. Supports Bangle.js 1 and Bangle.js 2.",
|
||||
"icon": "icon.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
@ -7,12 +7,10 @@
|
|||
0.07: Revert version 0.06. This version is the same as 0.05.
|
||||
0.08: Respect appRect on touch events
|
||||
0.09: Do not react if clkinfo is focused
|
||||
0.10: Extend the functionality via a quicklaunch.app.js file that can be launched
|
||||
with quicklaunch itself.
|
||||
0.10: Extend the functionality via a quicklaunch.app.js file that can be launched with quicklaunch itself.
|
||||
0.11: Add hints to the extension app. Tweak remove function.
|
||||
0.12: Stackable extension screens. After updating, please visit the quicklaunch
|
||||
settings page to prompt an automatic update of the quicklaunch.json settings file with
|
||||
new key names.
|
||||
0.12: Stackable extension screens. After updating, please visit the quicklaunch settings page to prompt an automatic update of the quicklaunch.json settings file with new key names.
|
||||
0.13: Touch and hold to pause the timeout to clock temporarily.
|
||||
0.14: Extension: Don't go down a path if nothing waits at the end. Revisit the current intersection instead.
|
||||
0.15: Extension: Compatibility with "Fastload Utils" app history feature.
|
||||
0.16: Snappier. Fewer storage interactions.
|
||||
|
|
|
@ -1,31 +1,40 @@
|
|||
{
|
||||
const R = Bangle.appRect;
|
||||
g.clearRect(R); // clear immediately to increase perceived snappiness.
|
||||
|
||||
const storage = require("Storage");
|
||||
let settings = storage.readJSON("quicklaunch.json", true) || {};
|
||||
let trace = (settings[settings.trace+"app"].src=="quicklaunch.app.js") ? settings.trace : settings.trace.substring(0, settings.trace.length-1); // If the stored trace leads beyond extension screens, walk back to the last extension screen. Compatibility with "Fastload Utils" App History feature.
|
||||
|
||||
let reset = function(name){
|
||||
if (!settings[name]) settings[name] = {"name":"(none)"};
|
||||
if (!storage.read(settings[name].src)) settings[name] = {"name":"(none)"};
|
||||
storage.write("quicklaunch.json", settings);
|
||||
const draw = () => {
|
||||
// Draw app hints
|
||||
g.reset().clearRect(R).setFont("Vector", 11)
|
||||
.setFontAlign(0,1,3).drawString(settings[trace+"lapp"].name, R.x2, R.y+R.h/2)
|
||||
.setFontAlign(0,1,1).drawString(settings[trace+"rapp"].name, R.x, R.y+R.h/2)
|
||||
.setFontAlign(0,1,0).drawString(settings[trace+"uapp"].name, R.x+R.w/2, R.y2)
|
||||
.setFontAlign(0,-1,0).drawString(settings[trace+"dapp"].name, R.x+R.w/2, R.y)
|
||||
.setFontAlign(0,0,0).drawString(settings[trace+"tapp"].name, R.x+R.w/2, R.y+R.h/2);
|
||||
};
|
||||
draw(); // draw asap to increase perceived snappiness.
|
||||
|
||||
let leaveTrace = function(trace) {
|
||||
if (settings[trace+"app"].name != "") {
|
||||
settings.trace = trace;
|
||||
storage.writeJSON("quicklaunch.json", settings);
|
||||
} else { trace = trace.substring(0, trace.length-1); }
|
||||
return trace;
|
||||
};
|
||||
|
||||
let launchApp = function(trace) {
|
||||
if (settings[trace+"app"]) {
|
||||
if (settings[trace+"app"].src){
|
||||
if (settings[trace+"app"].name == "Show Launcher") Bangle.showLauncher(); else if (!storage.read(settings[trace+"app"].src)) reset(trace+"app"); else load(settings[trace+"app"].src);
|
||||
}
|
||||
if (settings[trace+"app"] && settings[trace+"app"].src) {
|
||||
if (settings[trace+"app"].name == "Extension") draw();
|
||||
else if (settings[trace+"app"].name == "Show Launcher") Bangle.showLauncher();
|
||||
else if (!storage.read(settings[trace+"app"].src)) {
|
||||
E.showMessage(settings[trace+"app"].src+"\n"+/*LANG*/"was not found"+".", "Quick Launch");
|
||||
settings[trace+"app"] = {"name":"(none)"}; // reset entry.
|
||||
} else load(settings[trace+"app"].src);
|
||||
}
|
||||
};
|
||||
|
||||
let trace = (settings[settings.trace+"app"].src=="quicklaunch.app.js") ? settings.trace : settings.trace.substring(0, settings.trace.length-1); // If the stored trace leads beyond extension screens, walk back to the last extension screen. Compatibility with "Fastload Utils" App History feature.
|
||||
|
||||
let touchHandler = (_,e) => {
|
||||
if (e.type == 2) return;
|
||||
let R = Bangle.appRect;
|
||||
|
@ -47,15 +56,22 @@
|
|||
if (e.b == 0 && !timeoutToClock) updateTimeoutToClock();
|
||||
};
|
||||
|
||||
let saveAndClear = ()=> {
|
||||
storage.writeJSON("quicklaunch.json", settings);
|
||||
E.removeListener("kill", saveAndClear);
|
||||
if (timeoutToClock) clearTimeout(timeoutToClock); // Compatibility with Fastload Utils.
|
||||
}
|
||||
|
||||
Bangle.setUI({
|
||||
mode: "custom",
|
||||
touch: touchHandler,
|
||||
swipe : swipeHandler,
|
||||
drag : onLongTouchDoPause,
|
||||
remove: ()=>{if (timeoutToClock) clearTimeout(timeoutToClock);} // Compatibility with Fastload Utils.
|
||||
remove: saveAndClear
|
||||
});
|
||||
|
||||
g.clearRect(Bangle.appRect);
|
||||
E.on("kill", saveAndClear)
|
||||
|
||||
"Bangle.loadWidgets()"; // Hack: Fool Fastload Utils that we call Bangle.loadWidgets(). This way we get the fastest possibe loading in whichever environment we find ourselves.
|
||||
|
||||
// taken from Icon Launcher with some alterations
|
||||
|
@ -67,13 +83,4 @@
|
|||
};
|
||||
updateTimeoutToClock();
|
||||
|
||||
let R = Bangle.appRect;
|
||||
|
||||
// Draw app hints
|
||||
g.setFont("Vector", 11)
|
||||
.setFontAlign(0,1,3).drawString(settings[trace+"lapp"].name, R.x2, R.y+R.h/2)
|
||||
.setFontAlign(0,1,1).drawString(settings[trace+"rapp"].name, R.x, R.y+R.h/2)
|
||||
.setFontAlign(0,1,0).drawString(settings[trace+"uapp"].name, R.x+R.w/2, R.y2)
|
||||
.setFontAlign(0,-1,0).drawString(settings[trace+"dapp"].name, R.x+R.w/2, R.y)
|
||||
.setFontAlign(0,0,0).drawString(settings[trace+"tapp"].name, R.x+R.w/2, R.y+R.h/2);
|
||||
}
|
||||
|
|
|
@ -1,24 +1,29 @@
|
|||
{
|
||||
const storage = require("Storage");
|
||||
let settings = storage.readJSON("quicklaunch.json", true) || {};
|
||||
|
||||
let reset = function(name){
|
||||
if (!settings[name]) settings[name] = {"name":"(none)"};
|
||||
if (!storage.read(settings[name].src)) settings[name] = {"name":"(none)"};
|
||||
storage.write("quicklaunch.json", settings);
|
||||
};
|
||||
let settings;
|
||||
|
||||
let leaveTrace = function(trace) {
|
||||
if (!settings) settings = storage.readJSON("quicklaunch.json", true) || {};
|
||||
|
||||
settings.trace = trace;
|
||||
storage.writeJSON("quicklaunch.json", settings);
|
||||
return trace;
|
||||
};
|
||||
|
||||
let launchApp = function(trace) {
|
||||
if (!settings) settings = storage.readJSON("quicklaunch.json", true) || {};
|
||||
|
||||
if (settings[trace+"app"].src) {
|
||||
if (settings[trace+"app"].name == "Show Launcher") Bangle.showLauncher(); else if (!storage.read(settings[trace+"app"].src)) reset(trace+"app"); else load(settings[trace+"app"].src);
|
||||
}
|
||||
if (settings[trace+"app"].name == "Show Launcher") {
|
||||
Bangle.showLauncher();
|
||||
} else if (!storage.read(settings[trace+"app"].src)) {
|
||||
E.showMessage(settings[trace+"app"].src+"\n"+/*LANG*/"was not found"+".", "Quick Launch");
|
||||
settings[trace+"app"] = {"name":"(none)"}; // reset entry.
|
||||
storage.write("quicklaunch.json", settings);
|
||||
setTimeout(load, 2000);
|
||||
} else {load(settings[trace+"app"].src);}
|
||||
}
|
||||
};
|
||||
|
||||
let trace;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "quicklaunch",
|
||||
"name": "Quick Launch",
|
||||
"icon": "app.png",
|
||||
"version": "0.15",
|
||||
"version": "0.16",
|
||||
"description": "Tap or swipe left/right/up/down on your clock face to launch up to five apps of your choice. Configurations can be accessed through Settings->Apps.",
|
||||
"type": "bootloader",
|
||||
"tags": "tools, system",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: First Release
|
||||
0.02: Use built-in rounded-rect draw function, faster UI
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "rest",
|
||||
"name": "Rest - Workout Timer App",
|
||||
"shortName":"Rest",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Rest timer and Set counter for workout, fitness and lifting things.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url": "screenshot1.png"}, {"url": "screenshot2.png"}, {"url": "screenshot3.png"}],
|
||||
|
|
|
@ -1,18 +1,6 @@
|
|||
|
||||
function roundRect (x1, y1, x2, y2, halfrad) {
|
||||
const fullrad = halfrad + halfrad
|
||||
const bgColor = g.getBgColor();
|
||||
const fgColor = g.getColor();
|
||||
g.fillRect(x1, y1, x2, y2);
|
||||
g.setColor(bgColor).fillRect(x1, y1, x1 + halfrad, y1 + halfrad);
|
||||
g.setColor(fgColor).fillEllipse(x1, y1, x1 + fullrad, y1 + fullrad);
|
||||
g.setColor(bgColor).fillRect(x2 - halfrad, y1, x2, y1 + halfrad);
|
||||
g.setColor(fgColor).fillEllipse(x2 - fullrad, y1, x2, y1 + fullrad);
|
||||
|
||||
g.setColor(bgColor).fillRect(x1, y2-halfrad, x1 + halfrad, y2);
|
||||
g.setColor(fgColor).fillEllipse(x1, y2-fullrad, x1 + fullrad, y2);
|
||||
g.setColor(bgColor).fillRect(x2 - halfrad, y2-halfrad, x2, y2);
|
||||
g.setColor(fgColor).fillEllipse(x2 - fullrad, y2-fullrad, x2, y2);
|
||||
g.fillRect({x:x1, y:y1, x2:x2, y2:y2, r: halfrad});
|
||||
}
|
||||
|
||||
function center(r) {
|
||||
|
|
|
@ -4,7 +4,7 @@ Ring based watchface, read from the outside in. When the watch is unlocked the c
|
|||
Contributors: Amos Blanton, pinq-. Inspired by Rinkulainen by Julio Kallio.
|
||||
|
||||

|
||||
View when watch is locked.
|
||||
View when watch is locked. From outside: Hours, minutes, battery.
|
||||
|
||||

|
||||
View when watch is locked with numbers and bubble settings on
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue