Merge branch 'espruino:master' into master
|
|
@ -4,4 +4,5 @@ apps/schoolCalendar/fullcalendar/main.js
|
||||||
apps/authentiwatch/qr_packed.js
|
apps/authentiwatch/qr_packed.js
|
||||||
apps/qrcode/qr-scanner.umd.min.js
|
apps/qrcode/qr-scanner.umd.min.js
|
||||||
apps/gipy/pkg/gpconv.js
|
apps/gipy/pkg/gpconv.js
|
||||||
|
apps/health/chart.min.js
|
||||||
*.test.js
|
*.test.js
|
||||||
|
|
|
||||||
|
|
@ -58,3 +58,7 @@ body:
|
||||||
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: apps
|
||||||
|
attributes:
|
||||||
|
label: Installed apps
|
||||||
|
|
@ -98,7 +98,7 @@ This is the best way to test...
|
||||||
|
|
||||||
**Note:** It's a great idea to get a local copy of the repository on your PC,
|
**Note:** It's a great idea to get a local copy of the repository on your PC,
|
||||||
then run `bin/sanitycheck.js` - it'll run through a bunch of common issues
|
then run `bin/sanitycheck.js` - it'll run through a bunch of common issues
|
||||||
that there might be.
|
that there might be. To get the project running locally, you have to initialize and update the git submodules first: `git submodule --init && git submodule update`.
|
||||||
|
|
||||||
Be aware of the delay between commits and updates on github.io - it can take a few minutes (and a 'hard refresh' of your browser) for changes to take effect.
|
Be aware of the delay between commits and updates on github.io - it can take a few minutes (and a 'hard refresh' of your browser) for changes to take effect.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Fullscreen settings.
|
0.02: Fullscreen settings.
|
||||||
0.03: Tell clock widgets to hide.
|
0.03: Tell clock widgets to hide.
|
||||||
|
0.04: Use widget_utils.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const SETTINGS_FILE = "90sclk.setting.json";
|
const SETTINGS_FILE = "90sclk.setting.json";
|
||||||
const locale = require('locale');
|
const locale = require('locale');
|
||||||
const storage = require('Storage');
|
const storage = require('Storage');
|
||||||
|
const widget_utils = require('widget_utils');
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -109,7 +110,7 @@ function draw() {
|
||||||
|
|
||||||
// Draw widgets if not fullscreen
|
// Draw widgets if not fullscreen
|
||||||
if(settings.fullscreen){
|
if(settings.fullscreen){
|
||||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
widget_utils.hide();
|
||||||
} else {
|
} else {
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "90sclk",
|
"id": "90sclk",
|
||||||
"name": "90s Clock",
|
"name": "90s Clock",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "A 90s style watch-face",
|
"description": "A 90s style watch-face",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
|
|
||||||
|
|
@ -1 +1,4 @@
|
||||||
0.01: Beta version for Bangle 2 (2021/11/28)
|
0.01: Beta version for Bangle 2 (2021/11/28)
|
||||||
|
0.02: Shows night time on the map (2022/12/28)
|
||||||
|
0.03: Add 1 minute timer with upper taps (2023/01/05)
|
||||||
|
1.00: Page to set up custom time zones (2023/01/06)
|
||||||
|
|
@ -2,14 +2,17 @@
|
||||||
|
|
||||||
* Works with Bangle 2
|
* Works with Bangle 2
|
||||||
* Timer
|
* Timer
|
||||||
* Right tap: start/increase by 10 minutes; Left tap: decrease by 5 minutes
|
* Top Right tap: increase by 1 minute
|
||||||
|
* Top Left tap: decrease by 1 minute
|
||||||
|
* Bottom Right tap: increase by 10 minutes
|
||||||
|
* Bottom Left tap: decrease by 5 minutes
|
||||||
* Short buzz at T-30, T-20, T-10 ; Double buzz at T
|
* Short buzz at T-30, T-20, T-10 ; Double buzz at T
|
||||||
* Other time zones
|
* Other time zones
|
||||||
* Currently hardcoded to Paris and Tokyo (this will be customizable in a future version)
|
* Showing Paris and Tokyo by default, but you can customize this using the dedicated configuration page on the app store
|
||||||
* World Map
|
* World Map
|
||||||
* The yellow line shows the position of the sun
|
* The map shows day and night on Earth and the position of the Sun (yellow line)
|
||||||
|
|
||||||

|
 
|
||||||
|
|
||||||
## Creator
|
## Creator
|
||||||
[@alainsaas](https://github.com/alainsaas)
|
[@alainsaas](https://github.com/alainsaas)
|
||||||
|
|
|
||||||
|
|
@ -18,19 +18,29 @@ var timervalue = 0;
|
||||||
var istimeron = false;
|
var istimeron = false;
|
||||||
var timertick;
|
var timertick;
|
||||||
|
|
||||||
Bangle.on('touch',t=>{
|
Bangle.on('touch',(touchside, touchdata)=>{
|
||||||
if (t == 1) {
|
if (touchside == 1) {
|
||||||
Bangle.buzz(30);
|
Bangle.buzz(30);
|
||||||
if (timervalue < 5*60) { timervalue = 1 ; }
|
var changevalue = 0;
|
||||||
else { timervalue -= 5*60; }
|
if(touchdata.y > 88) {
|
||||||
|
changevalue += 60*5;
|
||||||
|
} else {
|
||||||
|
changevalue += 60*1;
|
||||||
|
}
|
||||||
|
if (timervalue < changevalue) { timervalue = 1 ; }
|
||||||
|
else { timervalue -= changevalue; }
|
||||||
}
|
}
|
||||||
else if (t == 2) {
|
else if (touchside == 2) {
|
||||||
Bangle.buzz(30);
|
Bangle.buzz(30);
|
||||||
if (!istimeron) {
|
if (!istimeron) {
|
||||||
istimeron = true;
|
istimeron = true;
|
||||||
timertick = setInterval(countDown, 1000);
|
timertick = setInterval(countDown, 1000);
|
||||||
}
|
}
|
||||||
timervalue += 60*10;
|
if(touchdata.y > 88) {
|
||||||
|
timervalue += 60*10;
|
||||||
|
} else {
|
||||||
|
timervalue += 60*1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -73,12 +83,13 @@ function countDown() {
|
||||||
function showWelcomeMessage() {
|
function showWelcomeMessage() {
|
||||||
g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6);
|
g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6);
|
||||||
g.setFontAlign(0, 0).setFont("6x8");
|
g.setFontAlign(0, 0).setFont("6x8");
|
||||||
g.drawString("Touch right to", 44, 80);
|
g.drawString("Tap right to", 44, 80);
|
||||||
g.drawString("start timer", 44, 88);
|
g.drawString("start timer", 44, 88);
|
||||||
setTimeout(function(){ g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6); }, 8000);
|
setTimeout(function(){ g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6); }, 8000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// time
|
// time
|
||||||
|
var offsets = require("Storage").readJSON("a_clock_timer.settings.json") || [ ["PAR",1], ["TYO",9] ];
|
||||||
var drawTimeout;
|
var drawTimeout;
|
||||||
|
|
||||||
function getGmt() {
|
function getGmt() {
|
||||||
|
|
@ -102,20 +113,34 @@ function queueNextDraw() {
|
||||||
function draw() {
|
function draw() {
|
||||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT);
|
g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT);
|
||||||
g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT);
|
g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT);
|
||||||
|
|
||||||
var x_sun = 176 - (getGmt().getHours() / 24 * 176 + 4);
|
var gmtHours = getGmt().getHours();
|
||||||
|
|
||||||
|
var x_sun = 176 - (gmtHours / 24 * 176 + 4);
|
||||||
g.setColor('#ff0').drawLine(x_sun, g.getHeight()-IMAGEHEIGHT, x_sun, g.getHeight());
|
g.setColor('#ff0').drawLine(x_sun, g.getHeight()-IMAGEHEIGHT, x_sun, g.getHeight());
|
||||||
g.reset();
|
g.reset();
|
||||||
|
|
||||||
|
var x_night_start = (176 - (((gmtHours-6)%24) / 24 * 176 + 4)) % 176;
|
||||||
|
var x_night_end = 176 - (((gmtHours+6)%24) / 24 * 176 + 4);
|
||||||
|
g.setColor('#000');
|
||||||
|
for (let x = x_night_start; x < (x_night_end < x_night_start ? 176 : x_night_end); x+=2) {
|
||||||
|
g.drawLine(x, g.getHeight()-IMAGEHEIGHT, x, g.getHeight());
|
||||||
|
}
|
||||||
|
if (x_night_end < x_night_start) {
|
||||||
|
for (let x = 0; x < x_night_end; x+=2) {
|
||||||
|
g.drawLine(x, g.getHeight()-IMAGEHEIGHT, x, g.getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var locale = require("locale");
|
var locale = require("locale");
|
||||||
|
|
||||||
var date = new Date();
|
var date = new Date();
|
||||||
g.setFontAlign(0,0);
|
g.setFontAlign(0,0);
|
||||||
g.setFont("Michroma36").drawString(locale.time(date,1), g.getWidth()/2, 46);
|
g.setFont("Michroma36").drawString(locale.time(date,1), g.getWidth()/2, 46);
|
||||||
g.setFont("6x8");
|
g.setFont("6x8");
|
||||||
g.drawString(locale.date(new Date(),1), 125, 68);
|
g.drawString(locale.date(new Date(),1), 125, 68);
|
||||||
g.drawString("PAR "+locale.time(getTimeFromTimezone(1),1), 125, 80);
|
g.drawString(offsets[0][0]+" "+locale.time(getTimeFromTimezone(offsets[0][1]),1), 125, 80);
|
||||||
g.drawString("TYO "+locale.time(getTimeFromTimezone(9),1), 125, 88);
|
g.drawString(offsets[1][0]+" "+locale.time(getTimeFromTimezone(offsets[1][1]),1), 125, 88);
|
||||||
|
|
||||||
queueNextDraw();
|
queueNextDraw();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>You can set the 2 additional timezones displayed by the clock.</p>
|
||||||
|
<table id="a_clock_timer-offsets">
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>UTC Offset (Hours)</th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p>Click <button id="upload" class="btn btn-primary">Upload</button></p>
|
||||||
|
<script src="../../core/lib/customize.js"></script>
|
||||||
|
<script>
|
||||||
|
var offsets=[];
|
||||||
|
try{
|
||||||
|
var stored = localStorage.getItem('a_clock_timer-offset-list')
|
||||||
|
if(stored) offsets = JSON.parse(stored);
|
||||||
|
if (!offsets || offsets.length!=2) {
|
||||||
|
throw "Offsets invalid";
|
||||||
|
}
|
||||||
|
} catch(e){
|
||||||
|
offsets=[
|
||||||
|
["PAR",1],
|
||||||
|
["TYO",9],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
console.log(offsets);
|
||||||
|
var tbl=document.getElementById("a_clock_timer-offsets");
|
||||||
|
for (var i=0; i<2; i++) {
|
||||||
|
var $offset = document.createElement('tr')
|
||||||
|
$offset.innerHTML = `
|
||||||
|
<td><input type="text" size="4" maxlength="3" id="name_${i}" value="${offsets[i][0]}"></td>
|
||||||
|
<td><input type="number" id="offset_${i}" value="${offsets[i][1]}"></td>`
|
||||||
|
tbl.append($offset);
|
||||||
|
}
|
||||||
|
document.getElementById("upload").addEventListener("click", function() {
|
||||||
|
var storage_offsets=[];
|
||||||
|
var app_offsets=[];
|
||||||
|
for (var i=0; i<2; i++) {
|
||||||
|
var name=document.getElementById("name_"+i).value;
|
||||||
|
var offset=document.getElementById("offset_"+i).value;
|
||||||
|
app_offsets.push([name,offset]);
|
||||||
|
storage_offsets.push([name,offset]);
|
||||||
|
}
|
||||||
|
console.log(storage_offsets);
|
||||||
|
console.log(app_offsets);
|
||||||
|
localStorage.setItem('a_clock_timer-offset-list',JSON.stringify(storage_offsets));
|
||||||
|
sendCustomizedApp({
|
||||||
|
storage:[
|
||||||
|
{name:"a_clock_timer.settings.json", content:JSON.stringify(app_offsets)},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
{
|
{
|
||||||
"id": "a_clock_timer",
|
"id": "a_clock_timer",
|
||||||
"name": "A Clock with Timer",
|
"name": "A Clock with Timer",
|
||||||
"version": "0.01",
|
"version": "1.00",
|
||||||
"description": "A Clock with Timer, Map and Time Zones",
|
"description": "A Clock with Timer, Map and Time Zones",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot-1.png"}],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
|
"custom": "custom.html",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"a_clock_timer.app.js","url":"app.js"},
|
{"name":"a_clock_timer.app.js","url":"app.js"},
|
||||||
{"name":"a_clock_timer.img","url":"app-icon.js","evaluate":true}
|
{"name":"a_clock_timer.img","url":"app-icon.js","evaluate":true}
|
||||||
]
|
],
|
||||||
|
"data": [{"name":"a_clock_timer.settings.json"}]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.9 KiB |
|
|
@ -1,2 +1,3 @@
|
||||||
1.00: Release (2021/12/01)
|
1.00: Release (2021/12/01)
|
||||||
1.01: Grey font when timer is frozen (2021/12/04)
|
1.01: Grey font when timer is frozen (2021/12/04)
|
||||||
|
1.02: Force light theme, since the app is not designed for dark theme (2022/12/28)
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,7 @@ function draw() {
|
||||||
g.drawRect(88+8,138-24, 176-10, 138+22);
|
g.drawRect(88+8,138-24, 176-10, 138+22);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
|
||||||
require("FontHaxorNarrow7x17").add(Graphics);
|
require("FontHaxorNarrow7x17").add(Graphics);
|
||||||
g.clear();
|
g.clear();
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id":"a_speech_timer",
|
"id":"a_speech_timer",
|
||||||
"name":"Speech Timer",
|
"name":"Speech Timer",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"1.01",
|
"version":"1.02",
|
||||||
"description": "A timer designed to help keeping your speeches and presentations to time.",
|
"description": "A timer designed to help keeping your speeches and presentations to time.",
|
||||||
"tags": "tool,timer",
|
"tags": "tool,timer",
|
||||||
"readme":"README.md",
|
"readme":"README.md",
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,6 @@
|
||||||
0.08: Fix error in clkinfo (didn't require Storage & locale)
|
0.08: Fix error in clkinfo (didn't require Storage & locale)
|
||||||
Fix clkinfo icon
|
Fix clkinfo icon
|
||||||
0.09: Ensure Agenda supplies an image for clkinfo items
|
0.09: Ensure Agenda supplies an image for clkinfo items
|
||||||
|
0.10: Update clock_info to avoid a redraw
|
||||||
|
0.11: Setting to use "Today" and "Yesterday" instead of dates
|
||||||
|
Added dynamic, short and range fields to clkinfo
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
(function() {
|
(function() {
|
||||||
|
function getPassedSec(date) {
|
||||||
|
var now = new Date();
|
||||||
|
var passed = (now-date)/1000;
|
||||||
|
if(passed<0) return 0;
|
||||||
|
return passed;
|
||||||
|
}
|
||||||
var agendaItems = {
|
var agendaItems = {
|
||||||
name: "Agenda",
|
name: "Agenda",
|
||||||
img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="),
|
img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="),
|
||||||
|
dynamic: true,
|
||||||
items: []
|
items: []
|
||||||
};
|
};
|
||||||
var locale = require("locale");
|
var locale = require("locale");
|
||||||
|
|
@ -15,12 +22,16 @@
|
||||||
var title = entry.title.slice(0,12);
|
var title = entry.title.slice(0,12);
|
||||||
var date = new Date(entry.timestamp*1000);
|
var date = new Date(entry.timestamp*1000);
|
||||||
var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
|
var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
|
||||||
|
var shortStr = ((date-now) > 86400000 || entry.allDay) ? dateStr : locale.time(date,1);
|
||||||
dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
|
dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
|
||||||
|
|
||||||
agendaItems.items.push({
|
agendaItems.items.push({
|
||||||
name: "Agenda "+i,
|
name: "Agenda "+i,
|
||||||
get: () => ({ text: title + "\n" + dateStr, img: agendaItems.img }),
|
hasRange: true,
|
||||||
show: function() { agendaItems.items[i].emit("redraw"); },
|
get: () => ({ text: title + "\n" + dateStr,
|
||||||
|
img: agendaItems.img, short: shortStr.trim(),
|
||||||
|
v: getPassedSec(date), min: 0, max: entry.durationInSeconds}),
|
||||||
|
show: function() {},
|
||||||
hide: function () {}
|
hide: function () {}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -33,16 +33,32 @@ CALENDAR=CALENDAR.sort((a,b)=>a.timestamp - b.timestamp);
|
||||||
function getDate(timestamp) {
|
function getDate(timestamp) {
|
||||||
return new Date(timestamp*1000);
|
return new Date(timestamp*1000);
|
||||||
}
|
}
|
||||||
|
function formatDay(date) {
|
||||||
|
if (!settings.useToday) {
|
||||||
|
return Locale.date(date);
|
||||||
|
}
|
||||||
|
const dateformatted = date.toISOString().split('T')[0]; // yyyy-mm-dd
|
||||||
|
const today = new Date(Date.now()).toISOString().split('T')[0]; // yyyy-mm-dd
|
||||||
|
if (dateformatted == today) {
|
||||||
|
return /*LANG*/"Today ";
|
||||||
|
} else {
|
||||||
|
const tomorrow = new Date(Date.now() + 86400 * 1000).toISOString().split('T')[0]; // yyyy-mm-dd
|
||||||
|
if (dateformatted == tomorrow) {
|
||||||
|
return /*LANG*/"Tomorrow ";
|
||||||
|
}
|
||||||
|
return Locale.date(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
function formatDateLong(date, includeDay, allDay) {
|
function formatDateLong(date, includeDay, allDay) {
|
||||||
let shortTime = Locale.time(date,1)+Locale.meridian(date);
|
let shortTime = Locale.time(date,1)+Locale.meridian(date);
|
||||||
if(allDay) shortTime = "";
|
if(allDay) shortTime = "";
|
||||||
if(includeDay || allDay)
|
if(includeDay || allDay) {
|
||||||
return Locale.date(date)+" "+shortTime;
|
return formatDay(date)+" "+shortTime;
|
||||||
|
}
|
||||||
return shortTime;
|
return shortTime;
|
||||||
}
|
}
|
||||||
function formatDateShort(date, allDay) {
|
function formatDateShort(date, allDay) {
|
||||||
return Locale.date(date).replace(/\d\d\d\d/,"")+(allDay?
|
return formatDay(date).replace(/\d\d\d\d/,"")+(allDay?"":Locale.time(date,1)+Locale.meridian(date));
|
||||||
"" : Locale.time(date,1)+Locale.meridian(date));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var lines = [];
|
var lines = [];
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "agenda",
|
"id": "agenda",
|
||||||
"name": "Agenda",
|
"name": "Agenda",
|
||||||
"version": "0.09",
|
"version": "0.11",
|
||||||
"description": "Simple agenda",
|
"description": "Simple agenda",
|
||||||
"icon": "agenda.png",
|
"icon": "agenda.png",
|
||||||
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,13 @@
|
||||||
updateSettings();
|
updateSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/*LANG*/"Use 'Today',..." : {
|
||||||
|
value : !!settings.useToday,
|
||||||
|
onchange: v => {
|
||||||
|
settings.useToday = v;
|
||||||
|
updateSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
E.showMenu(mainmenu);
|
E.showMenu(mainmenu);
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,6 @@
|
||||||
0.03: Do not load AGPS data on boot
|
0.03: Do not load AGPS data on boot
|
||||||
Increase minimum interval to 6 hours
|
Increase minimum interval to 6 hours
|
||||||
0.04: Write AGPS data chunks with delay to improve reliability
|
0.04: Write AGPS data chunks with delay to improve reliability
|
||||||
|
0.05: Show last success date
|
||||||
|
Do not start A-GPS update automatically
|
||||||
|
0.06: Switch off gps after updating
|
||||||
|
|
@ -23,12 +23,26 @@ Bangle.drawWidgets();
|
||||||
|
|
||||||
let waiting = false;
|
let waiting = false;
|
||||||
|
|
||||||
function start() {
|
function start(restart) {
|
||||||
g.reset();
|
g.reset();
|
||||||
g.clear();
|
g.clear();
|
||||||
waiting = false;
|
waiting = false;
|
||||||
display("Retry?", "touch to retry");
|
if (!restart) {
|
||||||
|
display("Start?", "touch to start");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
display("Retry?", "touch to retry");
|
||||||
|
}
|
||||||
Bangle.on("touch", () => { updateAgps(); });
|
Bangle.on("touch", () => { updateAgps(); });
|
||||||
|
|
||||||
|
const file = "agpsdata.json";
|
||||||
|
let data = require("Storage").readJSON(file, 1) || {};
|
||||||
|
if (data.lastUpdate) {
|
||||||
|
g.setFont("Vector", 11);
|
||||||
|
g.drawString("last success:", 5, g.getHeight() - 22);
|
||||||
|
g.drawString(new Date(data.lastUpdate).toISOString(), 5, g.getHeight() - 11);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAgps() {
|
function updateAgps() {
|
||||||
|
|
@ -36,7 +50,7 @@ function updateAgps() {
|
||||||
g.clear();
|
g.clear();
|
||||||
if (!waiting) {
|
if (!waiting) {
|
||||||
waiting = true;
|
waiting = true;
|
||||||
display("Updating A-GPS...", "takes ~ 10 seconds");
|
display("Updating A-GPS...", "takes ~10 seconds");
|
||||||
require("agpsdata").pull(function() {
|
require("agpsdata").pull(function() {
|
||||||
waiting = false;
|
waiting = false;
|
||||||
display("A-GPS updated.", "touch to close");
|
display("A-GPS updated.", "touch to close");
|
||||||
|
|
@ -45,10 +59,10 @@ function updateAgps() {
|
||||||
function(error) {
|
function(error) {
|
||||||
waiting = false;
|
waiting = false;
|
||||||
E.showAlert(error, "Error")
|
E.showAlert(error, "Error")
|
||||||
.then(() => { start(); });
|
.then(() => { start(true); });
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
display("Waiting...");
|
display("Waiting...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateAgps();
|
start(false);
|
||||||
|
|
|
||||||
|
|
@ -10,20 +10,24 @@ readSettings();
|
||||||
|
|
||||||
function setAGPS(b64) {
|
function setAGPS(b64) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
var initCommands = "Bangle.setGPSPower(1);\n"; // turn GPS on
|
|
||||||
const gnsstype = settings.gnsstype || 1; // default GPS
|
const gnsstype = settings.gnsstype || 1; // default GPS
|
||||||
initCommands += `Serial1.println("${CASIC_CHECKSUM("$PCAS04," + gnsstype)}")\n`; // set GNSS mode
|
|
||||||
// What about:
|
// What about:
|
||||||
// NAV-TIMEUTC (0x01 0x10)
|
// NAV-TIMEUTC (0x01 0x10)
|
||||||
// NAV-PV (0x01 0x03)
|
// NAV-PV (0x01 0x03)
|
||||||
// or AGPS.zip uses AID-INI (0x0B 0x01)
|
// or AGPS.zip uses AID-INI (0x0B 0x01)
|
||||||
|
Bangle.setGPSPower(1,"agpsdata"); // turn GPS on
|
||||||
eval(initCommands);
|
Serial1.println(CASIC_CHECKSUM("$PCAS04," + gnsstype)); // set GNSS mode
|
||||||
|
|
||||||
try {
|
try {
|
||||||
writeChunks(atob(b64), resolve);
|
writeChunks(atob(b64), ()=>{
|
||||||
|
setTimeout(()=>{
|
||||||
|
Bangle.setGPSPower(0,"agpsdata");
|
||||||
|
resolve();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("error:", e);
|
console.log("error:", e);
|
||||||
|
Bangle.setGPSPower(0,"agpsdata");
|
||||||
reject();
|
reject();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -36,9 +40,8 @@ function writeChunks(bin, resolve) {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
if (chunkI < bin.length) {
|
if (chunkI < bin.length) {
|
||||||
var chunk = bin.substr(chunkI, chunkSize);
|
var chunk = bin.substr(chunkI, chunkSize);
|
||||||
js = `Serial1.write(atob("${btoa(chunk)}"))\n`;
|
Serial1.write(atob(btoa(chunk)));
|
||||||
eval(js);
|
|
||||||
|
|
||||||
chunkI += chunkSize;
|
chunkI += chunkSize;
|
||||||
writeChunks(bin, resolve);
|
writeChunks(bin, resolve);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "A-GPS Data Downloader App",
|
"name": "A-GPS Data Downloader App",
|
||||||
"shortName":"A-GPS Data",
|
"shortName":"A-GPS Data",
|
||||||
"icon": "agpsdata.png",
|
"icon": "agpsdata.png",
|
||||||
"version":"0.04",
|
"version":"0.06",
|
||||||
"description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
|
"description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
|
||||||
"tags": "boot,tool,assisted,gps,agps,http",
|
"tags": "boot,tool,assisted,gps,agps,http",
|
||||||
"allow_emulator":true,
|
"allow_emulator":true,
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,6 @@
|
||||||
0.02: Design improvements and fixes.
|
0.02: Design improvements and fixes.
|
||||||
0.03: Indicate battery level through line occurrence.
|
0.03: Indicate battery level through line occurrence.
|
||||||
0.04: Use widget_utils module.
|
0.04: Use widget_utils module.
|
||||||
0.05: Support for clkinfo.
|
0.05: Support for clkinfo.
|
||||||
|
0.06: ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc.
|
||||||
|
0.07: Use clock_info.addInteractive instead of a custom implementation
|
||||||
|
|
@ -11,8 +11,7 @@ The original output of stable diffusion is shown here:
|
||||||
My implementation is shown below. Note that horizontal lines occur randomly, but the
|
My implementation is shown below. Note that horizontal lines occur randomly, but the
|
||||||
probability is correlated with the battery level. So if your screen contains only
|
probability is correlated with the battery level. So if your screen contains only
|
||||||
a few lines its time to charge your bangle again ;) Also note that the upper text
|
a few lines its time to charge your bangle again ;) Also note that the upper text
|
||||||
implementes the clkinfo module and can be configured via touch left/right/up/down.
|
implements the clkinfo module and can be configured via touch and swipe left/right and up/down.
|
||||||
Touch at the center to trigger the selected action.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
/************************************************
|
/************************************************
|
||||||
* AI Clock
|
* AI Clock
|
||||||
*/
|
*/
|
||||||
const storage = require('Storage');
|
|
||||||
const clock_info = require("clock_info");
|
const clock_info = require("clock_info");
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,147 +20,14 @@ Graphics.prototype.setFontGochiHand = function(scale) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************************
|
|
||||||
* Set some important constants such as width, height and center
|
|
||||||
*/
|
|
||||||
var W = g.getWidth(),R=W/2;
|
|
||||||
var H = g.getHeight();
|
|
||||||
var cx = W/2;
|
|
||||||
var cy = H/2;
|
|
||||||
var drawTimeout;
|
|
||||||
var lock_input = false;
|
|
||||||
|
|
||||||
|
function drawBackground(start, end) {
|
||||||
/************************************************
|
|
||||||
* SETTINGS
|
|
||||||
*/
|
|
||||||
const SETTINGS_FILE = "aiclock.setting.json";
|
|
||||||
let settings = {
|
|
||||||
menuPosX: 0,
|
|
||||||
menuPosY: 0,
|
|
||||||
};
|
|
||||||
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
|
||||||
for (const key in saved_settings) {
|
|
||||||
settings[key] = saved_settings[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/************************************************
|
|
||||||
* Menu
|
|
||||||
*/
|
|
||||||
function getDate(){
|
|
||||||
var date = new Date();
|
|
||||||
return ("0"+date.getDate()).substr(-2) + "/" + ("0"+(date.getMonth()+1)).substr(-2)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Custom clockItems menu - therefore, its added here and not in a clkinfo.js file.
|
|
||||||
var clockItems = {
|
|
||||||
name: getDate(),
|
|
||||||
img: null,
|
|
||||||
items: [
|
|
||||||
{ name: "Week",
|
|
||||||
get: () => ({ text: "Week " + weekOfYear(), img: null}),
|
|
||||||
show: function() { clockItems.items[0].emit("redraw"); },
|
|
||||||
hide: function () {}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
function weekOfYear() {
|
|
||||||
var date = new Date();
|
|
||||||
date.setHours(0, 0, 0, 0);
|
|
||||||
// Thursday in current week decides the year.
|
|
||||||
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
|
|
||||||
// January 4 is always in week 1.
|
|
||||||
var week1 = new Date(date.getFullYear(), 0, 4);
|
|
||||||
// Adjust to Thursday in week 1 and count number of weeks from date to week1.
|
|
||||||
return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000
|
|
||||||
- 3 + (week1.getDay() + 6) % 7) / 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Load menu
|
|
||||||
var menu = clock_info.load();
|
|
||||||
menu = menu.concat(clockItems);
|
|
||||||
|
|
||||||
|
|
||||||
// Ensure that our settings are still in range (e.g. app uninstall). Otherwise reset the position it.
|
|
||||||
if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPosX].items.length ){
|
|
||||||
settings.menuPosX = 0;
|
|
||||||
settings.menuPosY = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set draw functions for each item
|
|
||||||
menu.forEach((menuItm, x) => {
|
|
||||||
menuItm.items.forEach((item, y) => {
|
|
||||||
function drawItem() {
|
|
||||||
// For the clock, we have a special case, as we don't wanna redraw
|
|
||||||
// immediately when something changes. Instead, we update data each minute
|
|
||||||
// to save some battery etc. Therefore, we hide (and disable the listener)
|
|
||||||
// immedeately after redraw...
|
|
||||||
item.hide();
|
|
||||||
|
|
||||||
// After drawing the item, we enable inputs again...
|
|
||||||
lock_input = false;
|
|
||||||
|
|
||||||
var info = item.get();
|
|
||||||
drawMenuItem(info.text, info.img);
|
|
||||||
}
|
|
||||||
|
|
||||||
item.on('redraw', drawItem);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
function canRunMenuItem(){
|
|
||||||
if(settings.menuPosY == 0){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var menuEntry = menu[settings.menuPosX];
|
|
||||||
var item = menuEntry.items[settings.menuPosY-1];
|
|
||||||
return item.run !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function runMenuItem(){
|
|
||||||
if(settings.menuPosY == 0){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var menuEntry = menu[settings.menuPosX];
|
|
||||||
var item = menuEntry.items[settings.menuPosY-1];
|
|
||||||
try{
|
|
||||||
var ret = item.run();
|
|
||||||
if(ret){
|
|
||||||
Bangle.buzz(300, 0.6);
|
|
||||||
}
|
|
||||||
} catch (ex) {
|
|
||||||
// Simply ignore it...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Based on the great multi clock from https://github.com/jeffmer/BangleApps/
|
|
||||||
*/
|
|
||||||
Graphics.prototype.drawRotRect = function(w, r1, r2, angle) {
|
|
||||||
angle = angle % 360;
|
|
||||||
var w2=w/2, h=r2-r1, theta=angle*Math.PI/180;
|
|
||||||
return this.fillPoly(this.transformVertices([-w2,0,-w2,-h,w2,-h,w2,0],
|
|
||||||
{x:cx+r1*Math.sin(theta),y:cy-r1*Math.cos(theta),rotate:theta}));
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function drawBackground() {
|
|
||||||
g.setFontAlign(0,0);
|
g.setFontAlign(0,0);
|
||||||
g.setColor(g.theme.fg);
|
g.setColor("#000");
|
||||||
|
|
||||||
var bat = E.getBattery() / 100.0;
|
var bat = E.getBattery() / 100.0;
|
||||||
var y = 0;
|
var y = start;
|
||||||
while(y < H){
|
while(y < end){
|
||||||
// Show less lines in case of small battery level.
|
// Show less lines in case of small battery level.
|
||||||
if(Math.random() > bat){
|
if(Math.random() > bat){
|
||||||
y += 5;
|
y += 5;
|
||||||
|
|
@ -177,6 +43,30 @@ function drawBackground() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/************************************************
|
||||||
|
* Set some important constants such as width, height and center
|
||||||
|
*/
|
||||||
|
var W = g.getWidth(),R=W/2;
|
||||||
|
var H = g.getHeight();
|
||||||
|
var cx = W/2;
|
||||||
|
var cy = H/2;
|
||||||
|
var drawTimeout;
|
||||||
|
|
||||||
|
var clkInfoY = 60;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Based on the great multi clock from https://github.com/jeffmer/BangleApps/
|
||||||
|
*/
|
||||||
|
Graphics.prototype.drawRotRect = function(w, r1, r2, angle) {
|
||||||
|
angle = angle % 360;
|
||||||
|
var w2=w/2, h=r2-r1, theta=angle*Math.PI/180;
|
||||||
|
return this.fillPoly(this.transformVertices([-w2,0,-w2,-h,w2,-h,w2,0],
|
||||||
|
{x:cx+r1*Math.sin(theta),y:cy-r1*Math.cos(theta),rotate:theta}));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function drawCircle(isLocked){
|
function drawCircle(isLocked){
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
g.fillCircle(cx, cy, 12);
|
g.fillCircle(cx, cy, 12);
|
||||||
|
|
@ -186,56 +76,6 @@ function drawCircle(isLocked){
|
||||||
g.fillCircle(cx, cy, 6);
|
g.fillCircle(cx, cy, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toAngle(a){
|
|
||||||
if (a < 0){
|
|
||||||
return 360 + a;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(a > 360) {
|
|
||||||
return 360 - a;
|
|
||||||
}
|
|
||||||
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function drawMenuItem(text, image){
|
|
||||||
if(text == null){
|
|
||||||
drawTime();
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// image = atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==");
|
|
||||||
|
|
||||||
text = String(text);
|
|
||||||
|
|
||||||
g.reset().setBgColor("#fff").setColor("#000");
|
|
||||||
g.setFontAlign(0,0);
|
|
||||||
g.setFont("Vector", 20);
|
|
||||||
|
|
||||||
var imgWidth = image == null ? 0 : 24;
|
|
||||||
var strWidth = g.stringWidth(text);
|
|
||||||
var strHeight = text.split('\n').length > 1 ? 40 : Math.max(24, imgWidth+2);
|
|
||||||
var w = imgWidth + strWidth;
|
|
||||||
|
|
||||||
g.clearRect(cx-w/2-8, 40-strHeight/2-1, cx+w/2+4, 40+strHeight/2)
|
|
||||||
|
|
||||||
// Draw right line as designed by stable diffusion
|
|
||||||
g.drawLine(cx+w/2+5, 40-strHeight/2-1, cx+w/2+5, 40+strHeight/2);
|
|
||||||
g.drawLine(cx+w/2+6, 40-strHeight/2-1, cx+w/2+6, 40+strHeight/2);
|
|
||||||
g.drawLine(cx+w/2+7, 40-strHeight/2-1, cx+w/2+7, 40+strHeight/2);
|
|
||||||
|
|
||||||
// And finally the text
|
|
||||||
g.drawString(text, cx+imgWidth/2, 42);
|
|
||||||
g.drawString(text, cx+1+imgWidth/2, 41);
|
|
||||||
|
|
||||||
if(image != null) {
|
|
||||||
var scale = image.width ? imgWidth / image.width : 1;
|
|
||||||
g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 41-12, {scale: scale});
|
|
||||||
}
|
|
||||||
|
|
||||||
drawTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function drawTime(){
|
function drawTime(){
|
||||||
// Draw digital time first
|
// Draw digital time first
|
||||||
|
|
@ -292,35 +132,23 @@ function drawDigits(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function drawDate(){
|
function draw(){
|
||||||
var menuEntry = menu[settings.menuPosX];
|
// Note that we force a redraw also of the clock info as
|
||||||
|
// we want to ensure (for design purpose) that the hands
|
||||||
// The first entry is the overview...
|
// are above the clkinfo section.
|
||||||
if(settings.menuPosY == 0){
|
clockInfoMenu.redraw();
|
||||||
drawMenuItem(menuEntry.name, menuEntry.img);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw item if needed
|
|
||||||
lock_input = true;
|
|
||||||
var item = menuEntry.items[settings.menuPosY-1];
|
|
||||||
item.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function drawMainClock(){
|
||||||
|
|
||||||
|
|
||||||
function draw(){
|
|
||||||
// Queue draw in one minute
|
// Queue draw in one minute
|
||||||
queueDraw();
|
queueDraw();
|
||||||
|
|
||||||
g.reset();
|
g.setColor("#fff");
|
||||||
g.clearRect(0, 0, g.getWidth(), g.getHeight());
|
g.reset().clearRect(0, clkInfoY, g.getWidth(), g.getHeight());
|
||||||
g.setColor(1,1,1);
|
|
||||||
|
|
||||||
drawBackground();
|
drawBackground(clkInfoY, H);
|
||||||
drawDate();
|
drawTime();
|
||||||
drawCircle(Bangle.isLocked());
|
drawCircle(Bangle.isLocked());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -330,7 +158,7 @@ function draw(){
|
||||||
*/
|
*/
|
||||||
Bangle.on('lcdPower',on=>{
|
Bangle.on('lcdPower',on=>{
|
||||||
if (on) {
|
if (on) {
|
||||||
draw(true);
|
draw();
|
||||||
} else { // stop draw timer
|
} else { // stop draw timer
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
|
|
@ -341,66 +169,10 @@ Bangle.on('lock', function(isLocked) {
|
||||||
drawCircle(isLocked);
|
drawCircle(isLocked);
|
||||||
});
|
});
|
||||||
|
|
||||||
Bangle.on('touch', function(btn, e){
|
|
||||||
var left = parseInt(g.getWidth() * 0.22);
|
|
||||||
var right = g.getWidth() - left;
|
|
||||||
var upper = parseInt(g.getHeight() * 0.22);
|
|
||||||
var lower = g.getHeight() - upper;
|
|
||||||
|
|
||||||
var is_upper = e.y < upper;
|
|
||||||
var is_lower = e.y > lower;
|
|
||||||
var is_left = e.x < left && !is_upper && !is_lower;
|
|
||||||
var is_right = e.x > right && !is_upper && !is_lower;
|
|
||||||
var is_center = !is_upper && !is_lower && !is_left && !is_right;
|
|
||||||
|
|
||||||
if(lock_input){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(is_lower){
|
|
||||||
Bangle.buzz(40, 0.6);
|
|
||||||
settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1);
|
|
||||||
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(is_upper){
|
|
||||||
Bangle.buzz(40, 0.6);
|
|
||||||
settings.menuPosY = settings.menuPosY-1;
|
|
||||||
settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].items.length : settings.menuPosY;
|
|
||||||
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(is_right){
|
|
||||||
Bangle.buzz(40, 0.6);
|
|
||||||
settings.menuPosX = (settings.menuPosX+1) % menu.length;
|
|
||||||
settings.menuPosY = 0;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(is_left){
|
|
||||||
Bangle.buzz(40, 0.6);
|
|
||||||
settings.menuPosY = 0;
|
|
||||||
settings.menuPosX = settings.menuPosX-1;
|
|
||||||
settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(is_center){
|
|
||||||
if(canRunMenuItem()){
|
|
||||||
runMenuItem();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
E.on("kill", function(){
|
E.on("kill", function(){
|
||||||
try{
|
clockInfoMenu.remove();
|
||||||
storage.write(SETTINGS_FILE, settings);
|
delete clockInfoMenu;
|
||||||
} catch(ex){
|
|
||||||
// If this fails, we still kill the app...
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -416,6 +188,55 @@ function queueDraw() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/************************************************
|
||||||
|
* Clock Info
|
||||||
|
*/
|
||||||
|
let clockInfoItems = clock_info.load();
|
||||||
|
let clockInfoMenu = clock_info.addInteractive(clockInfoItems, {
|
||||||
|
x : 0,
|
||||||
|
y: 0,
|
||||||
|
w: W,
|
||||||
|
h: clkInfoY,
|
||||||
|
draw : (itm, info, options) => {
|
||||||
|
g.setFontAlign(0,0);
|
||||||
|
g.setFont("Vector", 20);
|
||||||
|
|
||||||
|
g.setColor("#fff");
|
||||||
|
g.fillRect(options.x, options.y, options.x+options.w, options.y+options.h);
|
||||||
|
drawBackground(0, clkInfoY+2);
|
||||||
|
|
||||||
|
// Set text and font
|
||||||
|
var image = info.img;
|
||||||
|
var text = String(info.text);
|
||||||
|
|
||||||
|
var imgWidth = image == null ? 0 : 24;
|
||||||
|
var strWidth = g.stringWidth(text);
|
||||||
|
var strHeight = text.split('\n').length > 1 ? 40 : Math.max(24, imgWidth+2);
|
||||||
|
var w = imgWidth + strWidth;
|
||||||
|
|
||||||
|
// Draw right line as designed by stable diffusion
|
||||||
|
g.setColor(options.focus ? "#0f0" : "#fff");
|
||||||
|
g.fillRect(cx-w/2-8, 40-strHeight/2-1, cx+w/2+4, 40+strHeight/2)
|
||||||
|
|
||||||
|
g.setColor("#000");
|
||||||
|
g.drawLine(cx+w/2+5, 40-strHeight/2-1, cx+w/2+5, 40+strHeight/2);
|
||||||
|
g.drawLine(cx+w/2+6, 40-strHeight/2-1, cx+w/2+6, 40+strHeight/2);
|
||||||
|
g.drawLine(cx+w/2+7, 40-strHeight/2-1, cx+w/2+7, 40+strHeight/2);
|
||||||
|
|
||||||
|
// Draw text and image
|
||||||
|
g.drawString(text, cx+imgWidth/2, 42);
|
||||||
|
g.drawString(text, cx+1+imgWidth/2, 41);
|
||||||
|
|
||||||
|
if(image != null) {
|
||||||
|
var scale = image.width ? imgWidth / image.width : 1;
|
||||||
|
g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 41-12, {scale: scale});
|
||||||
|
}
|
||||||
|
|
||||||
|
drawMainClock();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Lets start widgets, listen for btn etc.
|
* Lets start widgets, listen for btn etc.
|
||||||
*/
|
*/
|
||||||
|
|
@ -430,7 +251,7 @@ Bangle.loadWidgets();
|
||||||
require('widget_utils').hide();
|
require('widget_utils').hide();
|
||||||
|
|
||||||
// Clear the screen once, at startup and draw clock
|
// Clear the screen once, at startup and draw clock
|
||||||
g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
|
g.setTheme({bg:"#fff",fg:"#000",dark:false});
|
||||||
draw();
|
draw();
|
||||||
|
|
||||||
// After drawing the watch face, we can draw the widgets
|
// After drawing the watch face, we can draw the widgets
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "AI Clock",
|
"name": "AI Clock",
|
||||||
"shortName":"AI Clock",
|
"shortName":"AI Clock",
|
||||||
"icon": "aiclock.png",
|
"icon": "aiclock.png",
|
||||||
"version":"0.05",
|
"version":"0.07",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",
|
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",
|
||||||
|
|
|
||||||
|
|
@ -37,3 +37,4 @@
|
||||||
0.34: Add "Confirm" option to alarm/timer edit menus
|
0.34: Add "Confirm" option to alarm/timer edit menus
|
||||||
0.35: Add automatic translation of more strings
|
0.35: Add automatic translation of more strings
|
||||||
0.36: alarm widget moved out of app
|
0.36: alarm widget moved out of app
|
||||||
|
0.37: add message input and dated Events
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,18 @@
|
||||||
# Alarms & Timers
|
# Alarms & Timers
|
||||||
|
|
||||||
This app allows you to add/modify any alarms and timers.
|
This app allows you to add/modify any alarms, timers and events.
|
||||||
|
|
||||||
|
Optional: When a keyboard app is detected, you can add a message to display when any of these is triggered.
|
||||||
|
|
||||||
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.
|
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.
|
||||||
|
|
||||||
## Menu overview
|
## Menu overview
|
||||||
|
|
||||||
- `New...`
|
- `New...`
|
||||||
- `New Alarm` → Configure a new alarm
|
- `New Alarm` → Configure a new alarm (triggered based on time and day of week)
|
||||||
- `Repeat` → Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely)
|
- `Repeat` → Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely)
|
||||||
- `New Timer` → Configure a new timer
|
- `New Timer` → Configure a new timer (triggered based on amount of time elapsed in hours/minutes/seconds)
|
||||||
|
- `New Event` → Configure a new event (triggered based on time and date)
|
||||||
- `Advanced`
|
- `Advanced`
|
||||||
- `Scheduler settings` → Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details
|
- `Scheduler settings` → Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details
|
||||||
- `Enable All` → Enable _all_ disabled alarms & timers
|
- `Enable All` → Enable _all_ disabled alarms & timers
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,10 @@ function showMainMenu() {
|
||||||
};
|
};
|
||||||
|
|
||||||
alarms.forEach((e, index) => {
|
alarms.forEach((e, index) => {
|
||||||
var label = e.timer
|
var label = (e.timer
|
||||||
? require("time_utils").formatDuration(e.timer)
|
? require("time_utils").formatDuration(e.timer)
|
||||||
: require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : "");
|
: (e.date ? `${e.date.substring(5,10)} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : ""))
|
||||||
|
) + (e.msg ? " " + e.msg : "");
|
||||||
menu[label] = {
|
menu[label] = {
|
||||||
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
|
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
|
||||||
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
|
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
|
||||||
|
|
@ -67,11 +68,12 @@ function showNewMenu() {
|
||||||
"": { "title": /*LANG*/"New..." },
|
"": { "title": /*LANG*/"New..." },
|
||||||
"< Back": () => showMainMenu(),
|
"< Back": () => showMainMenu(),
|
||||||
/*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined),
|
/*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined),
|
||||||
/*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined)
|
/*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined),
|
||||||
|
/*LANG*/"Event": () => showEditAlarmMenu(undefined, undefined, true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
|
||||||
var isNew = alarmIndex === undefined;
|
var isNew = alarmIndex === undefined;
|
||||||
|
|
||||||
var alarm = require("sched").newDefaultAlarm();
|
var alarm = require("sched").newDefaultAlarm();
|
||||||
|
|
@ -82,11 +84,16 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var time = require("time_utils").decodeTime(alarm.t);
|
var time = require("time_utils").decodeTime(alarm.t);
|
||||||
|
if (withDate && !alarm.date) alarm.date = new Date().toLocalISOString().slice(0,10);
|
||||||
|
var date = alarm.date ? new Date(alarm.date) : undefined;
|
||||||
|
var title = date ? (isNew ? /*LANG*/"New Event" : /*LANG*/"Edit Event") : (isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm");
|
||||||
|
var keyboard = "textinput";
|
||||||
|
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
|
||||||
|
|
||||||
const menu = {
|
const menu = {
|
||||||
"": { "title": isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm" },
|
"": { "title": title },
|
||||||
"< Back": () => {
|
"< Back": () => {
|
||||||
prepareAlarmForSave(alarm, alarmIndex, time);
|
prepareAlarmForSave(alarm, alarmIndex, time, date);
|
||||||
saveAndReload();
|
saveAndReload();
|
||||||
showMainMenu();
|
showMainMenu();
|
||||||
},
|
},
|
||||||
|
|
@ -106,6 +113,36 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
||||||
wrap: true,
|
wrap: true,
|
||||||
onchange: v => time.m = v
|
onchange: v => time.m = v
|
||||||
},
|
},
|
||||||
|
/*LANG*/"Day": {
|
||||||
|
value: date ? date.getDate() : null,
|
||||||
|
min: 1,
|
||||||
|
max: 31,
|
||||||
|
wrap: true,
|
||||||
|
onchange: v => date.setDate(v)
|
||||||
|
},
|
||||||
|
/*LANG*/"Month": {
|
||||||
|
value: date ? date.getMonth() + 1 : null,
|
||||||
|
format: v => require("date_utils").month(v),
|
||||||
|
onchange: v => date.setMonth((v+11)%12)
|
||||||
|
},
|
||||||
|
/*LANG*/"Year": {
|
||||||
|
value: date ? date.getFullYear() : null,
|
||||||
|
min: new Date().getFullYear(),
|
||||||
|
max: 2100,
|
||||||
|
onchange: v => date.setFullYear(v)
|
||||||
|
},
|
||||||
|
/*LANG*/"Message": {
|
||||||
|
value: alarm.msg,
|
||||||
|
onchange: () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
keyboard.input({text:alarm.msg}).then(result => {
|
||||||
|
alarm.msg = result;
|
||||||
|
prepareAlarmForSave(alarm, alarmIndex, time, date, true);
|
||||||
|
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
},
|
||||||
/*LANG*/"Enabled": {
|
/*LANG*/"Enabled": {
|
||||||
value: alarm.on,
|
value: alarm.on,
|
||||||
onchange: v => alarm.on = v
|
onchange: v => alarm.on = v
|
||||||
|
|
@ -115,8 +152,8 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
||||||
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => {
|
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => {
|
||||||
alarm.rp = repeat;
|
alarm.rp = repeat;
|
||||||
alarm.dow = dow;
|
alarm.dow = dow;
|
||||||
alarm.t = require("time_utils").encodeTime(time);
|
prepareAlarmForSave(alarm, alarmIndex, time, date, true);
|
||||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
|
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v),
|
/*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v),
|
||||||
|
|
@ -136,6 +173,15 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!keyboard) delete menu[/*LANG*/"Message"];
|
||||||
|
if (alarm.date || withDate) {
|
||||||
|
delete menu[/*LANG*/"Repeat"];
|
||||||
|
} else {
|
||||||
|
delete menu[/*LANG*/"Day"];
|
||||||
|
delete menu[/*LANG*/"Month"];
|
||||||
|
delete menu[/*LANG*/"Year"];
|
||||||
|
}
|
||||||
|
|
||||||
if (!isNew) {
|
if (!isNew) {
|
||||||
menu[/*LANG*/"Delete"] = () => {
|
menu[/*LANG*/"Delete"] = () => {
|
||||||
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => {
|
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => {
|
||||||
|
|
@ -145,7 +191,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
||||||
showMainMenu();
|
showMainMenu();
|
||||||
} else {
|
} else {
|
||||||
alarm.t = require("time_utils").encodeTime(time);
|
alarm.t = require("time_utils").encodeTime(time);
|
||||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
|
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -154,14 +200,17 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareAlarmForSave(alarm, alarmIndex, time) {
|
function prepareAlarmForSave(alarm, alarmIndex, time, date, temp) {
|
||||||
alarm.t = require("time_utils").encodeTime(time);
|
alarm.t = require("time_utils").encodeTime(time);
|
||||||
alarm.last = alarm.t < require("time_utils").getCurrentTimeMillis() ? new Date().getDate() : 0;
|
alarm.last = alarm.t < require("time_utils").getCurrentTimeMillis() ? new Date().getDate() : 0;
|
||||||
|
if(date) alarm.date = date.toLocalISOString().slice(0,10);
|
||||||
|
|
||||||
if (alarmIndex === undefined) {
|
if(!temp) {
|
||||||
alarms.push(alarm);
|
if (alarmIndex === undefined) {
|
||||||
} else {
|
alarms.push(alarm);
|
||||||
alarms[alarmIndex] = alarm;
|
} else {
|
||||||
|
alarms[alarmIndex] = alarm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,6 +304,8 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var time = require("time_utils").decodeTime(timer.timer);
|
var time = require("time_utils").decodeTime(timer.timer);
|
||||||
|
var keyboard = "textinput";
|
||||||
|
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
|
||||||
|
|
||||||
const menu = {
|
const menu = {
|
||||||
"": { "title": isNew ? /*LANG*/"New Timer" : /*LANG*/"Edit Timer" },
|
"": { "title": isNew ? /*LANG*/"New Timer" : /*LANG*/"Edit Timer" },
|
||||||
|
|
@ -285,6 +336,18 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
|
||||||
wrap: true,
|
wrap: true,
|
||||||
onchange: v => time.s = v
|
onchange: v => time.s = v
|
||||||
},
|
},
|
||||||
|
/*LANG*/"Message": {
|
||||||
|
value: timer.msg,
|
||||||
|
onchange: () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
keyboard.input({text:timer.msg}).then(result => {
|
||||||
|
timer.msg = result;
|
||||||
|
prepareTimerForSave(timer, timerIndex, time, true);
|
||||||
|
setTimeout(showEditTimerMenu, 10, timer, timerIndex);
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
},
|
||||||
/*LANG*/"Enabled": {
|
/*LANG*/"Enabled": {
|
||||||
value: timer.on,
|
value: timer.on,
|
||||||
onchange: v => timer.on = v
|
onchange: v => timer.on = v
|
||||||
|
|
@ -306,6 +369,7 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!keyboard) delete menu[/*LANG*/"Message"];
|
||||||
if (!isNew) {
|
if (!isNew) {
|
||||||
menu[/*LANG*/"Delete"] = () => {
|
menu[/*LANG*/"Delete"] = () => {
|
||||||
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {
|
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {
|
||||||
|
|
@ -324,15 +388,17 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareTimerForSave(timer, timerIndex, time) {
|
function prepareTimerForSave(timer, timerIndex, time, temp) {
|
||||||
timer.timer = require("time_utils").encodeTime(time);
|
timer.timer = require("time_utils").encodeTime(time);
|
||||||
timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer;
|
timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer;
|
||||||
timer.last = 0;
|
timer.last = 0;
|
||||||
|
|
||||||
if (timerIndex === undefined) {
|
if (!temp) {
|
||||||
alarms.push(timer);
|
if (timerIndex === undefined) {
|
||||||
} else {
|
alarms.push(timer);
|
||||||
alarms[timerIndex] = timer;
|
} else {
|
||||||
|
alarms[timerIndex] = timer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "alarm",
|
"id": "alarm",
|
||||||
"name": "Alarms & Timers",
|
"name": "Alarms & Timers",
|
||||||
"shortName": "Alarms",
|
"shortName": "Alarms",
|
||||||
"version": "0.36",
|
"version": "0.37",
|
||||||
"description": "Set alarms and timers on your Bangle",
|
"description": "Set alarms and timers on your Bangle",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,alarm",
|
"tags": "tool,alarm",
|
||||||
|
|
|
||||||
|
|
@ -18,3 +18,5 @@
|
||||||
0.18: Use new message library
|
0.18: Use new message library
|
||||||
If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged)
|
If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged)
|
||||||
0.19: Add automatic translation for a couple of strings.
|
0.19: Add automatic translation for a couple of strings.
|
||||||
|
0.20: Fix wrong event used for forwarded GPS data from Gadgetbridge and add mapper to map longitude value correctly.
|
||||||
|
0.21: Fix broken 'Messages' button in menu
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,11 @@
|
||||||
event.satellites = NaN;
|
event.satellites = NaN;
|
||||||
event.course = NaN;
|
event.course = NaN;
|
||||||
event.fix = 1;
|
event.fix = 1;
|
||||||
Bangle.emit('gps', event);
|
if (event.long!==undefined) {
|
||||||
|
event.lon = event.long;
|
||||||
|
delete event.long;
|
||||||
|
}
|
||||||
|
Bangle.emit('GPS', event);
|
||||||
},
|
},
|
||||||
"is_gps_active": function() {
|
"is_gps_active": function() {
|
||||||
gbSend({ t: "gps_power", status: Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0 });
|
gbSend({ t: "gps_power", status: Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0 });
|
||||||
|
|
@ -208,7 +212,7 @@
|
||||||
// Replace set GPS power logic to suppress activation of gps (and instead request it from the phone)
|
// Replace set GPS power logic to suppress activation of gps (and instead request it from the phone)
|
||||||
Bangle.setGPSPower = (isOn, appID) => {
|
Bangle.setGPSPower = (isOn, appID) => {
|
||||||
// if not connected, use old logic
|
// if not connected, use old logic
|
||||||
if (!NRF.getSecurityStatus().connected) return originalSetGpsPower(isOn, appID);
|
if (!NRF.getSecurityStatus().connected) return originalSetGpsPower(isOn, appID);
|
||||||
// Emulate old GPS power logic
|
// Emulate old GPS power logic
|
||||||
if (!Bangle._PWR) Bangle._PWR={};
|
if (!Bangle._PWR) Bangle._PWR={};
|
||||||
if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];
|
if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "android",
|
"id": "android",
|
||||||
"name": "Android Integration",
|
"name": "Android Integration",
|
||||||
"shortName": "Android",
|
"shortName": "Android",
|
||||||
"version": "0.19",
|
"version": "0.21",
|
||||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
(function(back) {
|
(function(back) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function gb(j) {
|
function gb(j) {
|
||||||
Bluetooth.println(JSON.stringify(j));
|
Bluetooth.println(JSON.stringify(j));
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
updateSettings();
|
updateSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/*LANG*/"Messages" : ()=>require("message").openGUI(),
|
/*LANG*/"Messages" : ()=>require("messages").openGUI(),
|
||||||
};
|
};
|
||||||
E.showMenu(mainmenu);
|
E.showMenu(mainmenu);
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Update to work with Bangle.js 2
|
0.02: Update to work with Bangle.js 2
|
||||||
0.03: Select GNSS systems to use for Bangle.js 2
|
0.03: Select GNSS systems to use for Bangle.js 2
|
||||||
|
0.04: Now turns GPS off after upload
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@
|
||||||
function jsFromBase64(b64) {
|
function jsFromBase64(b64) {
|
||||||
var bin = atob(b64);
|
var bin = atob(b64);
|
||||||
var chunkSize = 128;
|
var chunkSize = 128;
|
||||||
var js = "\x10Bangle.setGPSPower(1);\n"; // turn GPS on
|
var js = "\x10Bangle.setGPSPower(1,'agps');\n"; // turn GPS on
|
||||||
if (isB1) { // UBLOX
|
if (isB1) { // UBLOX
|
||||||
//js += `\x10Bangle.on('GPS-raw',function (d) { if (d.startsWith("\\xB5\\x62\\x05\\x01")) Terminal.println("GPS ACK"); else if (d.startsWith("\\xB5\\x62\\x05\\x00")) Terminal.println("GPS NACK"); })\n`;
|
//js += `\x10Bangle.on('GPS-raw',function (d) { if (d.startsWith("\\xB5\\x62\\x05\\x01")) Terminal.println("GPS ACK"); else if (d.startsWith("\\xB5\\x62\\x05\\x00")) Terminal.println("GPS NACK"); })\n`;
|
||||||
//js += "\x10var t=getTime()+1;while(t>getTime());\n"; // wait 1 sec
|
//js += "\x10var t=getTime()+1;while(t>getTime());\n"; // wait 1 sec
|
||||||
|
|
@ -158,6 +158,7 @@
|
||||||
var chunk = bin.substr(i,chunkSize);
|
var chunk = bin.substr(i,chunkSize);
|
||||||
js += `\x10Serial1.write(atob("${btoa(chunk)}"))\n`;
|
js += `\x10Serial1.write(atob("${btoa(chunk)}"))\n`;
|
||||||
}
|
}
|
||||||
|
js = "\x10setTimeout(() => Bangle.setGPSPower(0,'agps'), 1000);\n"; // turn GPS off after a delay
|
||||||
return js;
|
return js;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "assistedgps",
|
"id": "assistedgps",
|
||||||
"name": "Assisted GPS Updater (AGPS)",
|
"name": "Assisted GPS Updater (AGPS)",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
|
"description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
|
||||||
"sortorder": -1,
|
"sortorder": -1,
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,4 @@
|
||||||
0.14: Use ClockFace_menu.addItems
|
0.14: Use ClockFace_menu.addItems
|
||||||
0.15: Add Power saving option
|
0.15: Add Power saving option
|
||||||
0.16: Support Fast Loading
|
0.16: Support Fast Loading
|
||||||
|
0.17: Hide widgets instead of not loading them at all
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "barclock",
|
"id": "barclock",
|
||||||
"name": "Bar Clock",
|
"name": "Bar Clock",
|
||||||
"version": "0.16",
|
"version": "0.17",
|
||||||
"description": "A simple digital clock showing seconds as a bar",
|
"description": "A simple digital clock showing seconds as a bar",
|
||||||
"icon": "clock-bar.png",
|
"icon": "clock-bar.png",
|
||||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],
|
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
(function(back) {
|
(function(back) {
|
||||||
let s = require("Storage").readJSON("barclock.settings.json", true) || {};
|
let s = require("Storage").readJSON("barclock.settings.json", true) || {};
|
||||||
|
// migrate "don't load widgets" to "hide widgets"
|
||||||
|
if (!("hideWidgets" in s) && ("loadWidgets" in s) && !s.loadWidgets) {
|
||||||
|
s.hideWidgets = 1;
|
||||||
|
}
|
||||||
|
delete s.loadWidgets;
|
||||||
|
|
||||||
function save(key, value) {
|
function save(key, value) {
|
||||||
s[key] = value;
|
s[key] = value;
|
||||||
|
|
@ -19,7 +24,7 @@
|
||||||
};
|
};
|
||||||
let items = {
|
let items = {
|
||||||
showDate: s.showDate,
|
showDate: s.showDate,
|
||||||
loadWidgets: s.loadWidgets,
|
hideWidgets: s.hideWidgets,
|
||||||
};
|
};
|
||||||
// Power saving for Bangle.js 1 doesn't make sense (no updates while screen is off anyway)
|
// Power saving for Bangle.js 1 doesn't make sense (no updates while screen is off anyway)
|
||||||
if (process.env.HWVERSION>1) {
|
if (process.env.HWVERSION>1) {
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,4 @@
|
||||||
0.05: Update *on* the minute rather than every 15 secs
|
0.05: Update *on* the minute rather than every 15 secs
|
||||||
Now show widgets
|
Now show widgets
|
||||||
Make compatible with themes, and Bangle.js 2
|
Make compatible with themes, and Bangle.js 2
|
||||||
|
0.06: Enable fastloading
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
{
|
||||||
// Berlin Clock see https://en.wikipedia.org/wiki/Mengenlehreuhr
|
// Berlin Clock see https://en.wikipedia.org/wiki/Mengenlehreuhr
|
||||||
// https://github.com/eska-muc/BangleApps
|
// https://github.com/eska-muc/BangleApps
|
||||||
const fields = [4, 4, 11, 4];
|
const fields = [4, 4, 11, 4];
|
||||||
|
|
@ -6,18 +7,18 @@ const width = g.getWidth() - 2 * offset;
|
||||||
const height = g.getHeight() - 2 * offset;
|
const height = g.getHeight() - 2 * offset;
|
||||||
const rowHeight = height / 4;
|
const rowHeight = height / 4;
|
||||||
|
|
||||||
var show_date = false;
|
let show_date = false;
|
||||||
var show_time = false;
|
let show_time = false;
|
||||||
var yy = 0;
|
let yy = 0;
|
||||||
|
|
||||||
var rowlights = [];
|
let rowlights = [];
|
||||||
var time_digit = [];
|
let time_digit = [];
|
||||||
|
|
||||||
// timeout used to update every minute
|
// timeout used to update every minute
|
||||||
var drawTimeout;
|
let drawTimeout;
|
||||||
|
|
||||||
// schedule a draw for the next minute
|
// schedule a draw for the next minute
|
||||||
function queueDraw() {
|
let queueDraw = () => {
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = setTimeout(function() {
|
drawTimeout = setTimeout(function() {
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
|
|
@ -25,7 +26,7 @@ function queueDraw() {
|
||||||
}, 60000 - (Date.now() % 60000));
|
}, 60000 - (Date.now() % 60000));
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw() {
|
let draw = () => {
|
||||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
|
g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
|
|
||||||
|
|
@ -84,28 +85,39 @@ function draw() {
|
||||||
queueDraw();
|
queueDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleDate() {
|
let toggleDate = () => {
|
||||||
show_date = ! show_date;
|
show_date = ! show_date;
|
||||||
draw();
|
draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleTime() {
|
let toggleTime = () => {
|
||||||
show_time = ! show_time;
|
show_time = ! show_time;
|
||||||
draw();
|
draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop updates when LCD is off, restart when on
|
let clear = () => {
|
||||||
Bangle.on('lcdPower',on=>{
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let onLcdPower = on => {
|
||||||
if (on) {
|
if (on) {
|
||||||
draw(); // draw immediately, queue redraw
|
draw(); // draw immediately, queue redraw
|
||||||
} else { // stop draw timer
|
} else { // stop draw timer
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
clear();
|
||||||
drawTimeout = undefined;
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
let cleanup = () => {
|
||||||
|
clear();
|
||||||
|
Bangle.removeListener("lcdPower", onLcdPower);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop updates when LCD is off, restart when on
|
||||||
|
Bangle.on('lcdPower',onLcdPower);
|
||||||
|
|
||||||
// Show launcher when button pressed, handle up/down
|
// Show launcher when button pressed, handle up/down
|
||||||
Bangle.setUI("clockupdown", dir=> {
|
Bangle.setUI({mode: "clockupdown", remove: cleanup}, dir=> {
|
||||||
if (dir<0) toggleTime();
|
if (dir<0) toggleTime();
|
||||||
if (dir>0) toggleDate();
|
if (dir>0) toggleDate();
|
||||||
});
|
});
|
||||||
|
|
@ -114,3 +126,4 @@ g.clear();
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
draw();
|
draw();
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "berlinc",
|
"id": "berlinc",
|
||||||
"name": "Berlin Clock",
|
"name": "Berlin Clock",
|
||||||
"version": "0.05",
|
"version": "0.06",
|
||||||
"description": "Berlin Clock (see https://en.wikipedia.org/wiki/Mengenlehreuhr)",
|
"description": "Berlin Clock (see https://en.wikipedia.org/wiki/Mengenlehreuhr)",
|
||||||
"icon": "berlin-clock.png",
|
"icon": "berlin-clock.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,13 @@
|
||||||
0.20: Better handling of async data such as getPressure.
|
0.20: Better handling of async data such as getPressure.
|
||||||
0.21: On the default menu the week of year can be shown.
|
0.21: On the default menu the week of year can be shown.
|
||||||
0.22: Use the new clkinfo module for the menu.
|
0.22: Use the new clkinfo module for the menu.
|
||||||
0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo.
|
0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo.
|
||||||
|
0.24: Update clock_info to avoid a redraw
|
||||||
|
0.25: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on fw2v16.
|
||||||
|
ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc.
|
||||||
|
0.26: Use clkinfo.addInteractive instead of a custom implementation
|
||||||
|
0.27: Clean out some leftovers in the remove function after switching to
|
||||||
|
clkinfo.addInteractive that would cause ReferenceError.
|
||||||
|
0.28: Option to show (1) time only and (2) week of year.
|
||||||
|
0.29: use setItem of clockInfoMenu to change the active item
|
||||||
|
0.30: Use widget_utils.
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,12 @@ A very minimalistic clock.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
The BW clock implements features that are exposed by other apps through the `clkinfo` module.
|
The BW clock implements features that are exposed by other apps through the `clkinfo` module.
|
||||||
For example, if you install the HomeAssistant app, this menu item will be shown if you click right
|
For example, if you install the HomeAssistant app, this menu item will be shown if you first
|
||||||
and additionally allows you to send triggers directly from the clock (select triggers via up/down and
|
touch the bottom of the screen and then swipe left/right to the home assistant menu. To select
|
||||||
send via click center). Here are examples of other apps that are integrated:
|
sub-items simply swipe up/down. To run an action (e.g. trigger home assistant), simply select the clkinfo (border) and touch on the item again. See also the screenshot below:
|
||||||
|
|
||||||
- Bangle data such as steps, heart rate, battery or charging state.
|

|
||||||
- Show agenda entries. A timer for an agenda entry can also be set by simply clicking in the middle of the screen. This can be used to not forget a meeting etc. Note that only one agenda-timer can be set at a time. *Requirement: Gadgetbridge calendar sync enabled*
|
|
||||||
- Weather temperature as well as the wind speed can be shown. *Requirement: Weather app*
|
|
||||||
- HomeAssistant triggers can be executed directly. *Requirement: HomeAssistant app*
|
|
||||||
|
|
||||||
Note: If some apps are not installed (e.gt. weather app), then this menu item is hidden.
|
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
- Screen: Normal (widgets shown), Dynamic (widgets shown if unlocked) or Full (widgets are hidden).
|
- Screen: Normal (widgets shown), Dynamic (widgets shown if unlocked) or Full (widgets are hidden).
|
||||||
|
|
@ -22,25 +18,6 @@ Note: If some apps are not installed (e.gt. weather app), then this menu item is
|
||||||
- The colon (e.g. 7:35 = 735) can be hidden in the settings for an even larger time font to improve readability further.
|
- The colon (e.g. 7:35 = 735) can be hidden in the settings for an even larger time font to improve readability further.
|
||||||
- Your bangle uses the sys color settings so you can change the color too.
|
- Your bangle uses the sys color settings so you can change the color too.
|
||||||
|
|
||||||
## Menu structure
|
|
||||||
2D menu allows you to display lots of different data including data from 3rd party apps and it's also possible to control things e.g. to trigger HomeAssistant.
|
|
||||||
|
|
||||||
Simply click left / right to go through the menu entries such as Bangle, Weather etc.
|
|
||||||
and click up/down to move into this sub-menu. You can then click in the middle of the screen
|
|
||||||
to e.g. send a trigger via HomeAssistant once you selected it. The actions really depend
|
|
||||||
on the app that provide this sub-menu through the `clkinfo` module.
|
|
||||||
|
|
||||||
```
|
|
||||||
Bangle -- Agenda -- Weather -- HomeAssistant
|
|
||||||
| | | |
|
|
||||||
Battery Entry 1 Temperature Trigger1
|
|
||||||
| | | |
|
|
||||||
Steps ... ... ...
|
|
||||||
|
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Thanks to
|
## Thanks to
|
||||||
- Thanks to Gordon Williams not only for the great BangleJs, but specifically also for the implementation of `clkinfo` which simplified the BWClock a lot and moved complexety to the apps where it should be located.
|
- Thanks to Gordon Williams not only for the great BangleJs, but specifically also for the implementation of `clkinfo` which simplified the BWClock a lot and moved complexety to the apps where it should be located.
|
||||||
- <a href="https://www.flaticon.com/free-icons/" title="Icons">Icons created by Flaticon</a>
|
- <a href="https://www.flaticon.com/free-icons/" title="Icons">Icons created by Flaticon</a>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
|
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||||
|
|
||||||
/************************************************
|
/************************************************
|
||||||
* Includes
|
* Includes
|
||||||
*/
|
*/
|
||||||
const locale = require('locale');
|
const locale = require('locale');
|
||||||
const storage = require('Storage');
|
const storage = require('Storage');
|
||||||
const clock_info = require("clock_info");
|
const clock_info = require("clock_info");
|
||||||
|
const widget_utils = require("widget_utils");
|
||||||
|
|
||||||
/************************************************
|
/************************************************
|
||||||
* Globals
|
* Globals
|
||||||
|
|
@ -12,8 +14,6 @@ const clock_info = require("clock_info");
|
||||||
const SETTINGS_FILE = "bwclk.setting.json";
|
const SETTINGS_FILE = "bwclk.setting.json";
|
||||||
const W = g.getWidth();
|
const W = g.getWidth();
|
||||||
const H = g.getHeight();
|
const H = g.getHeight();
|
||||||
var lock_input = false;
|
|
||||||
|
|
||||||
|
|
||||||
/************************************************
|
/************************************************
|
||||||
* Settings
|
* Settings
|
||||||
|
|
@ -28,7 +28,20 @@ let settings = {
|
||||||
|
|
||||||
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
||||||
for (const key in saved_settings) {
|
for (const key in saved_settings) {
|
||||||
settings[key] = saved_settings[key]
|
settings[key] = saved_settings[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
let isFullscreen = function() {
|
||||||
|
var s = settings.screen.toLowerCase();
|
||||||
|
if(s == "dynamic"){
|
||||||
|
return Bangle.isLocked();
|
||||||
|
} else {
|
||||||
|
return s == "full";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let getLineY = function(){
|
||||||
|
return H/5*2 + (isFullscreen() ? 0 : 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************************
|
/************************************************
|
||||||
|
|
@ -74,32 +87,22 @@ Graphics.prototype.setMiniFont = function(scale) {
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
function imgLock(){
|
let imgLock = function() {
|
||||||
return {
|
return {
|
||||||
width : 16, height : 16, bpp : 1,
|
width : 16, height : 16, bpp : 1,
|
||||||
transparent : 0,
|
transparent : 0,
|
||||||
buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w="))
|
buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w="))
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/************************************************
|
/************************************************
|
||||||
* Menu
|
* Clock Info
|
||||||
*/
|
*/
|
||||||
// Custom bwItems menu - therefore, its added here and not in a clkinfo.js file.
|
let clockInfoItems = clock_info.load();
|
||||||
var bwItems = {
|
|
||||||
name: null,
|
|
||||||
img: null,
|
|
||||||
items: [
|
|
||||||
{ name: "WeekOfYear",
|
|
||||||
get: () => ({ text: "Week " + weekOfYear(), img: null}),
|
|
||||||
show: function() { bwItems.items[0].emit("redraw"); },
|
|
||||||
hide: function () {}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
function weekOfYear() {
|
// Add some custom clock-infos
|
||||||
|
let weekOfYear = function() {
|
||||||
var date = new Date();
|
var date = new Date();
|
||||||
date.setHours(0, 0, 0, 0);
|
date.setHours(0, 0, 0, 0);
|
||||||
// Thursday in current week decides the year.
|
// Thursday in current week decides the year.
|
||||||
|
|
@ -111,87 +114,98 @@ function weekOfYear() {
|
||||||
- 3 + (week1.getDay() + 6) % 7) / 7);
|
- 3 + (week1.getDay() + 6) % 7) / 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clockInfoItems[0].items.unshift({ name : "weekofyear",
|
||||||
|
get : function() { return { text : "Week " + weekOfYear(),
|
||||||
|
img : null}},
|
||||||
|
show : function() {},
|
||||||
|
hide : function() {},
|
||||||
|
})
|
||||||
|
|
||||||
// Load menu
|
// Empty for large time
|
||||||
var menu = clock_info.load();
|
clockInfoItems[0].items.unshift({ name : "nop",
|
||||||
menu = menu.concat(bwItems);
|
get : function() { return { text : null,
|
||||||
|
img : null}},
|
||||||
|
show : function() {},
|
||||||
|
hide : function() {},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
// Ensure that our settings are still in range (e.g. app uninstall). Otherwise reset the position it.
|
|
||||||
if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPosX].items.length ){
|
|
||||||
settings.menuPosX = 0;
|
|
||||||
settings.menuPosY = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set draw functions for each item
|
let clockInfoMenu = clock_info.addInteractive(clockInfoItems, {
|
||||||
menu.forEach((menuItm, x) => {
|
x : 0,
|
||||||
menuItm.items.forEach((item, y) => {
|
y: 135,
|
||||||
function drawItem() {
|
w: W,
|
||||||
// For the clock, we have a special case, as we don't wanna redraw
|
h: H-135,
|
||||||
// immediately when something changes. Instead, we update data each minute
|
draw : (itm, info, options) => {
|
||||||
// to save some battery etc. Therefore, we hide (and disable the listener)
|
var hideClkInfo = info.text == null;
|
||||||
// immedeately after redraw...
|
|
||||||
item.hide();
|
|
||||||
|
|
||||||
// After drawing the item, we enable inputs again...
|
g.setColor(g.theme.fg);
|
||||||
lock_input = false;
|
g.fillRect(options.x, options.y, options.x+options.w, options.y+options.h);
|
||||||
|
|
||||||
var info = item.get();
|
g.setFontAlign(0,0);
|
||||||
drawMenuItem(info.text, info.img);
|
g.setColor(g.theme.bg);
|
||||||
|
|
||||||
|
if (options.focus){
|
||||||
|
var y = hideClkInfo ? options.y+20 : options.y+2;
|
||||||
|
var h = hideClkInfo ? options.h-20 : options.h-2;
|
||||||
|
g.drawRect(options.x, y, options.x+options.w-2, y+h-1); // show if focused
|
||||||
|
g.drawRect(options.x+1, y+1, options.x+options.w-3, y+h-2); // show if focused
|
||||||
}
|
}
|
||||||
|
|
||||||
item.on('redraw', drawItem);
|
// In case we hide the clkinfo, we show the time again as the time should
|
||||||
})
|
// be drawn larger.
|
||||||
|
if(hideClkInfo){
|
||||||
|
drawTime();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set text and font
|
||||||
|
var image = info.img;
|
||||||
|
var text = String(info.text);
|
||||||
|
if(text.split('\n').length > 1){
|
||||||
|
g.setMiniFont();
|
||||||
|
} else {
|
||||||
|
g.setSmallFont();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute sizes
|
||||||
|
var strWidth = g.stringWidth(text);
|
||||||
|
var imgWidth = image == null ? 0 : 24;
|
||||||
|
var midx = options.x+options.w/2;
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
if (image) {
|
||||||
|
var scale = imgWidth / image.width;
|
||||||
|
g.drawImage(image, midx-parseInt(imgWidth*1.3/2)-parseInt(strWidth/2), options.y+6, {scale: scale});
|
||||||
|
}
|
||||||
|
g.drawString(text, midx+parseInt(imgWidth*1.3/2), options.y+20);
|
||||||
|
|
||||||
|
// In case we are in focus and the focus box changes (fullscreen yes/no)
|
||||||
|
// we draw the time again. Otherwise it could happen that a while line is
|
||||||
|
// not cleared correctly.
|
||||||
|
if(options.focus) drawTime();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function canRunMenuItem(){
|
|
||||||
if(settings.menuPosY == 0){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var menuEntry = menu[settings.menuPosX];
|
|
||||||
var item = menuEntry.items[settings.menuPosY-1];
|
|
||||||
return item.run !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function runMenuItem(){
|
|
||||||
if(settings.menuPosY == 0){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var menuEntry = menu[settings.menuPosX];
|
|
||||||
var item = menuEntry.items[settings.menuPosY-1];
|
|
||||||
try{
|
|
||||||
var ret = item.run();
|
|
||||||
if(ret){
|
|
||||||
Bangle.buzz(300, 0.6);
|
|
||||||
}
|
|
||||||
} catch (ex) {
|
|
||||||
// Simply ignore it...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/************************************************
|
/************************************************
|
||||||
* Draw
|
* Draw
|
||||||
*/
|
*/
|
||||||
function draw() {
|
let draw = function() {
|
||||||
// Queue draw again
|
// Queue draw again
|
||||||
queueDraw();
|
queueDraw();
|
||||||
|
|
||||||
// Draw clock
|
// Draw clock
|
||||||
drawDate();
|
drawDate();
|
||||||
drawMenuAndTime();
|
drawTime();
|
||||||
drawLock();
|
drawLock();
|
||||||
drawWidgets();
|
drawWidgets();
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
function drawDate(){
|
let drawDate = function() {
|
||||||
// Draw background
|
// Draw background
|
||||||
var y = H/5*2 + (isFullscreen() ? 0 : 8);
|
var y = getLineY()
|
||||||
g.reset().clearRect(0,0,W,y);
|
g.reset().clearRect(0,0,W,y);
|
||||||
|
|
||||||
// Draw date
|
// Draw date
|
||||||
|
|
@ -216,17 +230,17 @@ function drawDate(){
|
||||||
g.setMediumFont();
|
g.setMediumFont();
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
g.drawString(dateStr, W/2 - fullDateW / 2, y+2);
|
g.drawString(dateStr, W/2 - fullDateW / 2, y+2);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
function drawTime(y, smallText){
|
let drawTime = function() {
|
||||||
|
var hideClkInfo = clockInfoMenu.menuA == 0 && clockInfoMenu.menuB == 0;
|
||||||
|
|
||||||
// Draw background
|
// Draw background
|
||||||
|
var y1 = getLineY();
|
||||||
|
var y = y1;
|
||||||
var date = new Date();
|
var date = new Date();
|
||||||
|
|
||||||
// Draw time
|
|
||||||
g.setColor(g.theme.bg);
|
|
||||||
g.setFontAlign(0,0);
|
|
||||||
|
|
||||||
var hours = String(date.getHours());
|
var hours = String(date.getHours());
|
||||||
var minutes = date.getMinutes();
|
var minutes = date.getMinutes();
|
||||||
minutes = minutes < 10 ? String("0") + minutes : minutes;
|
minutes = minutes < 10 ? String("0") + minutes : minutes;
|
||||||
|
|
@ -236,212 +250,93 @@ function drawTime(y, smallText){
|
||||||
// Set y coordinates correctly
|
// Set y coordinates correctly
|
||||||
y += parseInt((H - y)/2) + 5;
|
y += parseInt((H - y)/2) + 5;
|
||||||
|
|
||||||
// Show large or small time depending on info entry
|
if (hideClkInfo){
|
||||||
if(smallText){
|
g.setLargeFont();
|
||||||
|
} else {
|
||||||
y -= 15;
|
y -= 15;
|
||||||
g.setMediumFont();
|
g.setMediumFont();
|
||||||
} else {
|
|
||||||
g.setLargeFont();
|
|
||||||
}
|
}
|
||||||
g.drawString(timeStr, W/2, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawMenuItem(text, image){
|
|
||||||
// First clear the time region
|
|
||||||
var y = H/5*2 + (isFullscreen() ? 0 : 8);
|
|
||||||
|
|
||||||
|
// Clear region and draw time
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
g.fillRect(0,y,W,H);
|
g.fillRect(0,y1,W,y+20 + (hideClkInfo ? 1 : 0) + (isFullscreen() ? 3 : 0));
|
||||||
|
|
||||||
// Draw menu text
|
g.setColor(g.theme.bg);
|
||||||
var hasText = (text != null && text != "");
|
g.setFontAlign(0,0);
|
||||||
if(hasText){
|
g.drawString(timeStr, W/2, y);
|
||||||
g.setFontAlign(0,0);
|
};
|
||||||
|
|
||||||
// For multiline text we show an even smaller font...
|
|
||||||
text = String(text);
|
|
||||||
if(text.split('\n').length > 1){
|
|
||||||
g.setMiniFont();
|
|
||||||
} else {
|
|
||||||
g.setSmallFont();
|
|
||||||
}
|
|
||||||
|
|
||||||
var imgWidth = image == null ? 0 : 24;
|
|
||||||
var strWidth = g.stringWidth(text);
|
|
||||||
g.setColor(g.theme.fg).fillRect(0, 149-14, W, H);
|
|
||||||
g.setColor(g.theme.bg).drawString(text, W/2 + imgWidth/2 + 2, 149+3);
|
|
||||||
|
|
||||||
if(image != null){
|
|
||||||
var scale = imgWidth / image.width;
|
|
||||||
g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 149 - parseInt(imgWidth/2), {scale: scale});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw time
|
|
||||||
drawTime(y, hasText);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function drawMenuAndTime(){
|
let drawLock = function() {
|
||||||
var menuEntry = menu[settings.menuPosX];
|
|
||||||
|
|
||||||
// The first entry is the overview...
|
|
||||||
if(settings.menuPosY == 0){
|
|
||||||
drawMenuItem(menuEntry.name, menuEntry.img);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw item if needed
|
|
||||||
lock_input = true;
|
|
||||||
var item = menuEntry.items[settings.menuPosY-1];
|
|
||||||
item.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function drawLock(){
|
|
||||||
if(settings.showLock && Bangle.isLocked()){
|
if(settings.showLock && Bangle.isLocked()){
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
g.drawImage(imgLock(), W-16, 2);
|
g.drawImage(imgLock(), W-16, 2);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
function drawWidgets(){
|
let drawWidgets = function() {
|
||||||
if(isFullscreen()){
|
if(isFullscreen()){
|
||||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
widget_utils.hide();
|
||||||
} else {
|
} else {
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
function isFullscreen(){
|
|
||||||
var s = settings.screen.toLowerCase();
|
|
||||||
if(s == "dynamic"){
|
|
||||||
return Bangle.isLocked()
|
|
||||||
} else {
|
|
||||||
return s == "full"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/************************************************
|
/************************************************
|
||||||
* Listener
|
* Listener
|
||||||
*/
|
*/
|
||||||
// timeout used to update every minute
|
// timeout used to update every minute
|
||||||
var drawTimeout;
|
let drawTimeout;
|
||||||
|
|
||||||
// schedule a draw for the next minute
|
// schedule a draw for the next minute
|
||||||
function queueDraw() {
|
let queueDraw = function() {
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = setTimeout(function() {
|
drawTimeout = setTimeout(function() {
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
draw();
|
draw();
|
||||||
}, 60000 - (Date.now() % 60000));
|
}, 60000 - (Date.now() % 60000));
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
// Stop updates when LCD is off, restart when on
|
// Stop updates when LCD is off, restart when on
|
||||||
Bangle.on('lcdPower',on=>{
|
let lcdListenerBw = function(on) {
|
||||||
if (on) {
|
if (on) {
|
||||||
draw(); // draw immediately, queue redraw
|
draw(); // draw immediately, queue redraw
|
||||||
} else { // stop draw timer
|
} else { // stop draw timer
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
Bangle.on('lcdPower', lcdListenerBw);
|
||||||
|
|
||||||
Bangle.on('lock', function(isLocked) {
|
let lockListenerBw = function(isLocked) {
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
|
|
||||||
if(!isLocked && settings.screen.toLowerCase() == "dynamic"){
|
if(!isLocked && settings.screen.toLowerCase() == "dynamic"){
|
||||||
// If we have to show the widgets again, we load it from our
|
// If we have to show the widgets again, we load it from our
|
||||||
// cache and not through Bangle.loadWidgets as its much faster!
|
// cache and not through Bangle.loadWidgets as its much faster!
|
||||||
for (let wd of WIDGETS) {wd.draw=wd._draw;wd.area=wd._area;}
|
widget_utils.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
draw();
|
draw();
|
||||||
});
|
};
|
||||||
|
Bangle.on('lock', lockListenerBw);
|
||||||
Bangle.on('charging',function(charging) {
|
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
|
||||||
drawTimeout = undefined;
|
|
||||||
|
|
||||||
|
let charging = function(charging){
|
||||||
// Jump to battery
|
// Jump to battery
|
||||||
settings.menuPosX = 0;
|
clockInfoMenu.setItem(0, 2);
|
||||||
settings.menuPosY = 1;
|
drawTime();
|
||||||
draw();
|
}
|
||||||
});
|
Bangle.on('charging', charging);
|
||||||
|
|
||||||
Bangle.on('touch', function(btn, e){
|
|
||||||
var widget_size = isFullscreen() ? 0 : 20; // Its not exactly 24px -- empirically it seems that 20 worked better...
|
|
||||||
var left = parseInt(g.getWidth() * 0.22);
|
|
||||||
var right = g.getWidth() - left;
|
|
||||||
var upper = parseInt(g.getHeight() * 0.22) + widget_size;
|
|
||||||
var lower = g.getHeight() - upper;
|
|
||||||
|
|
||||||
var is_upper = e.y < upper;
|
|
||||||
var is_lower = e.y > lower;
|
|
||||||
var is_left = e.x < left && !is_upper && !is_lower;
|
|
||||||
var is_right = e.x > right && !is_upper && !is_lower;
|
|
||||||
var is_center = !is_upper && !is_lower && !is_left && !is_right;
|
|
||||||
|
|
||||||
if(lock_input){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(is_lower){
|
|
||||||
Bangle.buzz(40, 0.6);
|
|
||||||
settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1);
|
|
||||||
|
|
||||||
drawMenuAndTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(is_upper){
|
|
||||||
if(e.y < widget_size){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bangle.buzz(40, 0.6);
|
|
||||||
settings.menuPosY = settings.menuPosY-1;
|
|
||||||
settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].items.length : settings.menuPosY;
|
|
||||||
|
|
||||||
drawMenuAndTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(is_right){
|
|
||||||
Bangle.buzz(40, 0.6);
|
|
||||||
settings.menuPosX = (settings.menuPosX+1) % menu.length;
|
|
||||||
settings.menuPosY = 0;
|
|
||||||
drawMenuAndTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(is_left){
|
|
||||||
Bangle.buzz(40, 0.6);
|
|
||||||
settings.menuPosY = 0;
|
|
||||||
settings.menuPosX = settings.menuPosX-1;
|
|
||||||
settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX;
|
|
||||||
drawMenuAndTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(is_center){
|
|
||||||
if(canRunMenuItem()){
|
|
||||||
runMenuItem();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
E.on("kill", function(){
|
|
||||||
try{
|
|
||||||
storage.write(SETTINGS_FILE, settings);
|
|
||||||
} catch(ex){
|
|
||||||
// If this fails, we still kill the app...
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
let kill = function(){
|
||||||
|
clockInfoMenu.remove();
|
||||||
|
delete clockInfoMenu;
|
||||||
|
};
|
||||||
|
E.on("kill", kill);
|
||||||
|
|
||||||
/************************************************
|
/************************************************
|
||||||
* Startup Clock
|
* Startup Clock
|
||||||
|
|
@ -450,17 +345,31 @@ E.on("kill", function(){
|
||||||
// The upper part is inverse i.e. light if dark and dark if light theme
|
// The upper part is inverse i.e. light if dark and dark if light theme
|
||||||
// is enabled. In order to draw the widgets correctly, we invert the
|
// is enabled. In order to draw the widgets correctly, we invert the
|
||||||
// dark/light theme as well as the colors.
|
// dark/light theme as well as the colors.
|
||||||
|
let themeBackup = g.theme;
|
||||||
g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear();
|
g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear();
|
||||||
|
|
||||||
// Show launcher when middle button pressed
|
// Show launcher when middle button pressed
|
||||||
Bangle.setUI("clock");
|
Bangle.setUI({
|
||||||
|
mode : "clock",
|
||||||
|
remove : function() {
|
||||||
|
// Called to unload all of the clock app
|
||||||
|
Bangle.removeListener('lcdPower', lcdListenerBw);
|
||||||
|
Bangle.removeListener('lock', lockListenerBw);
|
||||||
|
Bangle.removeListener('charging', charging);
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = undefined;
|
||||||
|
// save settings
|
||||||
|
kill();
|
||||||
|
E.removeListener("kill", kill);
|
||||||
|
g.setTheme(themeBackup);
|
||||||
|
widget_utils.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Load widgets and draw clock the first time
|
// Load widgets and draw clock the first time
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
|
|
||||||
// Cache draw function for dynamic screen to hide / show widgets
|
|
||||||
// Bangle.loadWidgets() could also be called later on but its much slower!
|
|
||||||
for (let wd of WIDGETS) {wd._draw=wd.draw; wd._area=wd.area;}
|
|
||||||
|
|
||||||
// Draw first time
|
// Draw first time
|
||||||
draw();
|
draw();
|
||||||
|
|
||||||
|
} // End of app scope
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"id": "bwclk",
|
"id": "bwclk",
|
||||||
"name": "BW Clock",
|
"name": "BW Clock",
|
||||||
"version": "0.23",
|
"version": "0.30",
|
||||||
"description": "A very minimalistic clock to mainly show date and time.",
|
"description": "A very minimalistic clock.",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}],
|
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock,clkinfo",
|
"tags": "clock,clkinfo",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
|
@ -1,3 +1,9 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Support Bangle.js 2
|
0.02: Support Bangle.js 2
|
||||||
0.03: Fix bug for Bangle.js 2 where g.flip was not being called.
|
0.03: Fix bug for Bangle.js 2 where g.flip was not being called.
|
||||||
|
0.04: Combine code for both apps
|
||||||
|
Better colors for Bangle.js 2
|
||||||
|
Fix selection animation for Bangle.js 2
|
||||||
|
New icon
|
||||||
|
Slightly wider arc segments for better visibility
|
||||||
|
Extract arc drawing code in library
|
||||||
|
|
|
||||||
|
|
@ -11,16 +11,21 @@ the players seated in a circle, set the number of segments equal to the number
|
||||||
of players, ensure that each person knows which colour represents them, and then
|
of players, ensure that each person knows which colour represents them, and then
|
||||||
choose a segment. After a short animation, the chosen segment will fill the screen.
|
choose a segment. After a short animation, the chosen segment will fill the screen.
|
||||||
|
|
||||||
You can use Choozi to randomly select an element from any set with 2 to 13 members,
|
You can use Choozi to randomly select an element from any set with 2 to 15 members,
|
||||||
as long as you can define a bijection between members of the set and coloured
|
as long as you can define a bijection between members of the set and coloured
|
||||||
segments on the Bangle.js display.
|
segments on the Bangle.js display.
|
||||||
|
|
||||||
## Controls
|
## Controls Bangle 1
|
||||||
|
|
||||||
BTN1: increase the number of segments
|
BTN1: increase the number of segments
|
||||||
BTN2: choose a segment at random
|
BTN2: choose a segment at random
|
||||||
BTN3: decrease the number of segments
|
BTN3: decrease the number of segments
|
||||||
|
|
||||||
|
## Controls Bangle 2
|
||||||
|
|
||||||
|
Swipe up/down: increase/decrease the number of segments
|
||||||
|
BTN1 or tap: choose a segment at random
|
||||||
|
|
||||||
## Creator
|
## Creator
|
||||||
|
|
||||||
James Stanley
|
James Stanley
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
require("heatshrink").decompress(atob("mEwggLIrnM4uqAAIhPgvMAAPFzIABzWgCxkMCweqC4QABDBYtC5QVFDBoWCCo5KLOQIWKDARFICxhJIFwOpC5owFFyAwGUYIuOGAwuRC4guSJAgXBCyIwDIyQXF5IXSzJeVMAReUAAOQhheTMAVcC6yOUC4aOUC7GZUyoXXzWqhQXVxGqC9mYC7OqC9eoxEKC6uBC6uIwAXBPCSmBwEAC6Z2BiAXBJCR2BgEAjQXSlGBC4JgSLwYABJCJGBLwJIDGB+IIwRIDGByNBIwZIDGBhdBRoQwSLoIuFGAYYKCwIuGGAgYI1QWBRgYYJMYmaFoSMEAAyrBAAgVCCxgYGjAWQAAMBC4UILZQA=="))
|
require("heatshrink").decompress(atob("mEwwcH/4AW/u27dt2wQL/YOBCIXbv4QI+AODAQVsh4RHwEbCI0LCI9gCIOANAXbsFbG437tkDPg1btoRFFoILBgmSpMggECHQO/CAf2CIVJkgRBAQIjC24RFsECCItIgIRFMYMAiQRFpMAlqmDVwPYgAOEAQUggu274RD4BWCCIskCIPbCIPt20ABwwCCwARFgIRJyEWCIVt2EJCJi2BCJmSUgIRCwARNt/7CIIOICI1sWAwCFoFbCOtt8EACJsAgARR8hwBCJlJk4RlgARQAgIRKDwMn/gRBdJgRPyARBn4RBpARLiQRB/4RBgIRJwAREpIRLAYP///ypMgCJMACI0ECI4JCp4RB/wZECIsAAYN/CIP/5JPDCIhjDCIraHTIWTCAX//K7DCI+fCIf/EZA1CCAn//ipCLIsBk4RF/5ZHCIIQG//wPo8vCI//6QRFpYQIAAPpCIeXCBQAC/VfBI4="))
|
||||||
|
|
@ -4,15 +4,16 @@
|
||||||
*
|
*
|
||||||
* James Stanley 2021
|
* James Stanley 2021
|
||||||
*/
|
*/
|
||||||
|
const GU = require("graphics_utils");
|
||||||
var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff'];
|
var colours = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#ffffff'];
|
||||||
|
var colours2 = ['#808080', '#404040', '#000040', '#004000', '#400000', '#ff8000', '#804000', '#4000c0'];
|
||||||
|
|
||||||
var stepAngle = 0.18; // radians - resolution of polygon
|
var stepAngle = 0.18; // radians - resolution of polygon
|
||||||
var gapAngle = 0.035; // radians - gap between segments
|
var gapAngle = 0.035; // radians - gap between segments
|
||||||
var perimMin = 110; // px - min. radius of perimeter
|
var perimMin = g.getWidth()*0.40; // px - min. radius of perimeter
|
||||||
var perimMax = 120; // px - max. radius of perimeter
|
var perimMax = g.getWidth()*0.49; // px - max. radius of perimeter
|
||||||
|
|
||||||
var segmentMax = 106; // px - max radius of filled-in segment
|
var segmentMax = g.getWidth()*0.38; // px - max radius of filled-in segment
|
||||||
var segmentStep = 5; // px - step size of segment fill animation
|
var segmentStep = 5; // px - step size of segment fill animation
|
||||||
var circleStep = 4; // px - step size of circle fill animation
|
var circleStep = 4; // px - step size of circle fill animation
|
||||||
|
|
||||||
|
|
@ -22,10 +23,10 @@ var minSpeed = 0.001; // rad/sec
|
||||||
var animStartSteps = 300; // how many steps before it can start slowing?
|
var animStartSteps = 300; // how many steps before it can start slowing?
|
||||||
var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
|
var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
|
||||||
var ballSize = 3; // px - ball radius
|
var ballSize = 3; // px - ball radius
|
||||||
var ballTrack = 100; // px - radius of ball path
|
var ballTrack = perimMin - ballSize*2; // px - radius of ball path
|
||||||
|
|
||||||
var centreX = 120; // px - centre of screen
|
var centreX = g.getWidth()*0.5; // px - centre of screen
|
||||||
var centreY = 120; // px - centre of screen
|
var centreY = g.getWidth()*0.5; // px - centre of screen
|
||||||
|
|
||||||
var fontSize = 50; // px
|
var fontSize = 50; // px
|
||||||
|
|
||||||
|
|
@ -33,7 +34,6 @@ var radians = 2*Math.PI; // radians per circle
|
||||||
|
|
||||||
var defaultN = 3; // default value for N
|
var defaultN = 3; // default value for N
|
||||||
var minN = 2;
|
var minN = 2;
|
||||||
var maxN = colours.length;
|
|
||||||
var N;
|
var N;
|
||||||
var arclen;
|
var arclen;
|
||||||
|
|
||||||
|
|
@ -51,42 +51,14 @@ function shuffle (array) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw an arc between radii minR and maxR, and between
|
|
||||||
// angles minAngle and maxAngle
|
|
||||||
function arc(minR, maxR, minAngle, maxAngle) {
|
|
||||||
var step = stepAngle;
|
|
||||||
var angle = minAngle;
|
|
||||||
var inside = [];
|
|
||||||
var outside = [];
|
|
||||||
var c, s;
|
|
||||||
while (angle < maxAngle) {
|
|
||||||
c = Math.cos(angle);
|
|
||||||
s = Math.sin(angle);
|
|
||||||
inside.push(centreX+c*minR); // x
|
|
||||||
inside.push(centreY+s*minR); // y
|
|
||||||
// outside coordinates are built up in reverse order
|
|
||||||
outside.unshift(centreY+s*maxR); // y
|
|
||||||
outside.unshift(centreX+c*maxR); // x
|
|
||||||
angle += step;
|
|
||||||
}
|
|
||||||
c = Math.cos(maxAngle);
|
|
||||||
s = Math.sin(maxAngle);
|
|
||||||
inside.push(centreX+c*minR);
|
|
||||||
inside.push(centreY+s*minR);
|
|
||||||
outside.unshift(centreY+s*maxR);
|
|
||||||
outside.unshift(centreX+c*maxR);
|
|
||||||
|
|
||||||
var vertices = inside.concat(outside);
|
|
||||||
g.fillPoly(vertices, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw the arc segments around the perimeter
|
// draw the arc segments around the perimeter
|
||||||
function drawPerimeter() {
|
function drawPerimeter() {
|
||||||
|
g.setBgColor('#000000');
|
||||||
g.clear();
|
g.clear();
|
||||||
for (var i = 0; i < N; i++) {
|
for (var i = 0; i < N; i++) {
|
||||||
g.setColor(colours[i%colours.length]);
|
g.setColor(colours[i%colours.length]);
|
||||||
var minAngle = (i/N)*radians;
|
var minAngle = (i/N)*radians;
|
||||||
arc(perimMin,perimMax,minAngle,minAngle+arclen);
|
GU.fillArc(g, centreX, centreY, perimMin,perimMax,minAngle,minAngle+arclen, stepAngle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,6 +103,7 @@ function animateChoice(target) {
|
||||||
g.fillCircle(x, y, ballSize);
|
g.fillCircle(x, y, ballSize);
|
||||||
oldx=x;
|
oldx=x;
|
||||||
oldy=y;
|
oldy=y;
|
||||||
|
if (process.env.HWVERSION == 2) g.flip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,11 +114,15 @@ function choose() {
|
||||||
var maxAngle = minAngle + arclen;
|
var maxAngle = minAngle + arclen;
|
||||||
animateChoice((minAngle+maxAngle)/2);
|
animateChoice((minAngle+maxAngle)/2);
|
||||||
g.setColor(colours[chosen%colours.length]);
|
g.setColor(colours[chosen%colours.length]);
|
||||||
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep)
|
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep){
|
||||||
arc(i, perimMax, minAngle, maxAngle);
|
GU.fillArc(g, centreX, centreY, i, perimMax, minAngle, maxAngle, stepAngle);
|
||||||
arc(0, perimMax, minAngle, maxAngle);
|
if (process.env.HWVERSION == 2) g.flip();
|
||||||
for (var r = 1; r < segmentMax; r += circleStep)
|
}
|
||||||
|
GU.fillArc(g, centreX, centreY, 0, perimMax, minAngle, maxAngle, stepAngle);
|
||||||
|
for (var r = 1; r < segmentMax; r += circleStep){
|
||||||
g.fillCircle(centreX,centreY,r);
|
g.fillCircle(centreX,centreY,r);
|
||||||
|
if (process.env.HWVERSION == 2) g.flip();
|
||||||
|
}
|
||||||
g.fillCircle(centreX,centreY,segmentMax);
|
g.fillCircle(centreX,centreY,segmentMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,38 +148,47 @@ function setN(n) {
|
||||||
drawPerimeter();
|
drawPerimeter();
|
||||||
}
|
}
|
||||||
|
|
||||||
// save N to choozi.txt
|
// save N to choozi.save
|
||||||
function writeN() {
|
function writeN() {
|
||||||
var file = require("Storage").open("choozi.txt","w");
|
var savedN = read();
|
||||||
file.write(N);
|
if (savedN != N) require("Storage").write("choozi.save","" + N);
|
||||||
}
|
}
|
||||||
|
|
||||||
// load N from choozi.txt
|
function read(){
|
||||||
|
var n = require("Storage").read("choozi.save");
|
||||||
|
if (n !== undefined) return parseInt(n);
|
||||||
|
return defaultN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load N from choozi.save
|
||||||
function readN() {
|
function readN() {
|
||||||
var file = require("Storage").open("choozi.txt","r");
|
setN(read());
|
||||||
var n = file.readLine();
|
|
||||||
if (n !== undefined) setN(parseInt(n));
|
|
||||||
else setN(defaultN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shuffle(colours); // is this really best?
|
if (process.env.HWVERSION == 1){
|
||||||
Bangle.setLCDMode("direct");
|
colours=colours.concat(colours2);
|
||||||
Bangle.setLCDTimeout(0); // keep screen on
|
shuffle(colours);
|
||||||
|
} else {
|
||||||
|
shuffle(colours);
|
||||||
|
shuffle(colours2);
|
||||||
|
colours=colours.concat(colours2);
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxN = colours.length;
|
||||||
|
if (process.env.HWVERSION == 1){
|
||||||
|
Bangle.setLCDMode("direct");
|
||||||
|
Bangle.setLCDTimeout(0); // keep screen on
|
||||||
|
}
|
||||||
readN();
|
readN();
|
||||||
drawN();
|
drawN();
|
||||||
|
|
||||||
setWatch(() => {
|
Bangle.setUI("updown", (v)=>{
|
||||||
setN(N+1);
|
if (!v){
|
||||||
drawN();
|
writeN();
|
||||||
}, BTN1, {repeat:true});
|
drawPerimeter();
|
||||||
|
choose();
|
||||||
setWatch(() => {
|
} else {
|
||||||
writeN();
|
setN(N-v);
|
||||||
drawPerimeter();
|
drawN();
|
||||||
choose();
|
}
|
||||||
}, BTN2, {repeat:true});
|
});
|
||||||
|
|
||||||
setWatch(() => {
|
|
||||||
setN(N-1);
|
|
||||||
drawN();
|
|
||||||
}, BTN3, {repeat:true});
|
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 551 B |
|
|
@ -1,207 +0,0 @@
|
||||||
/* Choozi - Choose people or things at random using Bangle.js.
|
|
||||||
* Inspired by the "Chwazi" Android app
|
|
||||||
*
|
|
||||||
* James Stanley 2021
|
|
||||||
*/
|
|
||||||
|
|
||||||
var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff'];
|
|
||||||
|
|
||||||
var stepAngle = 0.18; // radians - resolution of polygon
|
|
||||||
var gapAngle = 0.035; // radians - gap between segments
|
|
||||||
var perimMin = 80; // px - min. radius of perimeter
|
|
||||||
var perimMax = 87; // px - max. radius of perimeter
|
|
||||||
|
|
||||||
var segmentMax = 70; // px - max radius of filled-in segment
|
|
||||||
var segmentStep = 5; // px - step size of segment fill animation
|
|
||||||
var circleStep = 4; // px - step size of circle fill animation
|
|
||||||
|
|
||||||
// rolling ball animation:
|
|
||||||
var maxSpeed = 0.08; // rad/sec
|
|
||||||
var minSpeed = 0.001; // rad/sec
|
|
||||||
var animStartSteps = 300; // how many steps before it can start slowing?
|
|
||||||
var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
|
|
||||||
var ballSize = 3; // px - ball radius
|
|
||||||
var ballTrack = 75; // px - radius of ball path
|
|
||||||
|
|
||||||
var centreX = 88; // px - centre of screen
|
|
||||||
var centreY = 88; // px - centre of screen
|
|
||||||
|
|
||||||
var fontSize = 50; // px
|
|
||||||
|
|
||||||
var radians = 2*Math.PI; // radians per circle
|
|
||||||
|
|
||||||
var defaultN = 3; // default value for N
|
|
||||||
var minN = 2;
|
|
||||||
var maxN = colours.length;
|
|
||||||
var N;
|
|
||||||
var arclen;
|
|
||||||
|
|
||||||
// https://www.frankmitchell.org/2015/01/fisher-yates/
|
|
||||||
function shuffle (array) {
|
|
||||||
var i = 0
|
|
||||||
, j = 0
|
|
||||||
, temp = null;
|
|
||||||
|
|
||||||
for (i = array.length - 1; i > 0; i -= 1) {
|
|
||||||
j = Math.floor(Math.random() * (i + 1));
|
|
||||||
temp = array[i];
|
|
||||||
array[i] = array[j];
|
|
||||||
array[j] = temp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw an arc between radii minR and maxR, and between
|
|
||||||
// angles minAngle and maxAngle
|
|
||||||
function arc(minR, maxR, minAngle, maxAngle) {
|
|
||||||
var step = stepAngle;
|
|
||||||
var angle = minAngle;
|
|
||||||
var inside = [];
|
|
||||||
var outside = [];
|
|
||||||
var c, s;
|
|
||||||
while (angle < maxAngle) {
|
|
||||||
c = Math.cos(angle);
|
|
||||||
s = Math.sin(angle);
|
|
||||||
inside.push(centreX+c*minR); // x
|
|
||||||
inside.push(centreY+s*minR); // y
|
|
||||||
// outside coordinates are built up in reverse order
|
|
||||||
outside.unshift(centreY+s*maxR); // y
|
|
||||||
outside.unshift(centreX+c*maxR); // x
|
|
||||||
angle += step;
|
|
||||||
}
|
|
||||||
c = Math.cos(maxAngle);
|
|
||||||
s = Math.sin(maxAngle);
|
|
||||||
inside.push(centreX+c*minR);
|
|
||||||
inside.push(centreY+s*minR);
|
|
||||||
outside.unshift(centreY+s*maxR);
|
|
||||||
outside.unshift(centreX+c*maxR);
|
|
||||||
|
|
||||||
var vertices = inside.concat(outside);
|
|
||||||
g.fillPoly(vertices, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw the arc segments around the perimeter
|
|
||||||
function drawPerimeter() {
|
|
||||||
g.clear();
|
|
||||||
for (var i = 0; i < N; i++) {
|
|
||||||
g.setColor(colours[i%colours.length]);
|
|
||||||
var minAngle = (i/N)*radians;
|
|
||||||
arc(perimMin,perimMax,minAngle,minAngle+arclen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// animate a ball rolling around and settling at "target" radians
|
|
||||||
function animateChoice(target) {
|
|
||||||
var angle = 0;
|
|
||||||
var speed = 0;
|
|
||||||
var oldx = -10;
|
|
||||||
var oldy = -10;
|
|
||||||
var decelFromAngle = -1;
|
|
||||||
var allowDecel = false;
|
|
||||||
for (var i = 0; true; i++) {
|
|
||||||
angle = angle + speed;
|
|
||||||
if (angle > radians) angle -= radians;
|
|
||||||
if (i < animStartSteps || (speed < maxSpeed && !allowDecel)) {
|
|
||||||
speed = speed + accel;
|
|
||||||
if (speed > maxSpeed) {
|
|
||||||
speed = maxSpeed;
|
|
||||||
/* when we reach max speed, we know how long it takes
|
|
||||||
* to accelerate, and therefore how long to decelerate, so
|
|
||||||
* we can work out what angle to start decelerating from */
|
|
||||||
if (decelFromAngle < 0) {
|
|
||||||
decelFromAngle = target-angle;
|
|
||||||
while (decelFromAngle < 0) decelFromAngle += radians;
|
|
||||||
while (decelFromAngle > radians) decelFromAngle -= radians;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!allowDecel && (angle < decelFromAngle) && (angle+speed >= decelFromAngle)) allowDecel = true;
|
|
||||||
if (allowDecel) speed = speed - accel;
|
|
||||||
if (speed < minSpeed) speed = minSpeed;
|
|
||||||
if (speed == minSpeed && angle < target && angle+speed >= target) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var r = i/2;
|
|
||||||
if (r > ballTrack) r = ballTrack;
|
|
||||||
var x = centreX+Math.cos(angle)*r;
|
|
||||||
var y = centreY+Math.sin(angle)*r;
|
|
||||||
g.setColor('#000000');
|
|
||||||
g.fillCircle(oldx,oldy,ballSize+1);
|
|
||||||
g.setColor('#ffffff');
|
|
||||||
g.fillCircle(x, y, ballSize);
|
|
||||||
oldx=x;
|
|
||||||
oldy=y;
|
|
||||||
g.flip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// choose a winning segment and animate its selection
|
|
||||||
function choose() {
|
|
||||||
var chosen = Math.floor(Math.random()*N);
|
|
||||||
var minAngle = (chosen/N)*radians;
|
|
||||||
var maxAngle = minAngle + arclen;
|
|
||||||
animateChoice((minAngle+maxAngle)/2);
|
|
||||||
g.setColor(colours[chosen%colours.length]);
|
|
||||||
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep)
|
|
||||||
arc(i, perimMax, minAngle, maxAngle);
|
|
||||||
arc(0, perimMax, minAngle, maxAngle);
|
|
||||||
for (var r = 1; r < segmentMax; r += circleStep)
|
|
||||||
g.fillCircle(centreX,centreY,r);
|
|
||||||
g.fillCircle(centreX,centreY,segmentMax);
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw the current value of N in the middle of the screen, with
|
|
||||||
// up/down arrows
|
|
||||||
function drawN() {
|
|
||||||
g.setColor(g.theme.fg);
|
|
||||||
g.setFont("Vector",fontSize);
|
|
||||||
g.drawString(N,centreX-g.stringWidth(N)/2+4,centreY-fontSize/2);
|
|
||||||
if (N < maxN)
|
|
||||||
g.fillPoly([centreX-6,centreY-fontSize/2-7, centreX+6,centreY-fontSize/2-7, centreX, centreY-fontSize/2-14]);
|
|
||||||
if (N > minN)
|
|
||||||
g.fillPoly([centreX-6,centreY+fontSize/2+5, centreX+6,centreY+fontSize/2+5, centreX, centreY+fontSize/2+12]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update number of segments, with min/max limit, "arclen" update,
|
|
||||||
// and screen reset
|
|
||||||
function setN(n) {
|
|
||||||
N = n;
|
|
||||||
if (N < minN) N = minN;
|
|
||||||
if (N > maxN) N = maxN;
|
|
||||||
arclen = radians/N - gapAngle;
|
|
||||||
drawPerimeter();
|
|
||||||
}
|
|
||||||
|
|
||||||
// save N to choozi.txt
|
|
||||||
function writeN() {
|
|
||||||
var file = require("Storage").open("choozi.txt","w");
|
|
||||||
file.write(N);
|
|
||||||
}
|
|
||||||
|
|
||||||
// load N from choozi.txt
|
|
||||||
function readN() {
|
|
||||||
var file = require("Storage").open("choozi.txt","r");
|
|
||||||
var n = file.readLine();
|
|
||||||
if (n !== undefined) setN(parseInt(n));
|
|
||||||
else setN(defaultN);
|
|
||||||
}
|
|
||||||
|
|
||||||
shuffle(colours); // is this really best?
|
|
||||||
Bangle.setLCDTimeout(0); // keep screen on
|
|
||||||
readN();
|
|
||||||
drawN();
|
|
||||||
|
|
||||||
setWatch(() => {
|
|
||||||
writeN();
|
|
||||||
drawPerimeter();
|
|
||||||
choose();
|
|
||||||
}, BTN1, {repeat:true});
|
|
||||||
|
|
||||||
Bangle.on('touch', function(zone,e) {
|
|
||||||
if(e.x>+88){
|
|
||||||
setN(N-1);
|
|
||||||
drawN();
|
|
||||||
}else{
|
|
||||||
setN(N+1);
|
|
||||||
drawN();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.6 KiB |
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "choozi",
|
"id": "choozi",
|
||||||
"name": "Choozi",
|
"name": "Choozi",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "Choose people or things at random using Bangle.js.",
|
"description": "Choose people or things at random using Bangle.js.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool",
|
"tags": "tool",
|
||||||
|
|
@ -10,8 +10,10 @@
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
"screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}],
|
"screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}],
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"choozi.app.js","url":"app.js","supports": ["BANGLEJS"]},
|
{"name":"choozi.app.js","url":"app.js"},
|
||||||
{"name":"choozi.app.js","url":"appb2.js","supports": ["BANGLEJS2"]},
|
|
||||||
{"name":"choozi.img","url":"app-icon.js","evaluate":true}
|
{"name":"choozi.img","url":"app-icon.js","evaluate":true}
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{"name":"choozi.save"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,3 +39,4 @@
|
||||||
0.21: Remade all icons without a palette for dark theme
|
0.21: Remade all icons without a palette for dark theme
|
||||||
Now re-adds widgets if they were hidden when fast-loading
|
Now re-adds widgets if they were hidden when fast-loading
|
||||||
0.22: Fixed crash if item has no image and cutting long overflowing text
|
0.22: Fixed crash if item has no image and cutting long overflowing text
|
||||||
|
0.23: Setting circles colours per clkinfo and not position
|
||||||
|
|
|
||||||
|
|
@ -20,24 +20,12 @@ let settings = Object.assign(
|
||||||
storage.readJSON("circlesclock.default.json", true) || {},
|
storage.readJSON("circlesclock.default.json", true) || {},
|
||||||
storage.readJSON(SETTINGS_FILE, true) || {}
|
storage.readJSON(SETTINGS_FILE, true) || {}
|
||||||
);
|
);
|
||||||
//TODO deprecate this (and perhaps use in the clkinfo module)
|
|
||||||
// Load step goal from health app and pedometer widget as fallback
|
|
||||||
if (settings.stepGoal == undefined) {
|
|
||||||
let d = storage.readJSON("health.json", true) || {};
|
|
||||||
settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.stepGoal : undefined;
|
|
||||||
|
|
||||||
if (settings.stepGoal == undefined) {
|
|
||||||
d = storage.readJSON("wpedom.json", true) || {};
|
|
||||||
settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let drawTimeout;
|
let drawTimeout;
|
||||||
const showWidgets = settings.showWidgets || false;
|
const showWidgets = settings.showWidgets || false;
|
||||||
const circleCount = settings.circleCount || 3;
|
const circleCount = settings.circleCount || 3;
|
||||||
const showBigWeather = settings.showBigWeather || false;
|
const showBigWeather = settings.showBigWeather || false;
|
||||||
|
|
||||||
let hrtValue; //TODO deprecate this
|
|
||||||
let now = Math.round(new Date().getTime() / 1000);
|
let now = Math.round(new Date().getTime() / 1000);
|
||||||
|
|
||||||
// layout values:
|
// layout values:
|
||||||
|
|
@ -128,8 +116,11 @@ let draw = function() {
|
||||||
queueDraw();
|
queueDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
let getCircleColor = function(index) {
|
let getCircleColor = function(item, clkmenu) {
|
||||||
let color = settings["circle" + index + "color"];
|
let colorKey = clkmenu.name;
|
||||||
|
if(!clkmenu.dynamic) colorKey += "/"+item.name;
|
||||||
|
colorKey += "_color";
|
||||||
|
let color = settings[colorKey];
|
||||||
if (color && color != "") return color;
|
if (color && color != "") return color;
|
||||||
return g.theme.fg;
|
return g.theme.fg;
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +129,7 @@ let getGradientColor = function(color, percent) {
|
||||||
if (isNaN(percent)) percent = 0;
|
if (isNaN(percent)) percent = 0;
|
||||||
if (percent > 1) percent = 1;
|
if (percent > 1) percent = 1;
|
||||||
let colorList = [
|
let colorList = [
|
||||||
'#00FF00', '#80FF00', '#FFFF00', '#FF8000', '#FF0000'
|
'#00ff00', '#80ff00', '#ffff00', '#ff8000', '#ff0000'
|
||||||
];
|
];
|
||||||
if (color == "fg") {
|
if (color == "fg") {
|
||||||
color = colorFg;
|
color = colorFg;
|
||||||
|
|
@ -151,6 +142,17 @@ let getGradientColor = function(color, percent) {
|
||||||
let colorIndex = colorList.length - Math.round(colorList.length * percent);
|
let colorIndex = colorList.length - Math.round(colorList.length * percent);
|
||||||
return colorList[Math.min(colorIndex, colorList.length)] || "#ff0000";
|
return colorList[Math.min(colorIndex, colorList.length)] || "#ff0000";
|
||||||
}
|
}
|
||||||
|
colorList = [
|
||||||
|
'#0000ff', '#8800ff', '#ff00ff', '#ff0088', '#ff0000'
|
||||||
|
];
|
||||||
|
if (color == "blue-red") {
|
||||||
|
let colorIndex = Math.round(colorList.length * percent);
|
||||||
|
return colorList[Math.min(colorIndex, colorList.length) - 1] || "#0000ff";
|
||||||
|
}
|
||||||
|
if (color == "red-blue") {
|
||||||
|
let colorIndex = colorList.length - Math.round(colorList.length * percent);
|
||||||
|
return colorList[Math.min(colorIndex, colorList.length)] || "#ff0000";
|
||||||
|
}
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,10 +174,10 @@ let drawEmpty = function(img, w, color) {
|
||||||
.drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24});
|
.drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24});
|
||||||
}
|
}
|
||||||
|
|
||||||
let drawCircle = function(index, item, data) {
|
let drawCircle = function(index, item, data, clkmenu) {
|
||||||
var w = circlePosX[index-1];
|
var w = circlePosX[index-1];
|
||||||
drawCircleBackground(w);
|
drawCircleBackground(w);
|
||||||
const color = getCircleColor(index);
|
const color = getCircleColor(item, clkmenu);
|
||||||
//drawEmpty(info? info.img : null, w, color);
|
//drawEmpty(info? info.img : null, w, color);
|
||||||
var img = data.img;
|
var img = data.img;
|
||||||
var percent = 1; //fill up if no range
|
var percent = 1; //fill up if no range
|
||||||
|
|
@ -338,7 +340,8 @@ Bangle.setUI({
|
||||||
|
|
||||||
let clockInfoDraw = (itm, info, options) => {
|
let clockInfoDraw = (itm, info, options) => {
|
||||||
//print("Draw",itm.name,options);
|
//print("Draw",itm.name,options);
|
||||||
drawCircle(options.circlePosition, itm, info);
|
let clkmenu = clockInfoItems[options.menuA];
|
||||||
|
drawCircle(options.circlePosition, itm, info, clkmenu);
|
||||||
if (options.focus) g.reset().drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1)
|
if (options.focus) g.reset().drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1)
|
||||||
};
|
};
|
||||||
let clockInfoItems = require("clock_info").load();
|
let clockInfoItems = require("clock_info").load();
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,21 @@
|
||||||
"showWidgets": false,
|
"showWidgets": false,
|
||||||
"weatherCircleData": "humidity",
|
"weatherCircleData": "humidity",
|
||||||
"circleCount": 3,
|
"circleCount": 3,
|
||||||
"circle1color": "green-red",
|
"Bangle/Battery_color":"red-green",
|
||||||
"circle2color": "#0000ff",
|
"Bangle/Steps_color":"#0000ff",
|
||||||
"circle3color": "red-green",
|
"Bangle/HRM_color":"green-red",
|
||||||
"circle4color": "#ffff00",
|
"Bangle/Altitude_color":"#00ff00",
|
||||||
|
"Weather/conditionWithTemperature_color":"#ffff00",
|
||||||
|
"Weather/condition_color":"#00ffff",
|
||||||
|
"Weather/humidity_color":"#00ffff",
|
||||||
|
"Weather/wind_color":"fg",
|
||||||
|
"Weather/temperature_color":"blue-red",
|
||||||
|
"Alarms_color":"#00ff00",
|
||||||
|
"Agenda_color":"#ff0000",
|
||||||
"circle1colorizeIcon": true,
|
"circle1colorizeIcon": true,
|
||||||
"circle2colorizeIcon": true,
|
"circle2colorizeIcon": true,
|
||||||
"circle3colorizeIcon": true,
|
"circle3colorizeIcon": true,
|
||||||
"circle4colorizeIcon": false,
|
"circle4colorizeIcon": false,
|
||||||
"updateInterval": 60,
|
"updateInterval": 60,
|
||||||
"showBigWeather": false,
|
"showBigWeather": false
|
||||||
|
|
||||||
"minHR": 40,
|
|
||||||
"maxHR": 200,
|
|
||||||
"confidence": 0,
|
|
||||||
"stepGoal": 10000,
|
|
||||||
"stepDistanceGoal": 8000,
|
|
||||||
"stepLength": 0.8,
|
|
||||||
"hrmValidity": 60
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "circlesclock",
|
{ "id": "circlesclock",
|
||||||
"name": "Circles clock",
|
"name": "Circles clock",
|
||||||
"shortName":"Circles clock",
|
"shortName":"Circles clock",
|
||||||
"version":"0.22",
|
"version":"0.23",
|
||||||
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
|
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,10 @@
|
||||||
storage.write(SETTINGS_FILE, settings);
|
storage.write(SETTINGS_FILE, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff",
|
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff",
|
||||||
"#00ffff", "#fff", "#000", "green-red", "red-green", "fg"];
|
"#00ffff", "#fff", "#000", "green-red", "red-green", "blue-red", "red-blue", "fg"];
|
||||||
const namesColors = ["default", "red", "green", "blue", "yellow", "magenta",
|
const namesColors = ["default", "red", "green", "blue", "yellow", "magenta",
|
||||||
"cyan", "white", "black", "green->red", "red->green", "foreground"];
|
"cyan", "white", "black", "green->red", "red->green", "blue->red", "red->blue", "foreground"];
|
||||||
|
|
||||||
const weatherData = ["empty", "humidity", "wind"];
|
|
||||||
|
|
||||||
function showMainMenu() {
|
function showMainMenu() {
|
||||||
let menu ={
|
let menu ={
|
||||||
|
|
@ -30,31 +28,11 @@
|
||||||
step: 1,
|
step: 1,
|
||||||
onchange: x => save('circleCount', x),
|
onchange: x => save('circleCount', x),
|
||||||
},
|
},
|
||||||
/*LANG*/'circle 1': ()=>showCircleMenu(1),
|
|
||||||
/*LANG*/'circle 2': ()=>showCircleMenu(2),
|
|
||||||
/*LANG*/'circle 3': ()=>showCircleMenu(3),
|
|
||||||
/*LANG*/'circle 4': ()=>showCircleMenu(4),
|
|
||||||
/*LANG*/'battery warn': {
|
|
||||||
value: settings.batteryWarn,
|
|
||||||
min: 10,
|
|
||||||
max : 100,
|
|
||||||
step: 10,
|
|
||||||
format: x => {
|
|
||||||
return x + '%';
|
|
||||||
},
|
|
||||||
onchange: x => save('batteryWarn', x),
|
|
||||||
},
|
|
||||||
/*LANG*/'show widgets': {
|
/*LANG*/'show widgets': {
|
||||||
value: !!settings.showWidgets,
|
value: !!settings.showWidgets,
|
||||||
format: () => (settings.showWidgets ? 'Yes' : 'No'),
|
format: () => (settings.showWidgets ? 'Yes' : 'No'),
|
||||||
onchange: x => save('showWidgets', x),
|
onchange: x => save('showWidgets', x),
|
||||||
},
|
},
|
||||||
/*LANG*/'weather data': {
|
|
||||||
value: weatherData.indexOf(settings.weatherCircleData),
|
|
||||||
min: 0, max: 2,
|
|
||||||
format: v => weatherData[v],
|
|
||||||
onchange: x => save('weatherCircleData', weatherData[x]),
|
|
||||||
},
|
|
||||||
/*LANG*/'update interval': {
|
/*LANG*/'update interval': {
|
||||||
value: settings.updateInterval,
|
value: settings.updateInterval,
|
||||||
min: 0,
|
min: 0,
|
||||||
|
|
@ -65,41 +43,54 @@
|
||||||
},
|
},
|
||||||
onchange: x => save('updateInterval', x),
|
onchange: x => save('updateInterval', x),
|
||||||
},
|
},
|
||||||
//TODO deprecated local icons, may disappear in future
|
|
||||||
/*LANG*/'legacy weather icons': {
|
|
||||||
value: !!settings.legacyWeatherIcons,
|
|
||||||
format: () => (settings.legacyWeatherIcons ? 'Yes' : 'No'),
|
|
||||||
onchange: x => save('legacyWeatherIcons', x),
|
|
||||||
},
|
|
||||||
/*LANG*/'show big weather': {
|
/*LANG*/'show big weather': {
|
||||||
value: !!settings.showBigWeather,
|
value: !!settings.showBigWeather,
|
||||||
format: () => (settings.showBigWeather ? 'Yes' : 'No'),
|
format: () => (settings.showBigWeather ? 'Yes' : 'No'),
|
||||||
onchange: x => save('showBigWeather', x),
|
onchange: x => save('showBigWeather', x),
|
||||||
}
|
},
|
||||||
|
/*LANG*/'colorize icons': ()=>showCircleMenus()
|
||||||
};
|
};
|
||||||
|
clock_info.load().forEach(e=>{
|
||||||
|
if(e.dynamic) {
|
||||||
|
const colorKey = e.name + "_color";
|
||||||
|
menu[e.name+/*LANG*/' color'] = {
|
||||||
|
value: valuesColors.indexOf(settings[colorKey]) || 0,
|
||||||
|
min: 0, max: valuesColors.length - 1,
|
||||||
|
format: v => namesColors[v],
|
||||||
|
onchange: x => save(colorKey, valuesColors[x]),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
let values = e.items.map(i=>e.name+"/"+i.name);
|
||||||
|
let names = e.name=="Bangle" ? e.items.map(i=>i.name) : values;
|
||||||
|
values.forEach((v,i)=>{
|
||||||
|
const colorKey = v + "_color";
|
||||||
|
menu[names[i]+/*LANG*/' color'] = {
|
||||||
|
value: valuesColors.indexOf(settings[colorKey]) || 0,
|
||||||
|
min: 0, max: valuesColors.length - 1,
|
||||||
|
format: v => namesColors[v],
|
||||||
|
onchange: x => save(colorKey, valuesColors[x]),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showCircleMenu(circleId) {
|
function showCircleMenus() {
|
||||||
const circleName = "circle" + circleId;
|
const menu = {
|
||||||
const colorKey = circleName + "color";
|
'': { 'title': /*LANG*/'Colorize icons'},
|
||||||
const colorizeIconKey = circleName + "colorizeIcon";
|
/*LANG*/'< Back': ()=>showMainMenu(),
|
||||||
|
};
|
||||||
const menu = {
|
for(var circleId=1; circleId<=4; ++circleId) {
|
||||||
'': { 'title': /*LANG*/'Circle ' + circleId },
|
const circleName = "circle" + circleId;
|
||||||
/*LANG*/'< Back': ()=>showMainMenu(),
|
const colorKey = circleName + "color";
|
||||||
/*LANG*/'color': {
|
const colorizeIconKey = circleName + "colorizeIcon";
|
||||||
value: valuesColors.indexOf(settings[colorKey]) || 0,
|
menu[/*LANG*/'circle ' + circleId] = {
|
||||||
min: 0, max: valuesColors.length - 1,
|
|
||||||
format: v => namesColors[v],
|
|
||||||
onchange: x => save(colorKey, valuesColors[x]),
|
|
||||||
},
|
|
||||||
/*LANG*/'colorize icon': {
|
|
||||||
value: settings[colorizeIconKey] || false,
|
value: settings[colorizeIconKey] || false,
|
||||||
format: () => (settings[colorizeIconKey] ? 'Yes' : 'No'),
|
format: () => (settings[colorizeIconKey]? /*LANG*/'Yes': /*LANG*/'No'),
|
||||||
onchange: x => save(colorizeIconKey, x),
|
onchange: x => save(colorizeIconKey, x),
|
||||||
},
|
};
|
||||||
};
|
}
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
0.01: First release
|
0.01: First release
|
||||||
|
0.02: Update clock_info to avoid a redraw and image allocation
|
||||||
|
|
|
||||||
|
|
@ -4,26 +4,13 @@
|
||||||
items: [
|
items: [
|
||||||
{ name : "FW",
|
{ name : "FW",
|
||||||
get : () => {
|
get : () => {
|
||||||
let d = new Date();
|
|
||||||
let g = Graphics.createArrayBuffer(24,24,1,{msb:true});
|
|
||||||
g.drawImage(atob("GBjC////AADve773VWmmmmlVVW22nnlVVbLL445VVwAAAADVWAAAAAAlrAAAAAA6sAAAAAAOWAAAAAAlrAD//wA6sANVVcAOWANVVcAlrANVVcA6rANVVcA6WANVVcAlsANVVcAOrAD//wA6WAAAAAAlsAAAAAAOrAAAAAA6WAAAAAAlVwAAAADVVbLL445VVW22nnlVVWmmmmlV"),1,0);
|
|
||||||
return {
|
return {
|
||||||
text : process.env.VERSION,
|
text : process.env.VERSION,
|
||||||
img : g.asImage("string")
|
img : atob("GBjC////AADve773VWmmmmlVVW22nnlVVbLL445VVwAAAADVWAAAAAAlrAAAAAA6sAAAAAAOWAAAAAAlrAD//wA6sANVVcAOWANVVcAlrANVVcA6rANVVcA6WANVVcAlsANVVcAOrAD//wA6WAAAAAAlsAAAAAAOrAAAAAA6WAAAAAAlVwAAAADVVbLL445VVW22nnlVVWmmmmlV")
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
show : function() {
|
show : function() {},
|
||||||
this.interval = setTimeout(()=>{
|
hide : function() {}
|
||||||
this.emit("redraw");
|
|
||||||
this.interval = setInterval(()=>{
|
|
||||||
this.emit("redraw");
|
|
||||||
}, 86400000);
|
|
||||||
}, 86400000 - (Date.now() % 86400000));
|
|
||||||
},
|
|
||||||
hide : function() {
|
|
||||||
clearInterval(this.interval);
|
|
||||||
this.interval = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "clkinfofw",
|
{ "id": "clkinfofw",
|
||||||
"name": "Firmware Clockinfo",
|
"name": "Firmware Clockinfo",
|
||||||
"version":"0.01",
|
"version":"0.02",
|
||||||
"description": "For clocks that display 'clockinfo', this displays the firmware version string",
|
"description": "For clocks that display 'clockinfo', this displays the firmware version string",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "clkinfo",
|
"type": "clkinfo",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: New clock
|
0.01: New clock
|
||||||
0.02: Use ClockFace library, add settings
|
0.02: Use ClockFace library, add settings
|
||||||
0.03: Use ClockFace_menu.addSettingsFile
|
0.03: Use ClockFace_menu.addSettingsFile
|
||||||
|
0.04: Hide widgets instead of not loading them at all
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "cogclock",
|
"id": "cogclock",
|
||||||
"name": "Cog Clock",
|
"name": "Cog Clock",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "A cross-shaped clock inside a cog",
|
"description": "A cross-shaped clock inside a cog",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
/*LANG*/"< Back": back,
|
/*LANG*/"< Back": back,
|
||||||
};
|
};
|
||||||
require("ClockFace_menu").addSettingsFile(menu, "cogclock.settings.json", [
|
require("ClockFace_menu").addSettingsFile(menu, "cogclock.settings.json", [
|
||||||
"showDate", "loadWidgets"
|
"showDate", "hideWidgets"
|
||||||
]);
|
]);
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,4 @@
|
||||||
0.07: Use default Bangle formatter for booleans
|
0.07: Use default Bangle formatter for booleans
|
||||||
0.08: fix idle timer always getting set to true
|
0.08: fix idle timer always getting set to true
|
||||||
0.09: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
0.09: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
||||||
|
0.10: Use widget_utils.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
var SunCalc = require("suncalc"); // from modules folder
|
var SunCalc = require("suncalc"); // from modules folder
|
||||||
const storage = require('Storage');
|
const storage = require('Storage');
|
||||||
const locale = require("locale");
|
const locale = require("locale");
|
||||||
|
const widget_utils = require('widget_utils');
|
||||||
const SETTINGS_FILE = "daisy.json";
|
const SETTINGS_FILE = "daisy.json";
|
||||||
const LOCATION_FILE = "mylocation.json";
|
const LOCATION_FILE = "mylocation.json";
|
||||||
const h = g.getHeight();
|
const h = g.getHeight();
|
||||||
|
|
@ -547,8 +548,6 @@ g.clear();
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
/*
|
/*
|
||||||
* we are not drawing the widgets as we are taking over the whole screen
|
* we are not drawing the widgets as we are taking over the whole screen
|
||||||
* so we will blank out the draw() functions of each widget and change the
|
|
||||||
* area to the top bar doesn't get cleared.
|
|
||||||
*/
|
*/
|
||||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
widget_utils.hide();
|
||||||
draw();
|
draw();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "daisy",
|
{ "id": "daisy",
|
||||||
"name": "Daisy",
|
"name": "Daisy",
|
||||||
"version":"0.09",
|
"version":"0.10",
|
||||||
"dependencies": {"mylocation":"app"},
|
"dependencies": {"mylocation":"app"},
|
||||||
"description": "A beautiful digital clock with large ring guage, idle timer and a cyclic information line that includes, day, date, steps, battery, sunrise and sunset times",
|
"description": "A beautiful digital clock with large ring guage, idle timer and a cyclic information line that includes, day, date, steps, battery, sunrise and sunset times",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,4 @@
|
||||||
0.04: Now displays the opened text string at launch.
|
0.04: Now displays the opened text string at launch.
|
||||||
0.05: Now scrolls text when string gets longer than screen width.
|
0.05: Now scrolls text when string gets longer than screen width.
|
||||||
0.06: The code is now more reliable and the input snappier. Widgets will be drawn if present.
|
0.06: The code is now more reliable and the input snappier. Widgets will be drawn if present.
|
||||||
|
0.07: Settings for display colors
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,8 @@ Known bugs:
|
||||||
- Initially developed for use with dark theme set on Bangle.js 2 - that is still the preferred way to view it although it now works with other themes.
|
- Initially developed for use with dark theme set on Bangle.js 2 - that is still the preferred way to view it although it now works with other themes.
|
||||||
- When repeatedly doing 'del' on an empty text-string, the letter case is changed back and forth between upper and lower case.
|
- When repeatedly doing 'del' on an empty text-string, the letter case is changed back and forth between upper and lower case.
|
||||||
|
|
||||||
To do:
|
Settings:
|
||||||
- Possibly provide a dragboard.settings.js file
|
- CAPS LOCK: all characters are displayed and typed in uppercase
|
||||||
|
- ABC Color: color of the characters row
|
||||||
|
- Num Color: color of the digits and symbols row
|
||||||
|
- Highlight Color: color of the currently highlighted character
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,14 @@ exports.input = function(options) {
|
||||||
options = options||{};
|
options = options||{};
|
||||||
var text = options.text;
|
var text = options.text;
|
||||||
if ("string"!=typeof text) text="";
|
if ("string"!=typeof text) text="";
|
||||||
|
let settings = require('Storage').readJSON('dragboard.json',1)||{}
|
||||||
|
|
||||||
var R = Bangle.appRect;
|
var R = Bangle.appRect;
|
||||||
|
const paramToColor = (param) => g.toColor(`#${settings[param].toString(16).padStart(3,0)}`);
|
||||||
var BGCOLOR = g.theme.bg;
|
var BGCOLOR = g.theme.bg;
|
||||||
var HLCOLOR = g.theme.fg;
|
var HLCOLOR = settings.Highlight ? paramToColor("Highlight") : g.theme.fg;
|
||||||
var ABCCOLOR = g.toColor(1,0,0);//'#FF0000';
|
var ABCCOLOR = settings.ABC ? paramToColor("ABC") : g.toColor(1,0,0);//'#FF0000';
|
||||||
var NUMCOLOR = g.toColor(0,1,0);//'#00FF00';
|
var NUMCOLOR = settings.Num ? paramToColor("Num") : g.toColor(0,1,0);//'#00FF00';
|
||||||
var BIGFONT = '6x8:3';
|
var BIGFONT = '6x8:3';
|
||||||
var BIGFONTWIDTH = parseInt(BIGFONT.charAt(0)*parseInt(BIGFONT.charAt(-1)));
|
var BIGFONTWIDTH = parseInt(BIGFONT.charAt(0)*parseInt(BIGFONT.charAt(-1)));
|
||||||
var SMALLFONT = '6x8:1';
|
var SMALLFONT = '6x8:1';
|
||||||
|
|
@ -102,6 +104,7 @@ exports.input = function(options) {
|
||||||
//setTimeout(initDraw, 0); // So Bangle.appRect reads the correct environment. It would draw off to the side sometimes otherwise.
|
//setTimeout(initDraw, 0); // So Bangle.appRect reads the correct environment. It would draw off to the side sometimes otherwise.
|
||||||
|
|
||||||
function changeCase(abcHL) {
|
function changeCase(abcHL) {
|
||||||
|
if (settings.uppercase) return;
|
||||||
g.setColor(BGCOLOR);
|
g.setColor(BGCOLOR);
|
||||||
g.setFontAlign(-1, -1, 0);
|
g.setFontAlign(-1, -1, 0);
|
||||||
g.drawString(ABC, ABCPADDING, (R.y+R.h)/2);
|
g.drawString(ABC, ABCPADDING, (R.y+R.h)/2);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "dragboard",
|
{ "id": "dragboard",
|
||||||
"name": "Dragboard",
|
"name": "Dragboard",
|
||||||
"version":"0.06",
|
"version":"0.07",
|
||||||
"description": "A library for text input via swiping keyboard",
|
"description": "A library for text input via swiping keyboard",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type":"textinput",
|
"type":"textinput",
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"textinput","url":"lib.js"}
|
{"name":"textinput","url":"lib.js"},
|
||||||
|
{"name":"dragboard.settings.js","url":"settings.js"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
(function(back) {
|
||||||
|
let settings = require('Storage').readJSON('dragboard.json',1)||{};
|
||||||
|
const colors = {
|
||||||
|
4095: /*LANG*/"White",
|
||||||
|
4080: /*LANG*/"Yellow",
|
||||||
|
3840: /*LANG*/"Red",
|
||||||
|
3855: /*LANG*/"Magenta",
|
||||||
|
255: /*LANG*/"Cyan",
|
||||||
|
240: /*LANG*/"Green",
|
||||||
|
15: /*LANG*/"Blue",
|
||||||
|
0: /*LANG*/"Black",
|
||||||
|
'-1': /*LANG*/"Default"
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = () => require('Storage').write('dragboard.json', settings);
|
||||||
|
function colorMenu(key) {
|
||||||
|
let menu = {'': {title: key}, '< Back': () => E.showMenu(appMenu)};
|
||||||
|
Object.keys(colors).forEach(color => {
|
||||||
|
var label = colors[color];
|
||||||
|
menu[label] = {
|
||||||
|
value: settings[key] == color,
|
||||||
|
onchange: () => {
|
||||||
|
if (color >= 0) {
|
||||||
|
settings[key] = color;
|
||||||
|
} else {
|
||||||
|
delete settings[key];
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
setTimeout(E.showMenu, 10, appMenu);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
const appMenu = {
|
||||||
|
'': {title: 'Dragboard'}, '< Back': back,
|
||||||
|
/*LANG*/'CAPS LOCK': {
|
||||||
|
value: !!settings.uppercase,
|
||||||
|
onchange: v => {settings.uppercase = v; save();}
|
||||||
|
},
|
||||||
|
/*LANG*/'ABC Color': () => E.showMenu(colorMenu("ABC")),
|
||||||
|
/*LANG*/'Num Color': () => E.showMenu(colorMenu("Num")),
|
||||||
|
/*LANG*/'Highlight Color': () => E.showMenu(colorMenu("Highlight"))
|
||||||
|
};
|
||||||
|
|
||||||
|
E.showMenu(appMenu);
|
||||||
|
});
|
||||||
|
|
@ -23,4 +23,8 @@ button to exit is no longer an option.
|
||||||
facilitate 'fast switching' of apps where available.
|
facilitate 'fast switching' of apps where available.
|
||||||
0.20: Bangle 2: Revert use of Bangle.load() to classic load() calls since
|
0.20: Bangle 2: Revert use of Bangle.load() to classic load() calls since
|
||||||
widgets would still be loaded when they weren't supposed to.
|
widgets would still be loaded when they weren't supposed to.
|
||||||
|
0.21: Bangle 2: Call Bangle.drawWidgets() early on so that the widget field
|
||||||
|
immediately follows the correct theme.
|
||||||
|
0.22: Bangle 2: Change to not automatically marking the first app on a page
|
||||||
|
when moving pages. Add caching for faster startups.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,20 +13,25 @@
|
||||||
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
||||||
|
|
||||||
let s = require("Storage");
|
let s = require("Storage");
|
||||||
var apps = s.list(/\.info$/).map(app=>{
|
// Borrowed caching from Icon Launcher, code by halemmerich.
|
||||||
let a=s.readJSON(app,1);
|
let launchCache = s.readJSON("launch.cache.json", true)||{};
|
||||||
return a && {
|
let launchHash = require("Storage").hash(/\.info/);
|
||||||
name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
|
if (launchCache.hash!=launchHash) {
|
||||||
};}).filter(
|
launchCache = {
|
||||||
app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
|
hash : launchHash,
|
||||||
|
apps : s.list(/\.info$/)
|
||||||
apps.sort((a,b)=>{
|
.map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};})
|
||||||
let n=(0|a.sortorder)-(0|b.sortorder);
|
.filter(app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || !app.type))
|
||||||
if (n) return n; // do sortorder first
|
.sort((a,b)=>{
|
||||||
if (a.name<b.name) return -1;
|
var n=(0|a.sortorder)-(0|b.sortorder);
|
||||||
if (a.name>b.name) return 1;
|
if (n) return n; // do sortorder first
|
||||||
return 0;
|
if (a.name<b.name) return -1;
|
||||||
});
|
if (a.name>b.name) return 1;
|
||||||
|
return 0;
|
||||||
|
}) };
|
||||||
|
s.writeJSON("launch.cache.json", launchCache);
|
||||||
|
}
|
||||||
|
let apps = launchCache.apps;
|
||||||
apps.forEach(app=>{
|
apps.forEach(app=>{
|
||||||
if (app.icon)
|
if (app.icon)
|
||||||
app.icon = s.read(app.icon); // should just be a link to a memory area
|
app.icon = s.read(app.icon); // should just be a link to a memory area
|
||||||
|
|
@ -84,12 +89,13 @@
|
||||||
g.flip();
|
g.flip();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Bangle.drawWidgets(); // To immediately update widget field to follow current theme - remove leftovers if previous app set custom theme.
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
drawPage(0);
|
drawPage(0);
|
||||||
|
|
||||||
let swipeListenerDt = function(dirLeftRight, dirUpDown){
|
let swipeListenerDt = function(dirLeftRight, dirUpDown){
|
||||||
updateTimeoutToClock();
|
updateTimeoutToClock();
|
||||||
selected = 0;
|
selected = -1;
|
||||||
oldselected=-1;
|
oldselected=-1;
|
||||||
if(settings.swipeExit && dirLeftRight==1) Bangle.showClock();
|
if(settings.swipeExit && dirLeftRight==1) Bangle.showClock();
|
||||||
if (dirUpDown==-1||dirLeftRight==-1){
|
if (dirUpDown==-1||dirLeftRight==-1){
|
||||||
|
|
@ -153,3 +159,4 @@
|
||||||
updateTimeoutToClock();
|
updateTimeoutToClock();
|
||||||
|
|
||||||
} // end of app scope
|
} // end of app scope
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "dtlaunch",
|
"id": "dtlaunch",
|
||||||
"name": "Desktop Launcher",
|
"name": "Desktop Launcher",
|
||||||
"version": "0.20",
|
"version": "0.22",
|
||||||
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
|
"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"}],
|
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
0.1: New App!
|
0.1: New App!
|
||||||
|
0.2: Now with timer function
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,16 @@ Things I changed:
|
||||||
|
|
||||||
- The main font for the time is now Audiowide
|
- The main font for the time is now Audiowide
|
||||||
- Removed the written out day name and replaced it with steps and bpm
|
- Removed the written out day name and replaced it with steps and bpm
|
||||||
- Changed the date string to a (for me) more readable string
|
- Changed the date string to a (for me) more readable string
|
||||||
|
|
||||||
|
Timer function:
|
||||||
|
- Touch the right side, to start the timer
|
||||||
|
- Initial timer timeout is 300s/5min
|
||||||
|
- Right touch again, add 300s/5min to timeout
|
||||||
|
- Left touch, decrease timeout by 60s/1min
|
||||||
|
- So it is easy, to add timeouts like 7min/3min or 12min
|
||||||
|
- Special thanks to the maintainer of the a_clock_timer app from which I borrowed the code.
|
||||||
|
|
||||||
|
Todo:
|
||||||
|
- Make displayed information configurable, after https://github.com/espruino/BangleApps/issues/2226
|
||||||
|
- Clean up code
|
||||||
|
|
|
||||||
|
|
@ -1,67 +1,127 @@
|
||||||
|
// Fonts
|
||||||
Graphics.prototype.setFontAudiowide = function() {
|
Graphics.prototype.setFontAudiowide = function() {
|
||||||
// Actual height 33 (36 - 4)
|
|
||||||
var widths = atob("CiAsESQjJSQkHyQkDA==");
|
var widths = atob("CiAsESQjJSQkHyQkDA==");
|
||||||
var font = atob("AAAAAAAAAAAAAAAAAAAAAPAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAADgAAAAAAHgAAAAAAfgAAAAAA/gAAAAAD/gAAAAAH/gAAAAAf/AAAAAB/8AAAAAD/4AAAAAP/gAAAAAf/AAAAAB/8AAAAAD/4AAAAAP/gAAAAAf+AAAAAB/8AAAAAH/wAAAAAP/gAAAAA/+AAAAAB/8AAAAAD/wAAAAAD/gAAAAAD+AAAAAAD4AAAAAADwAAAAAADAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAA//+AAAAB///AAAAH///wAAAP///4AAAf///8AAA////+AAA/4AP+AAB/gAD/AAB/AA9/AAD+AB+/gAD+AD+/gAD+AD+/gAD8AH+fgAD8AP8fgAD8AP4fgAD8Af4fgAD8A/wfgAD8A/gfgAD8B/gfgAD8D/AfgAD8D+AfgAD8H+AfgAD8P8AfgAD8P4AfgAD8f4AfgAD8/wAfgAD8/gAfgAD+/gA/gAD+/AA/gAB/eAB/AAB/sAD/AAB/wAH/AAA////+AAAf///8AAAP///4AAAH///wAAAD///gAAAA//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//gAAAAH//gAAAAP//gAD8Af//gAD8A///gAD8B///gAD8B///gAD8B/AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+D+AfgAD//+AfgAD//+AfgAB//8AfgAA//4AfgAAf/wAfgAAP/gAfgAAB8AAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+B+A/gAD/////gAB/////AAB/////AAA////+AAAf///8AAAP///4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//4AAAAD//8AAAAD//+AAAAD//+AAAAD//+AAAAD//+AAAAD//+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//AAfgAD//wAfgAD//4AfgAD//8AfgAD//8AfgAD//+AfgAD8D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B/A/gAD8B///gAD8B///gAD8A///AAD8A///AAAAAf/+AAAAAP/4AAAAAD/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///AAAAH///wAAAf///8AAAf///8AAA////+AAB/////AAB/h+H/AAD/B+B/gAD+B+A/gAD+B+A/gAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B/A/gAD8B///gAD8B///gAD8A///AAAAAf//AAAAAf/+AAAAAH/4AAAAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAgAD8AAABgAD8AAAHgAD8AAAfgAD8AAA/gAD8AAD/gAD8AAP/gAD8AA//gAD8AB//AAD8AH/8AAD8Af/wAAD8A//AAAD8D/+AAAD8P/4AAAD8f/gAAAD9//AAAAD//8AAAAD//wAAAAD//gAAAAD/+AAAAAD/4AAAAAD/wAAAAAD/AAAAAAD8AAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAH/4AAAAAP/8AAAH+f/+AAAf////AAA/////gAB/////gAB///A/gAD//+AfgAD//+AfgAD+D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+D+AfgAD//+AfgAD//+AfgAB///A/gAB/////gAA/////AAAP////AAAD+f/+AAAAAP/8AAAAAH/4AAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/AAAAAAf/wAAAAA//4AAAAB//8AAAAB//8AfgAD//+AfgAD/D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+B+A/gAD+B+A/gAD/B+B/gAB/////AAB/////AAA////+AAAf///8AAAP///4AAAH///wAAAB///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAPAAAA/AAfgAAA/AAfgAAA/AAfgAAA/AAfgAAAeAAPAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
var font = atob("AAAAAAAAAAAAAAAAAAAAAPAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAADgAAAAAAHgAAAAAAfgAAAAAA/gAAAAAD/gAAAAAH/gAAAAAf/AAAAAB/8AAAAAD/4AAAAAP/gAAAAAf/AAAAAB/8AAAAAD/4AAAAAP/gAAAAAf+AAAAAB/8AAAAAH/wAAAAAP/gAAAAA/+AAAAAB/8AAAAAD/wAAAAAD/gAAAAAD+AAAAAAD4AAAAAADwAAAAAADAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAA//+AAAAB///AAAAH///wAAAP///4AAAf///8AAA////+AAA/4AP+AAB/gAD/AAB/AA9/AAD+AB+/gAD+AD+/gAD+AD+/gAD8AH+fgAD8AP8fgAD8AP4fgAD8Af4fgAD8A/wfgAD8A/gfgAD8B/gfgAD8D/AfgAD8D+AfgAD8H+AfgAD8P8AfgAD8P4AfgAD8f4AfgAD8/wAfgAD8/gAfgAD+/gA/gAD+/AA/gAB/eAB/AAB/sAD/AAB/wAH/AAA////+AAAf///8AAAP///4AAAH///wAAAD///gAAAA//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//gAAAAH//gAAAAP//gAD8Af//gAD8A///gAD8B///gAD8B///gAD8B/AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+D+AfgAD//+AfgAD//+AfgAB//8AfgAA//4AfgAAf/wAfgAAP/gAfgAAB8AAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+B+A/gAD/////gAB/////AAB/////AAA////+AAAf///8AAAP///4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//4AAAAD//8AAAAD//+AAAAD//+AAAAD//+AAAAD//+AAAAD//+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//AAfgAD//wAfgAD//4AfgAD//8AfgAD//8AfgAD//+AfgAD8D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B/A/gAD8B///gAD8B///gAD8A///AAD8A///AAAAAf/+AAAAAP/4AAAAAD/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///AAAAH///wAAAf///8AAAf///8AAA////+AAB/////AAB/h+H/AAD/B+B/gAD+B+A/gAD+B+A/gAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B/A/gAD8B///gAD8B///gAD8A///AAAAAf//AAAAAf/+AAAAAH/4AAAAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAgAD8AAABgAD8AAAHgAD8AAAfgAD8AAA/gAD8AAD/gAD8AAP/gAD8AA//gAD8AB//AAD8AH/8AAD8Af/wAAD8A//AAAD8D/+AAAD8P/4AAAD8f/gAAAD9//AAAAD//8AAAAD//wAAAAD//gAAAAD/+AAAAAD/4AAAAAD/wAAAAAD/AAAAAAD8AAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAH/4AAAAAP/8AAAH+f/+AAAf////AAA/////gAB/////gAB///A/gAD//+AfgAD//+AfgAD+D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+D+AfgAD//+AfgAD//+AfgAB///A/gAB/////gAA/////AAAP////AAAD+f/+AAAAAP/8AAAAAH/4AAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/AAAAAAf/wAAAAA//4AAAAB//8AAAAB//8AfgAD//+AfgAD/D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+B+A/gAD+B+A/gAD/B+B/gAB/////AAB/////AAA////+AAAf///8AAAP///4AAAH///wAAAB///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAPAAAA/AAfgAAA/AAfgAAA/AAfgAAA/AAfgAAAeAAPAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||||
var scale = 1; // size multiplier for this font
|
var scale = 1; // size multiplier for this font
|
||||||
g.setFontCustom(font, 46, widths, 48+(scale<<8)+(1<<16));
|
g.setFontCustom(font, 46, widths, 48+(scale<<8)+(1<<16));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Globals variables
|
||||||
|
var timervalue = 0;
|
||||||
|
var istimeron = false;
|
||||||
|
var timertick;
|
||||||
|
|
||||||
|
// Functions
|
||||||
function getSteps() {
|
function getSteps() {
|
||||||
var steps = 0;
|
var steps = 0;
|
||||||
try{
|
try{
|
||||||
if (WIDGETS.wpedom !== undefined) {
|
if (WIDGETS.wpedom !== undefined) {
|
||||||
steps = WIDGETS.wpedom.getSteps();
|
steps = WIDGETS.wpedom.getSteps();
|
||||||
} else if (WIDGETS.activepedom !== undefined) {
|
} else if (WIDGETS.activepedom !== undefined) {
|
||||||
steps = WIDGETS.activepedom.getSteps();
|
steps = WIDGETS.activepedom.getSteps();
|
||||||
} else {
|
} else {
|
||||||
steps = Bangle.getHealthStatus("day").steps;
|
steps = Bangle.getHealthStatus("day").steps;
|
||||||
}
|
}
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
// In case we failed, we can only show 0 steps.
|
// In case we failed, we can only show 0 steps.
|
||||||
return "?";
|
return "?";
|
||||||
}
|
}
|
||||||
|
|
||||||
return Math.round(steps);
|
return Math.round(steps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function timeToString(duration) {
|
||||||
|
var hrs = ~~(duration / 3600);
|
||||||
|
var mins = ~~((duration % 3600) / 60);
|
||||||
|
var secs = ~~duration % 60;
|
||||||
|
var ret = "";
|
||||||
|
if (hrs > 0) {
|
||||||
|
ret += "" + hrs + ":" + (mins < 10 ? "0" : "");
|
||||||
|
}
|
||||||
|
ret += "" + mins + ":" + (secs < 10 ? "0" : "");
|
||||||
|
ret += "" + secs;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function countDown() {
|
||||||
|
timervalue--;
|
||||||
|
|
||||||
|
g.reset().clearRect(0, 40, 44+99, g.getHeight()/2-25);
|
||||||
|
|
||||||
|
g.setFontAlign(0, -1, 0);
|
||||||
|
g.setFont("6x8", 2).drawString(timeToString(timervalue), 95, g.getHeight()/2-50);
|
||||||
|
|
||||||
|
if (timervalue <= 0) {
|
||||||
|
istimeron = false;
|
||||||
|
clearInterval(timertick);
|
||||||
|
|
||||||
|
Bangle.buzz().then(()=>{
|
||||||
|
return new Promise(resolve=>setTimeout(resolve, 500));
|
||||||
|
}).then(()=>{
|
||||||
|
return Bangle.buzz(1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if ((timervalue <= 30) && (timervalue % 10 == 0)) { Bangle.buzz(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touch
|
||||||
|
Bangle.on('touch',t => {
|
||||||
|
if (t == 1) {
|
||||||
|
// Touch on the left, reduce timervalue about 60s
|
||||||
|
Bangle.buzz(30);
|
||||||
|
if (timervalue < 60) { timervalue = 1 ; }
|
||||||
|
else { timervalue -= 60; }
|
||||||
|
}
|
||||||
|
// Touch on the right, raise timervaule about 300s
|
||||||
|
else if (t == 2) {
|
||||||
|
Bangle.buzz(30);
|
||||||
|
if (!istimeron) {
|
||||||
|
istimeron = true;
|
||||||
|
timertick = setInterval(countDown, 1000);
|
||||||
|
}
|
||||||
|
timervalue += 60*5;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||||
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
|
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
|
||||||
let drawTimeout;
|
let drawTimeout;
|
||||||
|
|
||||||
|
|
||||||
// Actually draw the watch face
|
// Actually draw the watch face
|
||||||
let draw = function() {
|
let draw = function() {
|
||||||
var x = g.getWidth() / 2;
|
var x = g.getWidth() / 2;
|
||||||
var y = g.getHeight() / 2;
|
var y = g.getHeight() / 2;
|
||||||
g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets)
|
g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets)
|
||||||
var date = new Date();
|
var date = new Date();
|
||||||
var timeStr = require("locale").time(date, 1); // Hour and minute
|
var timeStr = require("locale").time(date, 1); // Hour and minute
|
||||||
g.setFontAlign(0, 0).setFont("Audiowide").drawString(timeStr, x, y);
|
g.setFontAlign(0, 0).setFont("Audiowide").drawString(timeStr, x, y);
|
||||||
var dateStr = require("locale").date(date, 1).toUpperCase();
|
var dateStr = require("locale").date(date, 1).toUpperCase();
|
||||||
g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y+28);
|
g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y+28);
|
||||||
g.setFontAlign(0, 0).setFont("6x8", 2);
|
g.setFontAlign(0, 0).setFont("6x8", 2);
|
||||||
g.drawString(getSteps(), 50, y+70);
|
g.drawString(getSteps(), 50, y+70);
|
||||||
g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), g.getWidth() -37, y + 70);
|
g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), g.getWidth() -37, y + 70);
|
||||||
|
|
||||||
// queue next draw
|
// queue next draw
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
|
||||||
drawTimeout = setTimeout(function() {
|
|
||||||
drawTimeout = undefined;
|
|
||||||
draw();
|
|
||||||
}, 60000 - (Date.now() % 60000));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Show launcher when middle button pressed
|
|
||||||
Bangle.setUI({
|
|
||||||
mode : "clock",
|
|
||||||
remove : function() {
|
|
||||||
// Called to unload all of the clock app
|
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = undefined;
|
drawTimeout = setTimeout(function() {
|
||||||
delete Graphics.prototype.setFontAnton;
|
drawTimeout = undefined;
|
||||||
}});
|
draw();
|
||||||
// Load widgets
|
}, 60000 - (Date.now() % 60000));
|
||||||
Bangle.loadWidgets();
|
};
|
||||||
draw();
|
|
||||||
setTimeout(Bangle.drawWidgets,0);
|
// Show launcher when middle button pressed
|
||||||
}
|
Bangle.setUI({
|
||||||
|
mode : "clock",
|
||||||
|
remove : function() {
|
||||||
|
// Called to unload all of the clock app
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = undefined;
|
||||||
|
delete Graphics.prototype.setFontAnton;
|
||||||
|
}});
|
||||||
|
// Load widgets
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
draw();
|
||||||
|
setTimeout(Bangle.drawWidgets,0);
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"id": "entonclk",
|
"id": "entonclk",
|
||||||
"name": "Enton Clock",
|
"name": "Enton Clock",
|
||||||
"version": "0.1",
|
"version": "0.2",
|
||||||
"description": "A simple clock using the Audiowide font. ",
|
"description": "A simple clock using the Audiowide font with timer. ",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: App created!
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Gemini clock
|
||||||
|
|
||||||
|
A simple clock face using the Buro Destruct Geminis font, inspired by their Pebble Watch designs: https://burodestruct.net/work/pebble-watchfaces
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
It is designed for maximum legibility and utility whilst still showing widgets.
|
||||||
|
|
||||||
|
If editing or remixing this code, please retain leading zeroes on the hours, they are an integral part of the design.
|
||||||
|
|
||||||
|
The minutes are not right-aligned deliberately so that the numbers don't jump around too much when they change.
|
||||||
|
|
||||||
|
|
||||||
|
## Creator
|
||||||
|
Created by Giles Booth:
|
||||||
|
- http://www.suppertime.co.uk/blogmywiki/
|
||||||
|
- https://mastodon.social/@blogmywiki
|
||||||
|
- https://github.com/blogmywiki
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEw4f/AoP//+iiE00u++/nnMooWSyhT/AA8C9u27dtAQNkgEKAwdQCIUD5MkyVJAQNOwG9DIf+oARBgIPDAQOZoHSA4cj0ARIyeA6AIDpnAI4X2FgXcwgRBp0SDQYRCgErKAVt+ARBi4HC3AREAAnMCIIFCgN4CJOuiYRDgFmCKFXhIR/CMSPFCI0reYazCCJEDuzXGUIvoCIXCfY0Brdt284pOfqjLBBwQCCzNArf///9DoMx3gaBEAYTBnmA5wZCiQDB4+QCIeY+3bFgPQFg3QCIeXKwdMCJfOCIcbI4gRLhIhCvARMR4oRPWYIR/CNNnCI+Z9u20AREtCPHzMbtv8wEXv7GB3ARHAQXJoGuA4VCCIjdCCIWTwHQfY8DnIHDAQNACI+AgNvHwIACI4Nd23btoCC3x3BEQsggEKCAm26iIGAH4ATA=="))
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
|
|
@ -0,0 +1,17 @@
|
||||||
|
{ "id": "geminiclock",
|
||||||
|
"name": "Gemini clock",
|
||||||
|
"shortName":"Gemini Clock",
|
||||||
|
"icon": "app.png",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "Watch face using retro Gemini font",
|
||||||
|
"type": "clock",
|
||||||
|
"tags": "clock",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
"readme": "README.md",
|
||||||
|
"allow_emulator": true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"geminiclock.app.js","url":"gemini-watch-app.js"},
|
||||||
|
{"name":"geminiclock.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
|
@ -63,3 +63,13 @@
|
||||||
* Record traveled distance to get a good average speed.
|
* Record traveled distance to get a good average speed.
|
||||||
* Breaks (low speed) will not count in average speed.
|
* Breaks (low speed) will not count in average speed.
|
||||||
* Bugfix in average speed.
|
* Bugfix in average speed.
|
||||||
|
|
||||||
|
0.16:
|
||||||
|
* When lost indicates nearest point on path.
|
||||||
|
* Rescale display if lost and too far.
|
||||||
|
* New setting to hide points and increase display speed.
|
||||||
|
* Speed optimisations.
|
||||||
|
* Estimated time of Arrival/Going back.
|
||||||
|
* Display current and next segment in red so that you know where to go.
|
||||||
|
* Avoid angles flickering at low speed at the cost of less refresh.
|
||||||
|
* Splash screen while waiting for gps signal.
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,10 @@
|
||||||
|
|
||||||
Gipy allows you to follow gpx traces on your watch.
|
Gipy allows you to follow gpx traces on your watch.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
It is for now meant for bicycling and not hiking
|
It is mainly meant for bicycling but hiking might be fine.
|
||||||
(it uses your movement to figure out your orientation
|
|
||||||
and walking is too slow).
|
|
||||||
|
|
||||||
It is untested on Banglejs1. If you can try it, you would be welcome.
|
It is untested on Banglejs1. If you can try it, you would be welcome.
|
||||||
|
|
||||||
|
|
@ -20,10 +18,10 @@ It provides the following features :
|
||||||
- display the path with current position from gps
|
- display the path with current position from gps
|
||||||
- detects and buzzes if you leave the path
|
- detects and buzzes if you leave the path
|
||||||
- buzzes before sharp turns
|
- buzzes before sharp turns
|
||||||
- buzzes before nodes with comments
|
- buzzes before waypoints
|
||||||
(for example when you need to turn in https://mapstogpx.com/)
|
(for example when you need to turn in https://mapstogpx.com/)
|
||||||
- display instant / average speed
|
- display instant / average speed
|
||||||
- display distance to next node
|
- display distance to next point
|
||||||
- display additional data from openstreetmap :
|
- display additional data from openstreetmap :
|
||||||
- water points
|
- water points
|
||||||
- toilets
|
- toilets
|
||||||
|
|
@ -54,32 +52,47 @@ Your path will be displayed in svg.
|
||||||
|
|
||||||
### Starting Gipy
|
### Starting Gipy
|
||||||
|
|
||||||
Once you start gipy you will have a menu for selecting your trace (if more than one).
|
At start you will have a menu for selecting your trace (if more than one).
|
||||||
Choose the one you want and here you go :
|
Choose the one you want and you will reach the splash screen where you'll wait for the gps signal.
|
||||||
|
Once you have a signal you will reach the main screen:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
On your screen you can see :
|
On your screen you can see:
|
||||||
|
|
||||||
- yourself (the big black dot)
|
- yourself (the big black dot)
|
||||||
- the path (the top of the screen is in front of you)
|
- the path (the top of the screen is in front of you)
|
||||||
|
- on the path, current and next segments are red and other ones are black
|
||||||
- if needed a projection of yourself on the path (small black dot)
|
- if needed a projection of yourself on the path (small black dot)
|
||||||
- extremities of segments as white dots
|
- points as white dots
|
||||||
- turning points as doubled white dots
|
- waypoints as doubled white dots
|
||||||
- some text on the left (from top to bottom) :
|
- some text on the left (from top to bottom):
|
||||||
|
* time to reach start point at current average speed
|
||||||
* current time
|
* current time
|
||||||
|
* time to reach end point at current average speed
|
||||||
* left distance till end of current segment
|
* left distance till end of current segment
|
||||||
* distance from start of path / path length
|
* remaining distance / path length
|
||||||
* average speed / instant speed
|
* average speed / instant speed
|
||||||
- interest points from openstreetmap as color dots :
|
- interest points from openstreetmap as color dots :
|
||||||
* red : bakery
|
* red: bakery
|
||||||
* deep blue : water point
|
* deep blue: water point
|
||||||
* cyan : toilets (often doubles as water point)
|
* cyan: toilets (often doubles as water point)
|
||||||
* green : artwork
|
* green: artwork
|
||||||
- a *turn* indicator on the top right when you reach a turning point
|
- a *turn* indicator on the top right when you reach a turning point
|
||||||
- a *gps* indicator (blinking) on the top right if you lose gps signal
|
- a *gps* indicator (blinking) on the top right if you lose gps signal
|
||||||
- a *lost* indicator on the top right if you stray too far away from path
|
- a *lost* indicator on the top right if you stray too far away from path
|
||||||
- a black segment extending from you when you are lost, indicating the rough direction of where to go
|
|
||||||
|
### Lost
|
||||||
|
|
||||||
|
If you stray away from path we will rescale the display to continue displaying nearby segments and
|
||||||
|
display the direction to follow as a black segment.
|
||||||
|
|
||||||
|
Note that while lost, the app will slow down a lot since it will start scanning all possible points to figure out where you
|
||||||
|
are. On path it just needed to scan a few points ahead and behind.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The distance to next point displayed corresponds to the length of the black segment.
|
||||||
|
|
||||||
### Settings
|
### Settings
|
||||||
|
|
||||||
|
|
@ -87,6 +100,7 @@ Few settings for now (feel free to suggest me more) :
|
||||||
|
|
||||||
- keep gps alive : if turned off, will try to save battery by turning the gps off on long segments
|
- keep gps alive : if turned off, will try to save battery by turning the gps off on long segments
|
||||||
- max speed : used to compute how long to turn the gps off
|
- max speed : used to compute how long to turn the gps off
|
||||||
|
- display points : display/hide points (not waypoints)
|
||||||
|
|
||||||
### Caveats
|
### Caveats
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
|
|
||||||
* bugs
|
+ use Bangle.project(latlong)
|
||||||
|
|
||||||
- when exactly on turn, distance to next point is still often 50m
|
|
||||||
-----> it does not buzz very often on turns
|
|
||||||
|
|
||||||
- when going backwards we have a tendencing to get a wrong current_segment
|
|
||||||
|
|
||||||
* additional features
|
* additional features
|
||||||
|
|
||||||
|
|
@ -15,7 +10,6 @@
|
||||||
(and look at more than next point)
|
(and look at more than next point)
|
||||||
|
|
||||||
- display distance to next water/toilet ?
|
- display distance to next water/toilet ?
|
||||||
- dynamic map rescale
|
|
||||||
- display scale (100m)
|
- display scale (100m)
|
||||||
|
|
||||||
- compress path ?
|
- compress path ?
|
||||||
|
|
|
||||||