Merge branch 'espruino:master' into master

master
xxDUxx 2023-01-15 01:19:11 +01:00 committed by GitHub
commit ca05f34fd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
370 changed files with 7130 additions and 2172 deletions

View File

@ -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

View File

@ -58,3 +58,7 @@ body:
validations: validations:
required: true required: true
- type: textarea
id: apps
attributes:
label: Installed apps

View File

@ -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.

View File

@ -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.

View File

@ -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();
} }

View File

@ -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",

View File

@ -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)

View File

@ -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)
![](screenshot.png) ![](screenshot-1.png) ![](screenshot.png)
## Creator ## Creator
[@alainsaas](https://github.com/alainsaas) [@alainsaas](https://github.com/alainsaas)

View File

@ -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;
} }
else if (t == 2) { if (timervalue < changevalue) { timervalue = 1 ; }
else { timervalue -= changevalue; }
}
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);
} }
if(touchdata.y > 88) {
timervalue += 60*10; 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() {
@ -103,10 +114,24 @@ 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();
@ -114,8 +139,8 @@ function draw() {
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();
} }

View File

@ -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>

View File

@ -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"}]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -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)

View File

@ -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();

View File

@ -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",

View File

@ -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

View File

@ -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 () {}
}); });
}); });

View File

@ -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 = [];

View File

@ -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"}],

View File

@ -43,6 +43,13 @@
updateSettings(); updateSettings();
} }
}, },
/*LANG*/"Use 'Today',..." : {
value : !!settings.useToday,
onchange: v => {
settings.useToday = v;
updateSettings();
}
},
}; };
E.showMenu(mainmenu); E.showMenu(mainmenu);
}) })

View File

@ -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

View File

@ -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;
if (!restart) {
display("Start?", "touch to start");
}
else {
display("Retry?", "touch to retry"); 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() {
@ -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);

View File

@ -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,8 +40,7 @@ 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);

View File

@ -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,

View File

@ -3,3 +3,5 @@
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

View File

@ -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.
![](impl.png) ![](impl.png)

View File

@ -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(){
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 draw(){ function draw(){
// Note that we force a redraw also of the clock info as
// we want to ensure (for design purpose) that the hands
// are above the clkinfo section.
clockInfoMenu.redraw();
}
function drawMainClock(){
// 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

View File

@ -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.",

View File

@ -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

View File

@ -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` &rarr; Configure a new alarm - `New Alarm` &rarr; Configure a new alarm (triggered based on time and day of week)
- `Repeat` &rarr; Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely) - `Repeat` &rarr; Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely)
- `New Timer` &rarr; Configure a new timer - `New Timer` &rarr; Configure a new timer (triggered based on amount of time elapsed in hours/minutes/seconds)
- `New Event` &rarr; Configure a new event (triggered based on time and date)
- `Advanced` - `Advanced`
- `Scheduler settings` &rarr; Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details - `Scheduler settings` &rarr; Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details
- `Enable All` &rarr; Enable _all_ disabled alarms & timers - `Enable All` &rarr; Enable _all_ disabled alarms & timers

View File

@ -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,16 +200,19 @@ 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(!temp) {
if (alarmIndex === undefined) { if (alarmIndex === undefined) {
alarms.push(alarm); alarms.push(alarm);
} else { } else {
alarms[alarmIndex] = alarm; alarms[alarmIndex] = alarm;
} }
} }
}
function saveAndReload() { function saveAndReload() {
// Before saving revert the dow to the standard format (alarms only!) // Before saving revert the dow to the standard format (alarms only!)
@ -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,17 +388,19 @@ 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 (!temp) {
if (timerIndex === undefined) { if (timerIndex === undefined) {
alarms.push(timer); alarms.push(timer);
} else { } else {
alarms[timerIndex] = timer; alarms[timerIndex] = timer;
} }
} }
}
function showAdvancedMenu() { function showAdvancedMenu() {
E.showMenu({ E.showMenu({

View File

@ -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",

View File

@ -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

View File

@ -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 });

View File

@ -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",

View File

@ -36,7 +36,7 @@
updateSettings(); updateSettings();
} }
}, },
/*LANG*/"Messages" : ()=>require("message").openGUI(), /*LANG*/"Messages" : ()=>require("messages").openGUI(),
}; };
E.showMenu(mainmenu); E.showMenu(mainmenu);
}) })

View File

@ -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

View File

@ -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;
} }

View File

@ -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",

View File

@ -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

View File

@ -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"}],

View File

@ -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) {

View File

@ -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

View File

@ -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 (on) {
draw(); // draw immediately, queue redraw
} else { // stop draw timer
if (drawTimeout) clearTimeout(drawTimeout); if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined; drawTimeout = undefined;
} }
});
let onLcdPower = on => {
if (on) {
draw(); // draw immediately, queue redraw
} else { // stop draw timer
clear();
}
}
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();
}

View File

@ -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",

View File

@ -21,3 +21,12 @@
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.

View File

@ -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. ![](screenshot_3.png)
- 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>

View File

@ -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",
// Load menu get : function() { return { text : "Week " + weekOfYear(),
var menu = clock_info.load(); img : null}},
menu = menu.concat(bwItems); 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 ){ // Empty for large time
settings.menuPosX = 0; clockInfoItems[0].items.unshift({ name : "nop",
settings.menuPosY = 0; get : function() { return { text : null,
} img : null}},
show : function() {},
// Set draw functions for each item hide : function() {},
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){ let clockInfoMenu = clock_info.addInteractive(clockInfoItems, {
return false; x : 0,
y: 135,
w: W,
h: H-135,
draw : (itm, info, options) => {
var hideClkInfo = info.text == null;
g.setColor(g.theme.fg);
g.fillRect(options.x, options.y, options.x+options.w, options.y+options.h);
g.setFontAlign(0,0);
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
} }
var menuEntry = menu[settings.menuPosX]; // In case we hide the clkinfo, we show the time again as the time should
var item = menuEntry.items[settings.menuPosY-1]; // be drawn larger.
return item.run !== undefined; if(hideClkInfo){
} drawTime();
function runMenuItem(){
if(settings.menuPosY == 0){
return; return;
} }
var menuEntry = menu[settings.menuPosX]; // Set text and font
var item = menuEntry.items[settings.menuPosY-1]; var image = info.img;
try{ var text = String(info.text);
var ret = item.run(); if(text.split('\n').length > 1){
if(ret){ g.setMiniFont();
Bangle.buzz(300, 0.6); } else {
g.setSmallFont();
} }
} catch (ex) {
// Simply ignore it... // 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();
} }
});
/************************************************ /************************************************
* 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){ // Clear region and draw time
// First clear the time region
var y = H/5*2 + (isFullscreen() ? 0 : 8);
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 != "");
if(hasText){
g.setFontAlign(0,0); g.setFontAlign(0,0);
g.drawString(timeStr, W/2, y);
// 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('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;
} }
Bangle.on('charging', charging);
if(is_lower){ let kill = function(){
Bangle.buzz(40, 0.6); clockInfoMenu.remove();
settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1); delete clockInfoMenu;
};
drawMenuAndTime(); E.on("kill", kill);
}
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...
}
});
/************************************************ /************************************************
* 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

View File

@ -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"],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -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

View File

@ -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

View File

@ -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="))

View File

@ -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){
colours=colours.concat(colours2);
shuffle(colours);
} else {
shuffle(colours);
shuffle(colours2);
colours=colours.concat(colours2);
}
var maxN = colours.length;
if (process.env.HWVERSION == 1){
Bangle.setLCDMode("direct"); Bangle.setLCDMode("direct");
Bangle.setLCDTimeout(0); // keep screen on Bangle.setLCDTimeout(0); // keep screen on
}
readN(); readN();
drawN(); drawN();
setWatch(() => { Bangle.setUI("updown", (v)=>{
setN(N+1); if (!v){
drawN();
}, BTN1, {repeat:true});
setWatch(() => {
writeN(); writeN();
drawPerimeter(); drawPerimeter();
choose(); choose();
}, BTN2, {repeat:true}); } else {
setN(N-v);
setWatch(() => {
setN(N-1);
drawN(); drawN();
}, BTN3, {repeat:true}); }
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 551 B

View File

@ -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();
}
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -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"}
] ]
} }

View File

@ -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

View File

@ -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();

View File

@ -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
} }

View File

@ -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"}],

View File

@ -13,11 +13,9 @@
} }
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()
}; };
E.showMenu(menu); clock_info.load().forEach(e=>{
} if(e.dynamic) {
const colorKey = e.name + "_color";
function showCircleMenu(circleId) { menu[e.name+/*LANG*/' color'] = {
const circleName = "circle" + circleId;
const colorKey = circleName + "color";
const colorizeIconKey = circleName + "colorizeIcon";
const menu = {
'': { 'title': /*LANG*/'Circle ' + circleId },
/*LANG*/'< Back': ()=>showMainMenu(),
/*LANG*/'color': {
value: valuesColors.indexOf(settings[colorKey]) || 0, value: valuesColors.indexOf(settings[colorKey]) || 0,
min: 0, max: valuesColors.length - 1, min: 0, max: valuesColors.length - 1,
format: v => namesColors[v], format: v => namesColors[v],
onchange: x => save(colorKey, valuesColors[x]), onchange: x => save(colorKey, valuesColors[x]),
},
/*LANG*/'colorize icon': {
value: settings[colorizeIconKey] || false,
format: () => (settings[colorizeIconKey] ? 'Yes' : 'No'),
onchange: x => save(colorizeIconKey, 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);
}
function showCircleMenus() {
const menu = {
'': { 'title': /*LANG*/'Colorize icons'},
/*LANG*/'< Back': ()=>showMainMenu(),
};
for(var circleId=1; circleId<=4; ++circleId) {
const circleName = "circle" + circleId;
const colorKey = circleName + "color";
const colorizeIconKey = circleName + "colorizeIcon";
menu[/*LANG*/'circle ' + circleId] = {
value: settings[colorizeIconKey] || false,
format: () => (settings[colorizeIconKey]? /*LANG*/'Yes': /*LANG*/'No'),
onchange: x => save(colorizeIconKey, x),
};
}
E.showMenu(menu); E.showMenu(menu);
} }

View File

@ -1 +1,2 @@
0.01: First release 0.01: First release
0.02: Update clock_info to avoid a redraw and image allocation

View File

@ -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;
}
} }
] ]
}; };

View File

@ -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",

View File

@ -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

View File

@ -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"}],

View File

@ -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);
}); });

View File

@ -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.

View File

@ -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();

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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"}
] ]
} }

View File

@ -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);
});

View File

@ -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.

View File

@ -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))
.sort((a,b)=>{
var n=(0|a.sortorder)-(0|b.sortorder);
if (n) return n; // do sortorder first if (n) return n; // do sortorder first
if (a.name<b.name) return -1; if (a.name<b.name) return -1;
if (a.name>b.name) return 1; if (a.name>b.name) return 1;
return 0; 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

View File

@ -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",

View File

@ -1 +1,2 @@
0.1: New App! 0.1: New App!
0.2: Now with timer function

View File

@ -7,3 +7,15 @@ 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

View File

@ -1,11 +1,17 @@
// 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{
@ -24,6 +30,60 @@ function getSteps() {
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;

View File

@ -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",

View File

@ -0,0 +1 @@
0.01: App created!

View File

@ -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
![image](watch-in-use.jpg)
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

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4f/AoP//+iiE00u++/nnMooWSyhT/AA8C9u27dtAQNkgEKAwdQCIUD5MkyVJAQNOwG9DIf+oARBgIPDAQOZoHSA4cj0ARIyeA6AIDpnAI4X2FgXcwgRBp0SDQYRCgErKAVt+ARBi4HC3AREAAnMCIIFCgN4CJOuiYRDgFmCKFXhIR/CMSPFCI0reYazCCJEDuzXGUIvoCIXCfY0Brdt284pOfqjLBBwQCCzNArf///9DoMx3gaBEAYTBnmA5wZCiQDB4+QCIeY+3bFgPQFg3QCIeXKwdMCJfOCIcbI4gRLhIhCvARMR4oRPWYIR/CNNnCI+Z9u20AREtCPHzMbtv8wEXv7GB3ARHAQXJoGuA4VCCIjdCCIWTwHQfY8DnIHDAQNACI+AgNvHwIACI4Nd23btoCC3x3BEQsggEKCAm26iIGAH4ATA=="))

BIN
apps/geminiclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

File diff suppressed because one or more lines are too long

View File

@ -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}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

View File

@ -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.

View File

@ -2,12 +2,10 @@
Gipy allows you to follow gpx traces on your watch. Gipy allows you to follow gpx traces on your watch.
![Screenshot](screenshot1.png) ![Screenshot](splash.png)
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,22 +52,26 @@ 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:
![Screenshot](screenshot2.png) ![Screenshot](legend.png)
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
@ -79,7 +81,18 @@ On your screen you can see :
- 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.
![Lost](lost.png)
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

View File

@ -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 ?

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